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.
Related
I'm integrating PayPal using PHP and cURL and managed to create orders and capture the payments via the https://api.sandbox.paypal.com/v2/checkout/orders and
https://api.sandbox.paypal.com/v2/checkout/orders/<order_id>/capture endpoints
The orders I'm trying to refund have been successfully captured and looking at the details it shows their status is "COMPLETED" and the final_capture field is true, so the order has been placed and I can see the transaction ended successfully in the merchant account
Now I'm trying to test refunds using the capture ID I get from the capture call but it always fails with an error with the following response body
{
"error":"invalid_subject",
"error_description":"Subject Authentication failed"
}
I tracked down the problem behind the Subject Authentication failed problem being a wrong Paypal-Auth-Assertion header, that I printed and double checked multiple times already and it seems to be right. Here's the code I use to make the call
// Codeigniter function to read from $_POST
$capture_id = $this->input->post('paypal_capture_id');
$auth_1 = base64_encode("{\"alg\":\"none\"}");
$auth_2 = base64_encode("{\"payer_id\":<payer_id>,\"iss\":<client_id>}");
$auth_assertion_header = $auth_1 . "." . $auth_2 . ".";
$curlSES=curl_init();
curl_setopt($curlSES,CURLOPT_URL,"https://api.sandbox.paypal.com/v2/payments/captures/$capture_id/refund");
curl_setopt($curlSES, CURLOPT_POST, true);
curl_setopt($curlSES, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Bearer '. $access_token,
'PayPal-Auth-Assertion:' . $auth_assertion_header
));
curl_setopt($curlSES, CURLOPT_POSTFIELDS, '{}'); // empty payload means full refund
curl_setopt($curlSES, CURLOPT_RETURNTRANSFER, true);
$result=curl_exec($curlSES);
Where payer_id and client_id are filled using a merchant id account used for the sandbox environment and the client_id is the secret id for the application provided by Paypal, the $access_token has been previously generated from a function I use in other parts of the application and it works fine.
Furthermore, if I try to make the same call using Postman (and the PayPal API explorer as well) it produces a different error, that is
{
"error": "invalid_request",
"error_description": "No permissions to set target_client_id"
}
No search result for this error is really helpful so I'm lost on what I'm doing wrong there, although it doesn't seem related to the Paypal-Auth-Assertion as it falls back to the Subject Authentication failed error if I provide a wrong value on purpose.
I was facing the same issue, when trying to refund a captured order for a partner account. It seems to me u are using the string escaped json from the example.
Try using the json_encode function like below:
$header = base64_encode(json_encode(['alg' => 'none']));
$body = base64_encode(json_encode(['payer_id' => $merchantId, 'iss' => $clientId]));
$authHeader = $header . "." . $body . ".";
I was able to refund a captured order with this $authHeader.
Greetings
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');
}
How to verify POST data comes from PayPal ?
I am working on a store that sells some products. The payment gateway is PayPal.
Initially I set up a custom PayPal form and used the IPN responses to validate the data that is sent to me from PayPal.
Now my client has bought PayPal Advance Payment that uses PayPal PayFlow. The responses are not sent anymore through IPN (or are they?) instead they are returned by SILENT POST, basically when a transaction is perfomed on their end it is sent to a link of my choice and I process data through that link.
How do I validate the source of the POST data, so I know it is coming from PayPal and not a bad intentions user. I can not find any documentation on this. Also I want the same think when a users clicks "Cancel" button on paypal page and it is redirected to cancelation page on my website. I want that POST data source also verified.
Any thoughts on this ?
First solution than I found, also some PayPal support guy has mentioned something similar but he could not offer details as he said he is not an expert.
Basically you have to run a TRANSACTION of type INQUIRY with the received PNREF from SILENT POST, if the response returns ORIGRESULT equal to 0 then the transaction exists in PayPal database under your account.
I also send the SECURE TOKEN ID and SECURE TOKEN with the inquiry, I do not know if it helps or not, I saw this as a solution somewhere and I tried sending just TOKEN and TOKEN ID without ORIGID and sometimes it worked sometimes not.
On the developer reference from PayPal is specified that on a TRXTYPE=I (INQUIRY TRANSACTION) the ORIGID must be specified.
Code below:
//GET POST DATA FROM SILENT POST
$data = $_POST;
$result = $data['RESULT'];
$pnref = $data['PNREF'];
$secure_token = $data['SECURETOKEN'];
$secure_token_id = $data['SECURETOKENID'];
$fee_amount = $data['AMT'];
if(!isset($result) || $result != '0'){
//DO TRANSACTION FAILED OPERATIONS
exit;
}
//CHECK IF PNREF ID EXISTS ON PAYPAL
$post_data = "PARTNER=yourpartnerid"
."&VENDOR=your_vendor"
."&USER=your_user"
."&PWD=your_password"
."&SECURETOKENID=" . $secure_token_id
."&SECURETOKEN=" . $secure_token
."&ORIGID=" . $pnref
."&TRXTYPE=I"
."&VERBOSITY=HIGH";
$ch = curl_init('https://payflowpro.paypal.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$resp = curl_exec($ch);
$inquiry = array();
parse_str($resp, $inquiry);
//IF ORIGRESULT is 0 then PNREF/transaction exists.
if($inquiry['ORIGRESULT'] == '0' && $inquiry['RESPMSG'] == 'Approved'){ $validated = true; }
else{ $validated = false; }
if($result != 0 || $amount != 'your_correct_fee' || $validated == false){
// DO TRANSACTION NOT VALID OR HAS FAILED OPERATIONS
exit;
}
//DO TRANSACTION SUCCESSFULL OPERATIONS
The response from a INQUIRY looks this way:
RESULT=0&PNREF=ETHPC0BBF5FB&TRANSSTATE=8&ORIGRESULT=0&ORIGPNREF=ELFPB0E766F5&RESPMSG=Approved&AVSADDR=N&AVSZIP=N&ORIGPPREF=8GT035513B296200N&CORRELATIONID=97306f6456378&SETTLE_DATE=2014-07-09 14:11:36&TRANSTIME=2014-07-09 14:11:36&FIRSTNAME=John&LASTNAME=doe&AMT=0.0
Another way of doing it is checking the IP from which the SILENT POST is coming.
I noticed all SILENT POST data comes from 173.0.81.65
$ip_address = $_SERVER['REMOTE_ADDR'];
if($ip_address != '173.0.81.65'){ exit; }
#Andrew Angel
SILENT POST and NotifyURL is not the same thing.
From the NotifyURL you can use IPN and data verification it is done very differently there because you receive different kind of post data string back.
I talked with PayPal support and they said NotifyURL and IPN is not available for PayPal Advanced Payments only for PayPal Payments Pro.
Indeed they both do the same function, but they are different things and source validation is done differently.
Hope this helps someone, waiting on opinions.
We have PayPal Payments Advanced and I'm unable to get past the first gateway integration step. Perhaps I'm missing something simple that should be obvious.
All the official PayPal documents I've been able to find for integrating the gateway for Advanced say the first step is to obtain a Secure Token. The page at
https://developer.paypal.com/webapps/developer/docs/classic/payflow/gs_ppa_hosted_pages/
for example.
I'm posting my test script below (sensitive info modified).
Every time I run the test script, I get a "Error: Your transaction can no longer be processed. Please return to the merchant's web site or contact the merchant. Error: 160" error message.
According to the PayPal Gateway Developer Guide and Reference, error 160 is, "Secure Token already been used. Indicates that the secure token has expired due to either a successful transaction or the token has been used three times while trying to successfully process a transaction. You must generate a new secure token."
Yet, the secure token has not already been used. A new one is generated every time the script is run.
"Enable Secure Token" is set to "Yes" in PayPal Manager.
Here is the script. What am I doing wrong?
<?php
$url = 'https://payflowlink.paypal.com';
#$url = 'https://pilot-payflowlink.paypal.com';
$token = md5( 'Will Bontrager' . time() );
/* $info assignment is all one line. Multi-line here for readability */
$info = "PARTNER=PayPal&
VENDOR=CertainReservations&
USER=ABC123&
PWD=321cba&
TRXTYPE=S&
AMT=23.45&
CREATESECURETOKEN=Y&
SECURETOKENID=$token";
echo "<pre>Value:$info</pre>";
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_VERBOSE => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $info
);
$ch = curl_init($url);
curl_setopt_array($ch,$options);
$content = curl_exec($ch);
$err = curl_errno($ch);
$errmsg = curl_error($ch) ;
$info = curl_getinfo($ch);
curl_close($ch);
if( $err )
{
echo "<pre>Error. $err\n$errmsg\n";
print_r($info);
echo '</pre>';
}
echo $content;
?>
Thank you very much for any guidance.
I think I must be missing some critical information.
Will
Just wanted to add that error 160 is also thrown when no secure token is passed to step 2.
Had this happen: host blocked curl calls, so the Paypal iframe was requested without the secure token which resulted in error 160.
According to Page 31 of this Payflow Gateway documentation this might work. I've copied the content here in the event he PDF is removed or moved without a proper 301 redirect.
To create a secure token, pass all parameters that you need to process
the transaction except for payment details parameters such as the
credit card number, expiration date, and check number.
In addition, pass the following Payflow parameters to create the
secure token.
Set SECURETOKENID to a unique alphanumeric value up to 36 characters in length.
SECURETOKENID=9a9ea8208de1413abc3d60c86cb1f4c5
Set CREATESECURETOKEN to the value Y to request that the Gateway server return a token.
CREATESECURETOKEN=Y
Set SILENTTRAN to the value TRUE to suppress the display of hosted pages.
SILENTTRAN=TRUE
Successful transactions will return RESULT=0. From page 33.
A Payflow Secure Token will expire:
If the same Secure Token is passed to Payflow a total of 3 times.
20 minutes after the Secure Token was generated.
When the token is used in a successful transaction
It's likely the formatting... here's a little about the parameters you're passing (from page 51):
Because the ampersand (&) and equal sign (=) characters have special
meanings, they are invalid in a name-value pair value.
The following are invalid:
COMPANYNAME=Ruff & Johnson COMMENT1=Level=5
To include special characters in the value portion of a name-value
pair, use a length tag. The length tag specifies the exact number of
characters and spaces that appear in the value. The following are
valid.
COMPANYNAME[14]=Ruff & Johnson
COMMENT1[7]=Level=5
NOTE: Do not use quotation marks ("") even if you use a length tag.
As far as I can tell (this is not documented clearly that I have found though this is helpful) you can't get a secure token from https://pilot-payflowlink.paypal.com but https://pilot-payflowpro.paypal.com seems to work just fine. Once you have your token you can use it with payflowlink.
Hello everyone I'm back again, In my last post I was attempting to use the SOAP api (Integrating Dwolla with PHP with their API) but I found out the SOAP API is deprecated and apparently Dwolla has more efficient way such as the REST/oAuth2.0 which is why I'm here today asking how to use the rest API as its been almost 2 weeks and I'd really like to learn this.
First off I'll say that I've successfully been able to get an access_token I have no problem doing that. The issue is that when I try to use an a Send Endpoint(https://www.dwolla.com/developers/endpoints/accountapi/send) basically trying to send money to and account. My exact issue is that I can never get a successful response; only false or error message responses.
So on the index page I have "Add funds to your account" link. Users will click that link and it will take them to the Dwolla Page that will accept them to Sign in to their Dwolla account an then accept the permissions the website is asking for. After the user presses "Accept" it will redirect to the selected URL that I chose and send back an access_token to use for authorization purposes. Here is my code (This is the page that Dwolla redirects too and sends the access_token too)
<?php
//Define variables
$key = 'redacted';
$secret = 'redacted';
$dwolla_client_id = urlencode($key);
$dwolla_secret_key = urlencode($secret);
$code = urlencode($_GET["code"]);
//get token
$retireve_token = file_get_contents("https://www.dwolla.com/oauth/v2/token?client_id=".$dwolla_client_id."&client_secret=".$dwolla_secret_key."&grant_type=authorization_code&redirect_uri=http://localhost/purchase_order.php&code=".$code);
$decoded_json = json_decode($retireve_token, true);
var_dump($decoded_json);
if($decoded_json["access_token"]){
$arr = '{
"oauth_token": "'.$decoded_json["access_token"].'",
"fundsSource": "balance",
"pin": "1111",
"notes": "Payment for services rendered",
"amount": 1.01,
"destinationId": "812-111-1111",
"assumeCosts": false,
"facilitatorAmount": 0,
"destinationType": "dwolla"
}';
$opts = array('http'=>array('method'=>"POST",'content'=> $arr, 'header' => 'Content-Type: application/json'));
$ctx = stream_context_create($opts);
$send_request = file_get_contents('https://www.dwolla.com/oauth/rest/accountapi/send', false, $ctx);
var_dump(json_decode($send_request));
}
?>
I receive messages like this for example
array(1) { ["access_token"]=> string(50)
"redacted" } Warning:
file_get_contents(https://www.dwolla.com/oauth/rest/accountapi/send):
failed to open stream: HTTP request failed! HTTP/1.1 503 Service
Unavailable in /home/swiftbitcoins/purchase_order.php on line 47 NULL
what you are trying to make is a get request whereas Dwolla documentation refers to this as post request.
better you can do is user their php Library with built in methods to make calls. this is a standard library for making restful calls and much better than writing the way you have written in the code snippet above.
https://github.com/Dwolla/dwolla-php