2
\$\begingroup\$

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)
  1. 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;
 
 }
 
 }
  1. 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;
     }
    
  2. 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.

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Oct 24, 2022 at 9:31
\$\endgroup\$
1
  • \$\begingroup\$ Re "depreciated": Do you mean "deprecated"? \$\endgroup\$ Commented Nov 11, 2022 at 21:47

1 Answer 1

2
\$\begingroup\$

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.

answered Feb 27 at 19:27
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for the reply. So probably I should compare it too! \$\endgroup\$ Commented Mar 29 at 9:50

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.