📜 ⬆️ ⬇️

We integrate Paypal payment into web-application

This article discusses the integration of one-time payments as well as subscription payments using Paypal into a web application. Examples are implemented in PHP, but, in principle, without special problems, the same can be done with the help of other technologies. This method is chosen as a compromise between simplicity and flexibility. This is an attempt to write a guide that will help you quickly understand the topic and integrate Paypal payment into your project.

The article focuses mainly on those who previously did not work with this system. Paypal experts are unlikely to find here something new. But, perhaps, they will point out the shortcomings of this method or advise how this could be done differently.

Account creation


To implement this scheme, we need a business account. PayPal Payments Standard should be enough.
Follow the link and create an account.

Create a sandbox account


To test our application, we will use Paypal Sandbox. We need 2 sandbox account. Account of the buyer (buyer) and account of the seller (facilitator). First of all, you need to set a password for both sandbox accounts. To do this, go to the paypal site in the section for developers . Login, then go to the dashboard . In the menu on the left we find the Sandbox section, the accounts tab. Here we can see 2 sandbox accounts (Buyer and Facilitator).
')


Click on the profile, in the appeared modal window click change password, then save the password.
We set passwords for both accounts. After that you can go to the Paypal Sandbox website and try to login.

Paypal Setup


Now we need to set up a Paypal Facilitator account for which we will receive funds. Go to the site Sandbox, log in using the account facilitator and go to the profile settings. Open the profile menu, select the item my selling tools.



In the Selling online section, select the Website preferences option, click Update. Here you can enable user redirection. After the payment is completed, the default user will be redirected to the specified url. But it is also possible to redirect the user to another url (see below).



You also need to activate Paypal Instant Payment Notifications. To do this, in the section Getting paid and managing my risk, select Instant payment notifications and also click Update.



In the IPN settings, specify the URL on which our IPN Listener will work. This URL must be accessible globally. it will receive notifications of operations.



Turn on Message delivery and save. This completes the account setup. You can proceed to setting up payments directly.

One-time Payments


To begin with, we make one-time payments. This is probably the most common use case. The user simply wants to buy some product or a one-time service. Well, it would be desirable that we no longer need to change anything in the paypal settings. The list of products and prices would be stored in the database of our application, we could change them as we like. For one-time payments, we will use Payment Buttons (PayPal Payments Standard) .

Data structure


The list of goods is stored in the database of our application. We can add delete and edit products at any time. Here is the most simple structure, all information is stored in one table.

But you can complicate the task. For example, to change the price depending on the quantity of goods ordered, or to change the cost depending on the day of the week and time.

Or include in the order a lot of different products.

products - we will store goods here:

idnamepricedescription
oneProduct 11.0...
2Product 24.0...

users - we will store users here:

idfirstnamelastnameemailpassword
315AlanSmithalansmith@example.com$ 1 $ 2z4.hu5. $ E3A3H6csEPDBoH8VYK3AB0
316JoeDoejoedoe@example.com$ 1 $ Kd4.Lf0. $ PGc1h7vwmy9N6EJxac953 /

products_users - to whom we are and what we shipped:

iduser_idproduct_iditems_countcreated_date
one315one32015-09-03 08:23:05

We will also store in our database transaction history in the transactions table:

txn_idtxn_typemc_grossmc_currencyquantitypayment_datepayment_statusbusinessreceiver_emailpayer_idpayer_emailrelation_idrelation_typecreated_date

Form of payment


First, create an order form. We generate the form in our application, where we indicate the main parameters of the order (product name, price, quantity).

Here we can specify any price, name, quantity, etc. The custom field is useful in that you can transfer any data in it. Here we will transmit the product id, user id and possibly other information. We will need this data for further processing of the payment.
If you need to pass several parameters, you can use json or serialization. Or you can use additional fields like on0, on1, os0 and os1. Personally, I did not check it, I found the information here .

The following is an example of a form:

<?php $payNowButtonUrl = 'https://www.sandbox.paypal.com/cgi-bin/websc'; $userId = 315 // id   $receiverEmail = 'xxx-facilitator@yandex.ru'; //email  (   paypal ) $productId = 1; $itemName = 'Product 1'; //   $amount = '1.0'; //  ( 1 .) $quantity = 3; //  $returnUrl = 'http://your-site.com/single_payment?status=paymentSuccess'; $customData = ['user_id' => $userId, 'product_id' => $productId]; ?> <form action="<?php echo $payNowButtonUrl; ?>" method="post"> <input type="hidden" name="cmd" value="_xclick"> <input type="hidden" name="business" value="<?php echo $receiverEmail; ?>"> <input id="paypalItemName" type="hidden" name="item_name" value="<?php echo $itemName; ?>"> <input id="paypalQuantity" type="hidden" name="quantity" value="<?php echo $quantity; ?>"> <input id="paypalAmmount" type="hidden" name="amount" value="<?php echo $amount; ?>"> <input type="hidden" name="no_shipping" value="1"> <input type="hidden" name="return" value="<?php echo $returnUrl; ?>"> <input type="hidden" name="custom" value="<?php echo json_encode($customData);?>"> <input type="hidden" name="currency_code" value="USD"> <input type="hidden" name="lc" value="US"> <input type="hidden" name="bn" value="PP-BuyNowBF"> <button type="submit"> Pay Now </button> </form> 

In fact, the parameters can be much more, detailed information can be found in the documentation . After submitting the form, the user goes to the paypal payment page, where he sees the order details again.



Here the user can pay for the order using a paypal account or using a credit card. Next, the user is redirected back to our site (return parameter), where we can inform him that his payment is being processed.

Instant Payment Notification (IPN)


After the user makes a payment, Paypal processes it and sends a confirmation to our application. To do this, use the service Instant Payment Notification (IPN) .

At the beginning of the article, we set up our Paypal account and set the IPN Notification URL. Now is the time to create an IPN listener that will handle IPN requests. Paypal provides an example implementation of IPN listener . A detailed explanation of the service can be found here . In a nutshell, how it works: Paypal processes the user's payment, sees that everything is fine and the payment is completed successfully. After that, the IPN sends a request to this Notification URL of the following type: Post:
 mc_gross=37.50&protection_eligibility=Ineligible&payer_id=J86MHHMUDEHZU&tax=0.00&payment_date=07%3A04%3A48+Mar+30%2C+2015+PDT&payment_status=Completed&charset=windows-1252&first_name=test&mc_fee=1.39¬ify_version=3.8&custom=%7B%22user_id%22%3A314%2C%22service_provider%22%3A%22twilio%22%2C%22service_name%22%3A%22textMessages%22%7D&payer_status=verified&business=antonshel-facilitator%40gmail.com&quantity=150&verify_sign=AR-ITpb83c-ktcbmApqG4jM17OeQAx2RSvfYZo4XU8YFZrTSeF.iYsSx&payer_email=antonshel-buyer%40gmail.com&txn_id=30R69966SH780054J&payment_type=instant&last_name=buyer&receiver_email=antonshel-facilitator%40gmail.com&payment_fee=1.39&receiver_id=VM2QHCE6FBR3N&txn_type=web_accept&item_name=GetScorecard+Text+Messages&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&handling_amount=0.00&transaction_subject=%7B%22user_id%22%3A314%2C%22service_provider%22%3A%22twilio%22%2C%22service_name%22%3A%22textMessages%22%7D&payment_gross=37.50&shipping=0.00&ipn_track_id=6b01a2c76197 

Our IPN Listener should process this request. In particular:


 <?php /** * Class PaypalIpn */ class PaypalIpn{ private $debug = true; private $service; /** * @throws Exception */ public function createIpnListener(){ $postData = file_get_contents('php://input'); $transactionType = $this->getPaymentType($postData); $config = Config::get(); //        if($transactionType == PaypalTransactionType::TRANSACTION_TYPE_SINGLE_PAY){ $this->service = new PaypalSinglePayment(); } elseif($transactionType == PaypalTransactionType::TRANSACTION_TYPE_SUBSCRIPTION){ $this->service = new PaypalSubscription($config); } else{ throw new Exception('Wrong payment type'); } $raw_post_data = file_get_contents('php://input'); $raw_post_array = explode('&', $raw_post_data); $myPost = array(); foreach ($raw_post_array as $keyval) { $keyval = explode ('=', $keyval); if (count($keyval) == 2) $myPost[$keyval[0]] = urldecode($keyval[1]); } $customData = $customData = json_decode($myPost['custom'],true); $userId = $customData['user_id']; // read the post from PayPal system and add 'cmd' $req = 'cmd=_notify-validate'; if(function_exists('get_magic_quotes_gpc')) { $get_magic_quotes_exists = true; } else{ $get_magic_quotes_exists = false; } foreach ($myPost as $key => $value) { if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) { $value = urlencode(stripslashes($value)); } else { $value = urlencode($value); } $req .= "&$key=$value"; } $myPost['customData'] = $customData; $paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/websc'; //$paypal_url = 'https://www.paypal.com/cgi-bin/websc'; //   IPN  $res = $this->sendRequest($paypal_url,$req); // Inspect IPN validation result and act accordingly // Split response headers and payload, a better way for strcmp $tokens = explode("\r\n\r\n", trim($res)); $res = trim(end($tokens)); /**/ if (strcmp ($res, "VERIFIED") == 0) { //    $this->service->processPayment($myPost); } else if (strcmp ($res, "INVALID") == 0) { //     self::log([ 'message' => "Invalid IPN: $req" . PHP_EOL, 'level' => self::LOG_LEVEL_ERROR ], $myPost); } /**/ } private function sendRequest($paypal_url,$req){ $debug = $this->debug; $ch = curl_init($paypal_url); if ($ch == FALSE) { return FALSE; } curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); curl_setopt($ch, CURLOPT_POSTFIELDS, $req); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); if($debug == true) { curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLINFO_HEADER_OUT, 1); } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // ,  User-Agent -   .     live  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close', 'User-Agent: ' . $this->projectName)); $res = curl_exec($ch); curl_close($ch); return $res; } public function getPaymentType($rawPostData){ $post = $this->getPostFromRawData($rawPostData); if(isset($post['subscr_id'])){ return "subscr_payment"; } else{ return "web_accept"; } } /** * @param $raw_post_data * @return array */ public function getPostFromRawData($raw_post_data){ $raw_post_array = explode('&', $raw_post_data); $myPost = array(); foreach ($raw_post_array as $keyval) { $keyval = explode ('=', $keyval); if(count($keyval) == 2) $myPost[$keyval[0]] = urldecode($keyval[1]); } return $myPost; } } ?> 

After that, if Paypal has confirmed the authenticity of the request, we can proceed to its further processing.

Payment processing


First of all we need to get the value of the custom field, where we passed the order id, user id or something else (depending on the logic of our application). Accordingly, we can get user / order information from our database. You also need to get the transaction id.

Paypal can send confirmation of the same transaction several times. Therefore, we need to check and, if the transaction has not been processed, we process it. If the transaction has already been processed, then we do nothing.

We carry out the validation of the payment. If everything is fine, then you can save the payment information to the database and perform further actions (assign the user status “premium”, order status “paid”, etc.). If the payment did not pass validation, you must establish the reason and contact the user. Further operations, in particular, the cancellation of payment, are carried out manually.

 <?php function processPayment($myPost){ $customData = json_decode($myPost['custom'],true); $userId = $customData['user_id']; $productId = $customData['product_id']; // $userService = new UserService(); $userInfo = $userService->getUserData($userId); //       $transactionService = new TransactionService(); $transaction = $transactionService->getTransactionById($myPost['txn_id']); if($transaction === null){ //      $productService = new ProductService(); $product = $productService->getProductById($productId); //    if($this->validateTransaction($myPost,$product)){ //   .     . $transactionService->createTransaction($myPost); //  -   } else{ //    .    } } else{ //,     .    } } ?> 

Payment validation


Payment validation is highly dependent on the business logic of your application. Specific conditions may be added. For example, the user paid for 15 units of goods, and there are only 10. You can not miss such an order.

However, it makes sense to check such things at the stage of form generation. Payment validation is needed rather to prevent fraud (for example, the user manually increased the quantity of goods in the form of payment, and left the price the same).

There are a few things worth checking out anyway:

 <?php function validateTransaction($myPost,$product){ $valid = true; /* *    */ if($product->getTotalPrice($myPost['quantity']) != $myPost['payment_gross']){ $valid = false; } /* *     */ elseif($myPost['payment_gross'] == 0){ $valid = false; } /* *    */ elseif($myPost['payment_status'] !== 'Completed'){ $valid = false; } /* *    */ elseif($myPost['receiver_email'] != 'YOUR PAYPAL ACCOUNT'){ $valid = false; } /* *   */ elseif($myPost['mc_currency'] != 'USD'){ $valid = false; } return $valid; } ?> 

And, of course, add your own checks.

As a result, you should work one-time payments. At the stage of creating a payment form, we can specify any parameters. For example, you can flexibly control the price of goods (2 for the price of 3, each buyer has a 30% discount, etc.). We do not need to change anything in Paypal for this.

Subscriptions


Now consider the implementation of subscriptions. The principle is the same as with one-time payments. Only payments are repetitive. Therefore, their implementation is somewhat more complicated.

Several tariff plans are available, for example, Free - for free, Pro - $ 5 per user per month, Premium - $ 10 per user per month.
The user can cancel the subscription with a refund for the unused period. The user can also change the subscription terms, for example, switch to another tariff plan, or change the number of users.

It is clear that for Free subscription paypal is not needed at all. Perhaps this tariff plan should be activated automatically, immediately when a user registers with our application. This scheme is good because it shows typical usage for some SaaS system. And on the move it is not very clear how to implement this using Paypal.

To work with subscriptions, you will need additional tables:

subscription_plans - to store tariff plans:

idservice_providerservice_namepriceprice_typeperiod
oneServicepro5.00usermonth
2Serviceenterprise10.00usermonth
3Servicefree0.00usermonth

subscriptions - to store subscriptions:

iduser_idplan_idsubscription_idcreated_dateupdated_datepayment_dateitems_countstatus

Subscription form


The subscription form is very similar to the one-time payment form.

 <?php $payNowButtonUrl = 'https://www.sandbox.paypal.com/cgi-bin/websc'; $userId = 1 // id   $receiverEmail = 'xxx-facilitator@gmail.com'; //email  (   paypal ) $serviceId = 1; $serviceName = 'Service Pro'; //  ( ) $servicePrice = '5.00'; //   - 5$  1    $quantity = 3; //   $amount = $servicePrice * $quantity; //   - 15$   $returnUrl = 'http://your-site.com/subscription?status=paymentSuccess'; $customData = ['user_id' => $userId, 'service_id' => $serviceId ]; ?> <form id="createSubscription" action="<?php echo $payNowButtonUrl; ?>" method="post" target="_top"> <input type="hidden" name="cmd" value="_xclick-subscriptions"> <input type="hidden" name="business" value="<?php echo $receiverEmail; ?>"> <input type="hidden" name="lc" value="GB"> <input type="hidden" name="item_name" value="<?php echo $serviceName; ?>"> <input type="hidden" name="no_note" value="1"> <input type="hidden" name="no_shipping" value="1"> <input type="hidden" name="return" value="<?php echo $returnUrl; ?>"> <input type="hidden" name="src" value="1"> <input type="hidden" name="a3" value="<?php echo $amount; ?>"> <input type="hidden" name="p3" value="1"> <input type="hidden" name="t3" value="M"> <input id="customData" type="hidden" name="custom" value="<?php echo json_encode($customData); ?>"> <input type="hidden" name="currency_code" value="USD"> <button type="submit">Subscribe</button> </form> 

The subscription price is set by parameter a3. The subscription period is set using the parameters p3 and t3 (in this example, payments occur every month).

A detailed description of these and other parameters can be found in the documentation .

IPN


With IPN, basically everything is the same as with one-time payments. True, we will receive more requests because more events need to be handled: subscription creation, subscription payment, subscription cancellation, etc. As before, you need to check the accuracy for each request and only then process it.

Subscription Validation


Everything is a bit more complicated here than with one-time payments. We need to validate not only the payment, but also the creation of a subscription, cancellation of the subscription, possibly a change in the subscription. Perhaps something else, depending on the logic of the application. For example, we want the Pro tariff plan to create no more than 100 users. Or something like that. Again, all this can be tried to take into account at the stage of form creation.

What exactly needs to be checked in this case:


 <?php function validateSubscription($subscriptionPlan,$myPost){ $userId = $myPost['customData']['user_id']; $userService = new UserService(); $userInfo = $userService->getUserData($userId); $customData = $this->getCustomData($myPost); //    if($myPost['txn_type'] == 'subscr_cancel'){ $subscriptionService = new SubscriptionService(); $subscription = $subscriptionService->loadBySubscriptionId($myPost['subscr_id']); if(!$subscription->id){ //   return false; } } //   elseif($myPost['txn_type'] == 'subscr_payment'){ //    if($subscriptionPlan->price * $myPost['customData']['items_count'] != $myPost['mc_gross']){ return false; } // ,     0 if($myPost['mc_gross'] == 0){ return false; } //   if($myPost['receiver_email'] != 'xxx-facilitator@yandex.ru'){ return false; } //  if($myPost['mc_currency'] != 'USD'){ return false; } //   if($myPost['payment_status'] != 'Completed'){ return false; } } //   elseif($myPost['reason_code'] == 'refund' && $myPost['payment_status'] == 'Refunded'){ $transactionService = new TransactionService(); $lastTransaction = $transactionService->getLastActiveTransactionBySubscription($myPost['subscr_id']); //,    if(!$lastTransaction){ return false; } //,        if(abs($myPost['mc_gross']) > $lastTransaction['mc_gross']){ return false; } } return true; } ?> 

Payment processing


After successful validation, you can continue processing the payment. Here we have several subscription states:

Depending on the status of the subscription, requests will be processed differently.

 <?php function processPayment($myPost){ $customData = $this->getCustomData($myPost); $userId = $customData['user_id']; $userService = new UserService(); $userInfo = $userService->getUserData($userId); $subscriptionPlanService = new SubscriptionPlanService(); $subscriptionPlan = $subscriptionPlanService->getSubscriptionPlan($myPost); $transactionService = new TransactionService(); $subscriptionService = new SubscriptionService(); if(validateSubscription($subscriptionPlan,$myPost)){ $subscription = $subscriptionService->loadBySubscriptionId($myPost['subscr_id']); $transaction = $transactionService->getTransactionById($myPost['txn_id']); //  if($subscription->id){ //    if($myPost['txn_type'] == 'subscr_payment'){ //     if(!$transaction){ //   $subscription->status = 'active'; $subscription->payment_date = $myPost['payment_date']; $subscription->updated_date = date('Ymd H:i:s'); $subscription->save(); //   $myPost['relation_id'] = $subscription->id; $myPost['relation_type'] = 'transaction'; $transactionService->createTransaction($myPost); } else{ //  .     } } //   if($myPost['txn_type'] == 'subscr_cancel'){ $subscription->status = 'cancelled'; $subscription->updated_date = date('Ymd H:i:s'); $subscription->save(); } //   if($myPost['txn_type'] == 'subscr_eot'){ $subscription->status = 'expired'; $subscription->updated_date = date('Ymd H:i:s'); $subscription->save(); } //    if($myPost['txn_type'] == 'subscr_signup'){ } //       .  .     if($myPost['txn_type'] == 'subscr_modify'){ $subscription->status = 'modified'; $subscription->updated_date = date('Ymd H:i:s'); $subscription->save(); } //   if($myPost['payment_status'] == 'Refunded' && $myPost['reason_code'] == 'refund'){ //      $transactionService->updateTransactionStatus($myPost['parent_txn_id'],'Refunded'); //   () $myPost['txn_type'] = 'refund'; $myPost['relation_id'] = $subscription->id; $myPost['relation_type'] = 'subscription'; $transactionService->createTransaction($myPost); } } //    else{ //     if($myPost['txn_type'] == 'subscr_payment'){ $activeSubscriptions = $subscriptionService->getActiveSubscriptions($userId); // ,      . if(count($activeSubscriptions) > 0){ // ,        } elseif(!$transaction){ //   $subscription = new Subscription(); $subscription->user_id = $userId; $subscription->plan_id = $subscriptionPlan->id; $subscription->subscription_id = $myPost['subscr_id']; $subscription->created_date = date("Ymd H:i:s"); $subscription->updated_date = date('Ymd H:i:s'); $subscription->payment_date = $myPost['payment_date']; $subscription->items_count = $customData['items_count']; $subscription->status = 'active'; $subscriptionId = $subscription->save(); //   $myPost['relation_id'] = $subscriptionId; $myPost['relation_type'] = PaypalTransaction::TRANSACTION_RELATION_SUBSCRIPTION; $transactionService = new PaypalTransaction(); $transactionService->createTransaction($myPost); } else{ //    } } //  .      ,         if($myPost['txn_type'] == 'subscr_signup'){ } //  .     ..     if($myPost['txn_type'] == 'subscr_modify'){ } } } else{ //     } } ?> 

Cancel subscription


We implement the cancellation of the subscription, in case the user gets tired of using our application. In this case, use Paypal Classic Api to cancel the subscription.

To work with the API, we will need Username, Password and Signature. They can be found in the profile settings.



Subscription cancellation is done using the ManageRecurringPaymentsProfileStatus method .

 <?php // $profile_id - id  ( $myPost['subscr_id']) // $action - 'Cancel' public function changeSubscriptionStatus($profile_id, $action, $apiCredentials){ $api_request = 'USER=' . urlencode( $apiCredentials['username'] ) . '&PWD=' . urlencode( $apiCredentials['password'] ) . '&SIGNATURE=' . urlencode( $apiCredentials['signature'] ) . '&VERSION=76.0' . '&METHOD=ManageRecurringPaymentsProfileStatus' . '&PROFILEID=' . urlencode( $profile_id ) . '&ACTION=' . urlencode( $action ) . '&NOTE=' . urlencode( 'Profile cancelled at store' ); $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, 'https://api-3t.sandbox.paypal.com/nvp' ); // For live transactions, change to 'https://api-3t.paypal.com/nvp' curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_POST, 1 ); // Set the API parameters for this transaction curl_setopt( $ch, CURLOPT_POSTFIELDS, $api_request ); // Request response from PayPal $response = curl_exec( $ch ); // If no response was received from PayPal there is no point parsing the response if( ! $response ){ return false; } curl_close( $ch ); // An associative array is more usable than a parameter string parse_str( $response, $parsed_response ); return $parsed_response; } ?> 

There is some problem with this method, since we cannot cancel a subscription if it has already been canceled. But we also cannot check the status of the subscription. Therefore, it is necessary to cancel all subscriptions (in the normal situation, we will not have to cancel the subscription twice). This problem is described in this post .

Refund (full / partial)


Perhaps, besides canceling the subscription, the user would like to return the money for the unused period (note: subscribed for a month, canceled a week later - you need to return 75% of the cost).

You can also use Paypal Classic Api, RefundTransaction method for this .

 <?php // $transaction_id - $myPost['txn_id'] // $amount -    public function refundTransaction($transaction_id,$apiCredentials$amount = null){ $transaction_id = $transaction['txn_id']; $refundType = 'Full'; if($amount){ $amount = round($amount, 2, PHP_ROUND_HALF_DOWN); $amount = str_replace(',','.',$amount); $refundType = 'Partial'; } $api_request = 'USER=' . urlencode( $apiCredentials['username'] ) . '&PWD=' . urlencode( $apiCredentials['password'] ) . '&SIGNATURE=' . urlencode( $apiCredentials['signature'] ) . '&VERSION=119' . '&METHOD=RefundTransaction' . '&TRANSACTIONID=' . urlencode( $transaction_id ) . '&REFUNDTYPE=' . urlencode( $refundType ) . '&CURRENCYCODE=' . urlencode( 'USD' ); if($amount){ $api_request .= '&AMT=' . urlencode( $amount ); } $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, 'https://api-3t.sandbox.paypal.com/nvp' ); // For live transactions, change to 'https://api-3t.paypal.com/nvp' curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_POST, 1 ); // Set the API parameters for this transaction curl_setopt( $ch, CURLOPT_POSTFIELDS, $api_request ); // Request response from PayPal $response = curl_exec( $ch ); // If no response was received from PayPal there is no point parsing the response if( ! $response ){ return false; } curl_close( $ch ); // An associative array is more usable than a parameter string parse_str( $response, $parsed_response ); return $parsed_response; } ?> 

You can use the following code to calculate the refund amount. The code is designed to calculate the return of a monthly subscription.

 <?php public static function getTransactionRefundAmount($transaction){ $paymentDate = date('Ym-d',strtotime($transaction['payment_date'])); $currentDate = date('Ym-d'); $paymentDate = new DateTime($paymentDate); $currentDate = new DateTime($currentDate); $dDiff = $paymentDate->diff($currentDate); $days = $dDiff->days; $daysInMonth = cal_days_in_month(CAL_GREGORIAN,$currentDate->format('m'),$currentDate->format('Y')); $amount = $transaction['mc_gross'] - $transaction['mc_gross'] * $days / $daysInMonth; $amount = round($amount, 2, PHP_ROUND_HALF_DOWN); $amount = str_replace(',','.',$amount); return $amount; } ?> 

Subscription change


Now we add the ability to change the subscription terms. This is needed if the user wants to change the tariff plan, or the number of users. Unfortunately, paypal imposes certain restrictions on changing the subscription.

This issue is discussed here.

I don't want to check this information for myself. , , , . . .

, , . . , , , .

Conclusion


Paypal. -.

( , ..).

. Thank you all for your attention. . .

Upd : , Daniyar94 . PDT IPN. . habrahabr.ru/post/266091/#comment_8560801

Source: https://habr.com/ru/post/266091/


All Articles