How does Paypal achieve circular deductions (subscriptions)?

Posted by matt_wood87 on Tue, 16 Jul 2019 20:06:31 +0200

cause

Business needs to integrate Paypal to achieve circular deduction function, but Baidu and GOOGLE have a circle, except for the official website, did not find the relevant development tutorials, had to look at Paypal, spent two days after the integration success, here on how to use Paypal payment interface to make a summary.

Paypal now has multiple interfaces:

  1. Express Checkout is implemented through Braintree.
  2. Create App, through REST Api interface (now the mainstream interface);
  3. The interface of NVP/SOAP API apps (old interface);

Braintree interface

Braintree is a company acquired by Paypal, which not only supports Paypal's payment, but also provides a complete set of management, such as upgrade plan, credit card, customer information, which is more convenient to use. These functions Paypal's second REST interface actually integrates most of them, but Paypal's Dashboard can't directly manage these letters. Braintree is OK, so I'd rather use Braintree. The key is that the backend framework I use is Laravel, whose cashier solution supports Braintee by default, so this interface is my first choice. But when I realized all its functions, I found a problem: Braintree doesn't support it in China... Pawn...

REST API

This is a product of the times. If you have used OAuth 2.0 and REST API before, there should be no confusion about these interfaces.

Old interface

It is not recommended to use REST API interfaces unless they are unsatisfactory, such as policy constraints. The world is moving towards OAuth 2.0 authentication and REST API usage. Why go against the trend? So when the REST API can solve the problem, I haven't made an in-depth comparison with this set of interfaces.

Introduction to REST API

Official API Reference Documents https://developer.paypal.com/webapps/developer/docs/api/ The API and its usage are introduced in detail, but it is tedious to adjust these APIs directly by ourselves. At the same time, we just want to complete the business requirements as soon as possible instead of falling into the deep understanding of APIs.

So how to start? It is recommended to install the officially provided ones directly. PayPal-PHP-SDK Through its Wiki as a starting point.

Before completing the first example, make sure you have a Sandbox account and have it configured correctly:

  1. Client ID
  2. Client Secret
  3. Webhook API (must be at the beginning of https and port 443. Local debugging suggests generating addresses with ngrok reverse proxy)
  4. Returnurl (note Ibid.)

After completing the first example of Wiki, understanding the classification of the following interfaces will help you fulfill your business needs. Now I will introduce the classification of interfaces. Please understand it with examples. http://paypal.github.io/PayPal-PHP-SDK/sample/#payments.

  • Payments one-time payment interface does not support revolving donations. The main contents of payment include supporting Paypal payment, credit card payment, support through saved credit cards (need to use Vault interface, there will be such interface is mainly PCI requirements, do not allow general websites to collect sensitive information of credit cards), support payment to third-party payees.
  • Payouts is not used, ignored;
  • Authorization and Capture supports accessing your website directly through Paypal's account and obtaining relevant information.
  • Sale has something to do with malls. It's not used, it's ignored.
  • Order has something to do with malls, it's not used, it's ignored.
  • Billing Plan & Agreements upgrade plan and contract, that is, subscription function, to achieve circular deduction must use the function here, which is the focus of this article;
  • Vault Stores Credit Card Information
  • Payment Experience is not used, ignored;
  • Notifications deal with Webhook information, which is important, but not the focus of this article.
  • Invoice Bill Processing;
  • Identity authentication processing, OAuth 2.0 login, get corresponding token to request other API s, this Paypal-PHP-SDK has been done, this article will not talk about.

How to Realize Recycling Deduction

There are four steps:

  1. Create an upgrade plan and activate it.
  2. Create a subscription (Create Agreement), then jump to Paypal's website and wait for user's approval.
  3. Subscription is executed with the user's consent
  4. Obtaining Deduction Bills

1. Create an upgrade plan

The upgrade plan corresponds to the Plan class. There are several points to note in this step:

  • After the upgrade plan is created, it is in the state of CREATED and must be changed to ACTIVE before it can be used properly.
  • Plan s have two objects, Payment Definition and Merchant Preferences, which can't be empty.
  • If you want to create a TRIAL-type plan, the plan must also have a matching payment definition of REGULAR, otherwise you will make a mistake.
  • Look at the code for calling a setSetupFee (very, very, very important) method, which sets the cost of the first deduction after the subscription is completed, while the circular deduction method of the Agreement object sets the cost of the second start.

Take the plan to create a Standard as an example, with the following parameters:

$param = [
        "name" => "standard_monthly",
        "display_name" => "Standard Plan",
        "desc" => "standard  Plan for one month",
        "type" => "REGULAR",
        "frequency" => "MONTH",
        "frequency_interval" => 1,
        "cycles" => 0,
        "amount" => 20,
        "currency" => "USD"
    ];

Create and activate the program code as follows:

     //The $param example above is an array, and my actual application is actually an object that the user understands.
    public function createPlan($param)
    {
        $apiContext = $this->getApiContext();
        $plan = new Plan();

        // # Basic Information
        // Fill up the basic information that is required for the plan
        $plan->setName($param->name)
            ->setDescription($param->desc)
            ->setType('INFINITE');//Examples are always set to infinite loops

        // # Payment definitions for this billing plan.
        $paymentDefinition = new PaymentDefinition();

        // The possible values for such setters are mentioned in the setter method documentation.
        // Just open the class file. e.g. lib/PayPal/Api/PaymentDefinition.php and look for setFrequency method.
        // You should be able to see the acceptable values in the comments.
        $paymentDefinition->setName($param->name)
            ->setType($param->type)
            ->setFrequency($param->frequency)
            ->setFrequencyInterval((string)$param->frequency_interval)
            ->setCycles((string)$param->cycles)
            ->setAmount(new Currency(array('value' => $param->amount, 'currency' => $param->currency)));

        // Charge Models
        $chargeModel = new ChargeModel();
        $chargeModel->setType('TAX')
            ->setAmount(new Currency(array('value' => 0, 'currency' => $param->currency)));

        $returnUrl = config('payment.returnurl');
        $merchantPreferences = new MerchantPreferences();
        $merchantPreferences->setReturnUrl("$returnUrl?success=true")
            ->setCancelUrl("$returnUrl?success=false")
            ->setAutoBillAmount("yes")
            ->setInitialFailAmountAction("CONTINUE")
            ->setMaxFailAttempts("0")
            ->setSetupFee(new Currency(array('value' => $param->amount, 'currency' => 'USD')));

        $plan->setPaymentDefinitions(array($paymentDefinition));
        $plan->setMerchantPreferences($merchantPreferences);
                // For Sample Purposes Only.
        $request = clone $plan;

        // ### Create Plan
        try {
            $output = $plan->create($apiContext);
        } catch (Exception $ex) {
            return false;
        }
        $patch = new Patch();

        $value = new PayPalModel('{"state":"ACTIVE"}');

        $patch->setOp('replace')
            ->setPath('/')
            ->setValue($value);
        $patchRequest = new PatchRequest();
        $patchRequest->addPatch($patch);

        $output->update($patchRequest, $apiContext);

        return $output;
    }

2. Create a subscription (Create Agreement), then jump to Paypal's website and wait for user approval.

After Plan s are created, how to get users to subscribe is actually to create agreements. As for agreements, there are the following points of attention:

  • As mentioned earlier, the setSetupFee method of the Plan object sets the cost of the first deduction after the subscription is completed, while the circular deduction method of the Agreement object sets the cost of the second start.
  • The setStartDate method sets the time of the second deduction, so if you cycle monthly, it should be the current time plus one month. At the same time, the method requires that the time format is ISO8601 format, which can be easily solved by using Carbon library.
  • When creating Agreements, there was no unique ID generated at this time, so I had a little difficulty: how do I know which user this subscription is when the user has finished subscribing? The token in the URL obtained by Agreement's getApprovalLink method is unique. I extract the token as an identification method and replace it with the real ID after the user has completed the subscription.

The example parameters are as follows:

$param = [
    'id' => 'P-26T36113JT475352643KGIHY',//ID generated when Plan s were created in the previous step
    'name' => 'Standard', 
    'desc' => 'Standard Plan for one month'
];

The code is as follows:

   public function createPayment($param)
    {
        $apiContext = $this->getApiContext();
        $agreement = new Agreement();

        $agreement->setName($param['name'])
            ->setDescription($param['desc'])
            ->setStartDate(Carbon::now()->addMonths(1)->toIso8601String());

        // Add Plan ID
        // Please note that the plan Id should be only set in this case.
        $plan = new Plan();
        $plan->setId($param['id']);
        $agreement->setPlan($plan);

        // Add Payer
        $payer = new Payer();
        $payer->setPaymentMethod('paypal');
        $agreement->setPayer($payer);

        // For Sample Purposes Only.
        $request = clone $agreement;

        // ### Create Agreement
        try {
            // Please note that as the agreement has not yet activated, we wont be receiving the ID just yet.
            $agreement = $agreement->create($apiContext);

            // ### Get redirect url
            // The API response provides the url that you must redirect
            // the buyer to. Retrieve the url from the $agreement->getApprovalLink()
            // method
            $approvalUrl = $agreement->getApprovalLink();
        } catch (Exception $ex) {
            return "create payment failed, please retry or contact the merchant.";
        }
        return $approvalUrl;//Jump to $approvalUrl and wait for user approval
    }

The function returns $approvalUrl after execution, and remember to jump to Paypal's website through redirect($approvalUrl) and wait for the user to pay.

Subscription is executed with the user's consent

After the user agrees, the subscription is not completed, and the real subscription can only be completed by executing the Agreement execute method. The key to this step is

  • After the subscription is completed, it does not mean a deduction, and may be delayed for a few minutes.
  • If the setSetupFee fee for the first step is set to zero, the order will not be generated until the time for the revolving deduction is reached.

The code snippet is as follows:

    public function onPay($request)
    {
        $apiContext = $this->getApiContext();
        if ($request->has('success') && $request->success == 'true') {
            $token = $request->token;
            $agreement = new \PayPal\Api\Agreement();
            try {
                 $agreement->execute($token, $apiContext);
            } catch(\Exception $e) {
                return ull;
            return $agreement;
        }
        return null;
    }

Acquisition of transaction records

Subscribed transactions may not generate transaction deduction records immediately. If they are empty, try again in a few minutes. Notes in this step:

  • start_date and end_date cannot be empty
  • In actual testing, the object returned by this function can not always return empty JSON objects, so if you need to output JSON, please manually extract the corresponding parameters according to the API instructions of AgreementTransactions.
    /** Acquisition of transaction records
     * @param $id subscription payment_id
     * @warning Always retrieve all records of this subscription
     */
    public function transactions($id)
    {
        $apiContext = $this->getApiContext();

        $params = ['start_date' => date('Y-m-d', strtotime('-15 years')), 'end_date' => date('Y-m-d', strtotime('+5 days'))];
        try {
            $result = Agreement::searchTransactions($id, $params, $apiContext);
        } catch(\Exception $e) {
            Log::error("get transactions failed" . $e->getMessage());
            return null;
        }
        return $result->getAgreementTransactionList() ;
    }

Finally, Paypal officially also has the corresponding tutorials, but it calls the native interface. Unlike the above process, it only talks about the first three steps for interesting reference: https://developer.paypal.com/docs/integration/direct/billing-plans-and-agreements/.

Issues to be considered

The function has been realized, but a lot of attention has also been found:

  1. When using Sandbox test in China, the connection is very slow, and often prompts for timeouts or errors. Therefore, special consideration should be given to the situation when executing midway user closes the page.
  2. Be sure to implement webhook, otherwise your website will not be notified when the user enters Paypal to cancel his subscription.
  3. Once an agreement is created, it will remain in force unless it is cancelled voluntarily. So if your website has multiple upgrade plans (such as Basic,Standard,Advanced), when the user has subscribed to a plan, to switch the upgrade plan, the development must cancel the previous upgrade plan;
  4. Users agree to subscribe - (cancel old subscriptions - complete new subscriptions - modify user information to new subscriptions). The whole process in parentheses should be atomic and time-consuming, so it should be queued for execution until the successful experience is better.

Topics: PHP REST SDK JSON