As the REST API SDK for PHP V2 is deprecated, I wrote a simple PHP class to make a payment with PayPal through cURL and verifying it. Note :
- I didn't add shipment details for payment because didn't need that.
- For some reasons, I want to redirect users to the PayPal gateway (and not using popup method)
- class paypal-curl.class.php :
<?php
class paypalCurl {
private $url ; // base url of PayPal
private $id; // client id
private $secret; // secret id
private $accessToken = null; // changed by each request
public function init($id,$secret,$base):void
{
$this->url = $base;
$this->id = $id;
$this->secret = $secret;
}
private function makeAccessToken():bool
{
$url = $this->url;
$id = $this->id;
$secret = $this->secret;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . '/v1/oauth2/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials");
curl_setopt($ch, CURLOPT_USERPWD, $id . ':' . $secret);
$headers = array();
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
curl_close($ch);
$result = json_decode($result);
if (!empty($result->access_token)) {
$this->accessToken = $result->access_token;
return true;
}
return false;
}
public function makePaymentURL($orderId,$price,$currency,$return)
{
$url = $this->url;
$response = new stdClass();
$response->status = false;
//first request for a new access token
if ($this->makeAccessToken()) $accessToken = $this->accessToken;
else {
$response->msg = "could not create access token!";
return $response;
}
//then request for a payment url
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . '/v2/checkout/orders');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{
"intent": "CAPTURE",
"purchase_units": [
{
"items": [
{
"name": "Convert Excel",
"description": "Pay For ' . $orderId.'",
"quantity": "1",
"unit_amount": {
"currency_code": "'.$currency.'",
"value": "'.$price.'"
}
}
],
"amount": {
"currency_code": "'.$currency.'",
"value": "'.$price.'",
"breakdown": {
"item_total": {
"currency_code": "'.$currency.'",
"value": "'.$price.'"
}
}
}
}
],
"application_context":{
"shipping_preference":"NO_SHIPPING",
"return_url": "'.$return.'",
"cancel_url": "'.$return.'"
}
}');
$headers = array();
$headers[] = 'Content-Type: application/json';
$headers[] = 'Authorization: Bearer '.$accessToken;
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
$response->msg = curl_error($ch);
curl_close($ch);
return $response;
}
$result = json_decode($result);
if (!empty($result->links[1]->href)) { // success
$PayURL = $result->links[1]->href;
$response -> url = $PayURL;
$response -> status = true;
return $response;
}
elseif(!empty($result->message)){ // failed
$response->msg = $result->message;
}
curl_close($ch);
return $response;
}
public function verify($token) { // to verify payment
if (empty($token)) return false;
$response = new stdClass();
$response-> state = false;
//first create another access token
if ($this->makeAccessToken()) $accessToken = $this->accessToken;
else {
$response-> ref = "could not make access token!";
return $response;
}
$url = $this->url;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . '/v2/checkout/orders/'.$token.'/capture');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = array();
$headers[] = 'Content-Type: application/json';
$headers[] = 'Authorization: Bearer '.$accessToken;
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
$response-> ref = "could not connect to paypal gateway";
curl_close($ch);
return $response;
}
//retrieve data from json
if (!empty($result)) {
$pay = json_decode($result);
if (!empty($pay->status) && $pay->status == 'COMPLETED') {
//success!
$response->state = true;
$response->ref = $pay->status;
//get details
$punits = $pay->purchase_units;
$captures = $punits[0]->payments->captures;
$response->id = $captures[0]->id; // transaction id
$response->amount = $captures[0]->amount->value; // returns float value
$response->currency = $captures[0]->amount->currency_code;
$response->email = $pay->payment_source->paypal->email_address; //payer email
} else {
//maybe captured already! [ORDER_ALREADY_CAPTURED]
$details = $pay->details;
$response->ref = $details[0]->issue;
}
//echo "<pre>";var_dump($pay);echo "</pre>";
} else {
$response->ref = "result was empty!";
}
curl_close($ch);
return $response;
}
}
To make a payment :
<?php require_once __DIR__."/paypal-curl.class.php"; $base = "https://api-m.sandbox.paypal.com"; //$base = "https://api-m.paypal.com"; $id = "XXXX"; $secret = "YYYY"; //init input $order = time(); $price = 10; $currency = "USD"; $return = "https://example.com/verify.payment.php"; //make payment $paypal = new paypalCurl(); $paypal->init($id,$secret,$base); $result = $paypal->makePaymentURL($order,$price,$currency,$return); if ($result->status === true) { header("location:". $result->url); die; } else { //raise error echo $result->msg; die; }
To make a test payment (verify.payment.php) :
<?php
require_once __DIR__."/paypal-curl.class.php";
$token = $_GET["token"];
$base = "https://api-m.sandbox.paypal.com";
//$base = "https://api-m.paypal.com";
$id = "XXXX";
$secret = "YYYY";
$paypal = new paypalCurl();
$paypal -> init($id,$secret,$base);
$result = $paypal->verify($token);
var_dump($result);
So my specific question is:
Should I compare value of "Price value that PayPal JSON returns" with "mine in database"?
Any suggestion or overall improvement would be appreciated.
-
\$\begingroup\$ Re "depreciated": Do you mean "deprecated"? \$\endgroup\$Peter Mortensen– Peter Mortensen2022年11月11日 21:47:31 +00:00Commented Nov 11, 2022 at 21:47
1 Answer 1
Main Question
I haven't really used the Paypal API though I have reviewed other code using it including this project using the Laravel framework.
Should I compare value of "Price value that PayPal JSON returns" with "mine in database"?
In that code the class PaypalPayment
has a handle()
method which loads a Package record from the database and compares the price
property with the value amount from the Paypal authorization response. If the value is different it returns a response containing an error message that the funds were not captured.
$package = Package::find($request->input('package_id')); $user = User::find(session()->get("user")); // Check whether the amount captured corresponds to the expected amount $amountCaptured = intval($responseAuthorization->result->amount->value); if ($package->price !== $amountCaptured) { throw new HttpResponseException(response()->json(['success' => false, 'message' => 'Erreur lors de la capture des fonds.'], 500)); }
It appears that code uses the deprecated SDK, and there is a new Server SDK though it is considered a beta release.
Suggestions
Consider using a normal constructor method
It is okay to use a method called init()
to setup properties in the paypalCurl
class, but typically idiomatic PHP would use a constructor method __construct()
for such purposes. Also, Constructor Promotion could be used to simplify that constructor method.
Consider using Heredoc syntax for POST fields sent to cURL request
Using Heredoc syntax could allow for the elimination of appending strings together when String interpolation is used.
Consider following a style guide
Many PHP programmers follow recommendations outlined in PSR-1 and PSR-12 for idiomatic code.
Consider using constant where appropriate
Some values that are never changed could be stored in constants, and possibly defined in a common file to be included where necessary.
-
\$\begingroup\$ Thank you for the reply. So probably I should compare it too! \$\endgroup\$Kranchi– Kranchi2025年03月29日 09:50:32 +00:00Commented Mar 29 at 9:50