i'm using paypal's express checkout API in PHP, and the SetExpressCheckout operation works fine. But when paypal redirect's to my page that handles the GetExpressCheckoutDetails operation, it gives me a 10410: Invalid Token error. I'm using the token they provide in the url, so what's the problem?
Here's the code i'm using:
$token = urldecode($this->params['url']['token']);
$req_str = 'USER=%s&PWD=%s&SIGNATURE=%s&VERSION=%s&METHOD=%s&TOKEN=%';
$req_query = sprintf($req_str, Configure::read('Paypal.username'), Configure::read('Paypal.password'), Configure::read('Paypal.signature'), "65.1", 'GetExpressCheckoutDetails', $token);
I think you are missing a s in
$req_str = 'USER=%s&PWD=%s&SIGNATURE=%s&VERSION=%s&METHOD=%s&TOKEN=%';
HERE ----^
Related
I'm having trouble trying to verify paypal webhook signatures with PHP. Using the new V2 of paypals APIs I am receiving the paypal webhook on my page.
However I can not seem to successfully validate the signature.
From the link HERE I got some sample webhook validation PHP code from paypal.
I can not get it working, I don't know where I am supposed to get bootstrap.php from in the paypal code. The paypal information seems incomplete or half baked. Paypal seems to be terrible to set-up compared to Stripe.
Has anyone got experience of validating paypal webhook signatures with PHP when using V2 of the paypal APIs ?
Well I have come to the conclusion that the Paypal developer information is rather poor, it is scatted all over the place, on multiple different pages and sites. The examples they give on the paypal developer website HERE are not a complete picture of what is required to validate a webhook signature. Stripe developer documentation is much better formatted and concise.
Installing Paypal Checkout V2 SDK does not give you the necessary development tools to validate paypal webhook signatures i.e. you can process payments and receive webhooks but you can not validate the webhook signatures....I know stupid. Tip do not download the SDK directly as you will not include the required autoload.php file. Use composer to install Paypal Checkout V2 SDK so that you get the autoload.php file.
Once you are to a point that you can process payments and receive webhooks form paypal you need to install the another SKD called Paypal Rest API SDK. Again use composer to install the SDK so that you get a autoload.php file which you will need.
When you install Paypal Rest API SDK amazingly you will still be missing files that are required to validate the payapl webhook signatures. I can find no mention of these anywhere on the paypal developer website.
bootstrap.php & common.php
Thanks to #Grumpy I got some samples provided on github HERE
Note you will probably need to modify the samples a bit in order to get them working with your website. Tip set the logger to false and save yourself some bother if you don't have the necessary access permissions to write.
Once you have bootstrap.php & common.php files created you can write the code for your webhook endpoint page i.e. the page that paypal sends the webhook to. I have included my PHP code below for how to validate and then process the paypal webhook. Tip in the below code you need to specify the webhook ID, each webhook that you create in paypal has a unique ID. Also when you are testing you can not use webhook simulator as this will fail validation, you can make manually a payment with your sandbox account details which will trigger a webhook payment event.
Paypal sure don't make it easy, their documentation is all over the place compared to Stripe. The Paypal webhooks can sometimes take several minutes to arrive after a payment is made, very frustrating when trying to debug. Also it's a bit ridiculous that they have a webhook simulator on the paypal developer website that can not be used to validate signatures...if stripe can do it why can't paypal.
<?php
//get the webhook payload
$requestBody = file_get_contents('php://input');
//check if webhook payload has data
if($requestBody) {
//request body is set
} else {
//request body is not set
exit();
}
use \PayPal\Api\VerifyWebhookSignature;
use \PayPal\Api\WebhookEvent;
$apiContext = require __DIR__ . '/bootstrap.php';
//Receive HTTP headers that you received from PayPal webhook.
$headers = getallheaders();
//need header keys to be UPPERCASE
$headers = array_change_key_case($headers, CASE_UPPER);
/*
example header paypal signature content for webhook, these values are recieved as an array, we then need to use this data to verify the payload
CONTENT-LENGTH : 1376
CORRELATION-ID : 6db85170269e7
USER-AGENT : PayPal/AUHD-214.0-54377828
CONTENT-TYPE: application/json
PAYPAL-AUTH-ALGO : SHA256withRSA
PAYPAL-CERT-URL : https://api.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a784-5edc0ebc
PAYPAL-AUTH-VERSION : v2
PAYPAL-TRANSMISSION-SIG : Hc2lsDedYdSjOM4/t3T/ioAVQqFPNVB/AY/EyPNlavXk5WYUfnAmt9dyEP6neAPOjFHiVkXMK+JlLODbr6dalw6i26aFQdsPXqGl38Mafuu9elPE74qgsqNferUFgHi9QFXL+UZCNYcb4mvlDePXZIIAPbB0gOuFGOdEv2uqNwTCSAa/D8aguv1/51FWb3RkytFuVwXK/XNfIEy2oJCpDs8dgtYAZeojH8qO6IAwchdSpttMods5YfNBzT7oCoxO80hncVorBtjj1zQrkoynEB9WNNN9ytepNCkT8l29fQ4Sx/WRndm/PESCqxqmRoYJoiSosxYU3bZP7QTtILDykQ==
PAYPAL-TRANSMISSION-TIME : 2020-04-05T14:40:43Z
PAYPAL-TRANSMISSION-ID : 6dec99b0-774b-11ea-b306-c3ed128f0c4b
*/
//if any of the relevant paypal signature headers are not set exit()
if(
(!array_key_exists('PAYPAL-AUTH-ALGO', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-ID', $headers)) ||
(!array_key_exists('PAYPAL-CERT-URL', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-SIG', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-TIME', $headers))
)
{
exit();
}
//specify the ID for the webhook that you have set up on the paypal developer website, each web hook that you create has a unique ID
$webhookID = "ENTER_YOUR_WEBHOOK_ID_HERE";
//start paypal webhook signature validation
$signatureVerification = new VerifyWebhookSignature();
$signatureVerification->setAuthAlgo($headers['PAYPAL-AUTH-ALGO']);
$signatureVerification->setTransmissionId($headers['PAYPAL-TRANSMISSION-ID']);
$signatureVerification->setCertUrl($headers['PAYPAL-CERT-URL']);
$signatureVerification->setWebhookId($webhookID);
$signatureVerification->setTransmissionSig($headers['PAYPAL-TRANSMISSION-SIG']);
$signatureVerification->setTransmissionTime($headers['PAYPAL-TRANSMISSION-TIME']);
$signatureVerification->setRequestBody($requestBody);
$request = clone $signatureVerification;
try {
$output = $signatureVerification->post($apiContext);
} catch (Exception $ex) {
//error during signature validation, capture error and exit
ResultPrinter::printError("Validate Received Webhook Event", "WebhookEvent", null, $request->toJSON(), $ex);
exit(1);
}
$sigVerificationResult = $output->getVerificationStatus();
// $sigVerificationResult is a string and will either be "SUCCESS" or "FAILURE"
//if not webhook signature failed validation exit
if($sigVerificationResult != "SUCCESS"){
exit();
}
else if($sigVerificationResult == "SUCCESS"){
//paypay webhook signature is valid
//proceed to process webhook payload
//decode raw request body
$requestBodyDecode = json_decode($requestBody);
//pull whatever info required from decoded request body, some examples below
$paymentSystemID = $requestBodyDecode->id;
$eventType = $requestBodyDecode->event_type;
//do something with info captured from the webhook payload
}
This seems to work with php 8.1:
if (openssl_verify(
data: implode(separator: '|', array: [
$httpPayPalTransmissionId,
$httpPayPalTransmissionTime,
$webhookID,
crc32(string: $rawRequestBody),
]),
signature: base64_decode(string: $httpPayPalTransmissionSignature),
public_key: openssl_pkey_get_public(public_key: file_get_contents(filename: $cachedHttpPayPalCertUrl)),
algorithm: 'sha256WithRSAEncryption'
) === 1) {
die('OK');
} else {
die('FAILED');
}
I am trying to get the express checkout token for a PayPal one time purchase integration. I get this error when trying to make the cURL request:
ACK=Failure&L_ERRORCODE0=81002&L_SHORTMESSAGE0=Unspecified%20Method&L_LONGMESSAGE0=Method%20Specified%20is%20not%20Supported&L_SEVERITYCODE0=Error1
Here is my code
<?php
if(!isset($_GET['id'])) {
header("Location: index.php");
exit();
}
//Get paypal express checkout token
$ch = curl_init("https://api-3t.sandbox.paypal.com/nvp");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"USER: seller-v3rm_api1.test.com",
"PWD: <snip>",
"SIGNATURE: <snip>",
"METHOD: SetExpressCheckout",
"VERSION: 93",
"PAYMENTREQUEST_0_PAYMENTACTION: SALE",
"PAYMENTREQUEST_0_AMT: 25",
"PAYMENTREQUEST_0_CURRENCYCODE: USD",
"RETURNURL: http://test/buy.php",
"CANCELURL: http://test.com",
));
$token = curl_exec($ch);
echo $token;
Am i missing something?
You aren't setting up the request string correctly. I would really recommend taking a look at this PayPal PHP SDK.
First, it will eliminate your need to even mess with this sort of code because it handles it all for you. All you would need to do is open the SetExpressCheckout.php template file that it comes with and fill out the parameters, and then the same for the other calls you might use.
Also, though, if you want you could study the code to see how it's handling the cURL request. You need to build an actual NVP string (ie. &something=like&this=example). Then that is what you send to PayPal via cURL.
I'm moving an app from sandbox to live. I've changed to my live api user, api signature, api password and appid.
In addition I modified the api url and paypal url as follows.
`//public $apiUrl = 'https://svcs. sandbox.paypal.com/AdaptivePayments/';`
`//public $paypalUrl = "https://www. sandbox.paypal.com/webscr?cmd=_ap-payment&paykey="; `
`public $apiUrl = 'https://svcs. paypal.com/AdaptivePayments/'; `
`public $paypalUrl = "https://www. paypal.com/webscr?cmd=_ap-payment&paykey=";`
I'm landing on payal at
"url https://www .paypal.com/webscr?cmd=_ap-payment&paykey="
with no paykey populated.
I'm getting a message on the paypal page that says "This transaction is invalid. Please return to the recipient's website and try again."
What am I doing wrong?
Thanks a trillion in advance for your help.
Having recently finished the process of having created the script that retrieves permissions from a account holder I now find that I have to convert the retrieved access token and token secret (from the GetAccessToken response) to the API signature in order to create a X-PAYPAL-AUTHORIZATION header.
The X-PAYPAL-AUTHORIZATION header contains:
A timestamp
The access token from the GetAccessToken response
A signature generated from the following information:
Your API username
Your API password
The access token from the GetAccessToken response
The token secret from the GetAccessToken response
The endpoint for the PayPal API operation's request, such as https://api.paypal.com/nvp
HTTPS delivery method, such as POST
Request parameters associated with the request
The problem is I can't find how to generate the signature. There are no guides in PHP (JAVA and Ruby).
I did however note the line in the guide I followed (first link) to retrieve the permissions:
PayPal provides SDKs that you can use to generate authentication header signatures for Java, PHP, and .NET. When you use the SDK, you will get two values, such as the following:
But what followed was the JAVA guide and I could not find anything amongth Paypal's SDKs.
Any help would be greatly appreciated!
This documentation actually cuts out the function from their PHP SDK that should do it for you.
private function generateAuthString($apiCred, $accessToken, $tokenSecret, $endpoint)
{
$callerUid = $apiCred->getUserName();
$callerPswd = $apiCred->getPassword();
$auth = new AuthSignature();
$response = $auth->genSign($callerUid,$callerPswd,$accessToken,$tokenSecret,'POST',$endpoint);
$authString =
"token=".$accessToken.
",signature=".$response['oauth_signature'].
",timestamp=".$response['oauth_timestamp'];
return $authString;
}
I had an implementation of OAuth working with Fitbit to pull data from fitbit's service. However they recently updated their service and now the request is failing whenever I try to get an access token.
They have made the following statement about the new requirement:
The solution is to OAuth sign the requests to <https://api.fitbit.com/oauth/request_token> and <https://api.fitbit.com/oauth/access_token> in a similar manner that all other calls to the Fitbit API are signed.
Requests to <https://api.fitbit.com/oauth/request_token> need to be signed with your application's consumer key and secret.
Requests to <https://api.fitbit.com/oauth/access_token> need to be signed with your application's consumer key and secret and the oauth_token and oauth_verifier received from the authorization callback.
I am using the PHP PECL OAuth library for OAuth requests. However I can't find a way to add additional parameters to the signature. I am trying the following but I'm not sure that this is the correct way to update the OAuth Signature:
$params['consumer_key'] = $this->consumer_key;
$params['consumer_secret'] = $this->consumer_secret;
$params['oauth_token'] = $this->oauth_token;
$params['oauth_verifier'] = $_REQUEST['oauth_verifier'];
$this->signature = $this->oauth->generateSignature('GET', $this->access_url, $params);
$this->access_token = $this->oauth->getAccessToken($this->access_url, $this->signature, $_REQUEST['oauth_verifier']);
The OAuth error I get is:
401
Invalid auth/bad request (got a 401, expected HTTP/1.1 20X or a redirect)
oauthoauth_signatureInvalid signature: FfvYDv5MSOfwcOwLZBJa0TlKS4Q=false
The signature which is stored from the code above shows that the proper signature should be:
[signature] => wlfzqPs4aEkTkHfqyaO65D/RW6o=
This is the "Headers Sent" piece of the debug information:
[headers_sent] => Authorization: OAuth oauth_session_handle="Frdnxw8oHe3BgNVi0Fy4jBXrZko%3D",
oauth_verifier="ss6nmke8elf3so66jg3auued49",
oauth_consumer_key="(my key)",
oauth_signature_method="HMAC-SHA1",
oauth_nonce="30463910852ea5cc2d04e60.71895372",
oauth_timestamp="1391090882",
oauth_version="1.0",
oauth_token="2cabd6beab341e332bdf8e522b6019ef",
oauth_signature="hULwWcQOl%2F8aYjh0YjR843iVXtA%3D"
I can't find anything in the documentation which explains how I can set the signature for OAuth to use with it's request. Any Help would be greatly appreciated!!!
Please let me know if you need more information!
I have found the issue.
It turns out I was not saving the oauth_token_secret being handed back and I was instead using the consumer secret.
Once I updated this, the process ran as expected.