After spending two days figuring out how to cleanly call PayPal's SOAP API in the sandbox environment, I was pretty much gutted when I couldn't get the same code working against the production environment.
I just kept getting the same error:
[Errors] => stdClass Object
[ShortMessage] => Security error
[LongMessage] => Security header is not valid
[ErrorCode] => 10002
[SeverityCode] => Error
If you've hit this issue yourself then you're probably sick and tired by now of reading forum post after forum post telling people that this error only occurs when you use the wrong credentials or try to use the right credentials to access the wrong endpoint. Blah blah blah blah blah... yeah, read it a million times... got sick of reading it!
In my case I was following all the rules (or so I thought). I was definitely instantiating my SOAPClient with the URL for the production WSDL and I was definitely using production credentials. I even tried dropping the credentials and re-creating them, as suggested in one or two forum posts, but that didn't help.
I also whipped up some code to do a GetBalance() request in the production environment using the NVP API and that worked, proving beyond a skeric of doubt that my credentials were, indeed, correct.
And I did RTFM too, which, in hindsight, possibly contributed to the problem, because the PHP manual clearly states that you only need to provide the location of the endpoint as one of the options to the SOAPClient constructor if you're running in non-WSDL mode. I guess this makes sense, because the WSDL should contain the correct URL for the endpoint, right?
Let me repeat that...
The breakthrough came when, in desperation, I read through all of the comments on the SOAPClient page and discovered a hint in the second last comment, made by a Mr Jim Plush all the way back in 2005. The comment was this:
As of version 5.0.4 you can now dynamically change your location even if you're using a wsdl
So I thought to myself, "What the hell, I'll just explicitly set the location of the endpoint and see if that solves the problem".
And guess what? IT WORKED!!!!
You are not going to believe this, but at the time of writing, the endpoints, as defined in the PRODUCTION Paypal WSDL, are:
<wsdl:port name="PayPalAPI" binding="ns:PayPalAPISoapBinding">
<wsdl:port name="PayPalAPIAA" binding="ns:PayPalAPIAASoapBinding">
Yeah, it says "sandbox" in there. D'oh...
So if you are hitting this issue and you are using the more common signature-type authentication (as opposed to certificate authentication), the code to instantiate your SOAPClient object should look something like this:
$client = new SoapClient( 'https://www.paypal.com/wsdl/PayPalSvc.wsdl',
array( 'location' => 'https://api-3t.paypal.com/2.0/',
'soap_version' => SOAP_1_1 ));
I guess the big lesson in all this - again - is to always double check all assumptions, even those that you wouldn't normally question, when trying to resolve an issue.
I gotta tell you, the idea of lurking on StackOverflow until somebody asked this question again so that I could post the answer and get, like, a bazillion rep points did occur to me, but then I thought to myself, "I was only able to solve this problem due to a hint I got from somebody who was generous enough with their time to post their thoughts on php.net. Therefore, the right thing to do would be to make this information available ASAP so that others can benefit". So I did.
But I'm still going for those rep points if I spot this issue on StackOverflow. :-)