While a number of other payment processors have sprung up in recent years, Paypal continues to remain one of the de facto standards because of its relative prevalence and its ability to tokenize personal information. Many users have come to prefer Paypal for websites and services that may not have yet been fully vetted and would thus prefer to not hand over personal information, like credit card numbers, directly.

That said, integrating with Paypal is almost always a great business move as it will aid in capturing as many customers as possible for your web service or platform.

This post will walk you through a basic integration for Paypal with recurring billing. Paypal's latest REST API will be used in combination with Paypal-Checkout.

Terms

Before diving in, it is worth explaining a few terms that Paypal uses often when it comes to recurring billing:

  1. Agreement - An agreement is essentially a subscription, that is, a recurring payment that happens based on a predetermined schedule.

  2. Billing Plan - A billing plan provides the basic information to create an agreement. Things like cost, term and taxes are defined on a billing plan.

Integrating Checkout.js

To begin, Paypal-Checkout (checkout.js) will need to be included on the web page(s) where you would like to offer support for Paypal subscriptions.

This can be done by adding the following script to the scripts area of your web page:

<script src="https://www.paypalobjects.com/api/checkout.js" data-version-4></script>

Next, you will want add a <div> where the Paypal button should be shown:

<div class="paypal-button"></div>

Lastly, some Javascript code will be required to instantiate the button and remaining functions. You can add the following directly to a web page, or you can add the Javascript to a separate js file and include that in your web page.

paypal.Button.render({

    env: 'sandbox', // Or 'production',
    commit: true, // Show a 'Pay Now' button
    client: {
        'sandbox': 'YOUR CLIENT ID HERE', // switch to 'production' if in prod
    }

    payment: function(data, actions) {
        /* 
         * Set up the payment here 
         */
         
         // The goal here is to return a promise or callback that
         returns a payment ID
    },

    onAuthorize: function(data, actions) {
        /* 
         * Execute the payment here 
         */
         
         // Once the user has authorized the subscription, you will
         need to execute the agreement here.
    },
}, '#paypal-button');

If you reload your page you should now see a button similar to what is shown below:

Paypal Button

Creating Plans

Next you will need to create a plan to offer to your customers. Currently Paypal does not offer a web interace to do this, so plans must be configured via the REST API. That said, the best source for examples here would be the Paypal API documentation directly, found here.

Creating an Agreement

With plans created you can now begin the process of creating your first agreement. Paypal-Checkout does not offer direct support for billing agreements so you will need to manually handle this information server side.

First, write the server endpoint - in this example Python, the Flask framework and the Paypal Python SDK will be used (pip install paypalrestsdk - docs here) will be utilized:

# import standard flask modules
from urllib.prase import urlparse, parse_qs

@billing.route('/billing/paypal/agreement/token', methods=['POST'])
def get_agreement_token():
    plan_id = json.get('plan_id') // Get the plan ID from the request
    
    agreement = paypalrestsdk.BillingAgreement({})
    
    if agreement.create():
        parsed_url = urlparse(agreement.links[0]['href'])
        return jsonify({ 'token': parse_qs(parsed_url.query)['token'][0]})
    
    return jsonify({ 'error': 'failed'})

The goal with this endpoint is to take the plan ID selected by your customer and use it to retrieve a payment token. The token is retrieved on this line: parsed_url = urlparse(agreement.links[0]['href']) and then returned as a JSON object here: return jsonify({ 'token': parse_qs(parsed_url.query)['token'][0]}).

Back on the Javascript side, you can write an AJAX call to this endpoint like so:

paypal.Button.render({

    env: 'sandbox', // Or 'production',
    commit: true, // Show a 'Pay Now' button
    client: {
        'sandbox': 'YOUR CLIENT ID HERE', // switch to 'production' if in prod
    }

    payment: function(data, actions) {
        var plan_id = '1'; // change this to be the actual paypal plan ID
        
        $.ajax({
          url: '/billing/paypal/agreement/token',
          type: 'POST',
          data: JSON.stringify({ plan_id: plan_id }),
          contentType: 'application/json',
          dataType: 'json',
          success: function(data, status, xhr) {
            agreement_id = data.agreement_id;
            resolve(data.token); // resolve the promise so onAuthorize is called
          },
          error: function(xhr, status, error) {
            console.log('checkout error', error);
            reject(error)
          },
});        
    },

    onAuthorize: function(data, actions) {
        /* 
         * Execute the payment here 
         */
         
         // Once the user has authorized the subscription, you will
         need to execute the agreement here.
    },
}, '#paypal-button');

Once the payment token is returned in resolve the user is prompted to accept the agreement you have created. Once this has been done, the onAuthorize handler is called and you will need to execute the agreement for it to complete.

Executing the Agreement

To execute the agreement a separate server side endpoint will need to be created. Here is an example:

@billing.route('/billing/paypal/agreement/execute', methods=['POST'])
def execute_agreement():
    payment_token = json.get('payment_token')
    
    if paypalrestsdk.BillingAgreement.execute(payment_token):
        return jsonify({ 'status': 'success' }) // we're good to go!
        
    return jsonify({ 'error': 'failed' }) // something broke!

This endpoint does not need to return anything in particular, so long as the response code is 200. The payment_token is obtained in the JSON body POSTed to this endpoint, it is then supplied to the Paypal Python SDK and the execute function is called with that parameter. If all is well, the status: success line is returned, else an error is returned.

To integrate this on the Javascript side, another AJAX call will need to be made:


    env: 'sandbox', // Or 'production',
    commit: true, // Show a 'Pay Now' button
    client: {
        'sandbox': 'YOUR CLIENT ID HERE', // switch to 'production' if in prod
    }

    payment: function(data, actions) {
        var plan_id = '1'; // change this to be the actual paypal plan ID
        
        $.ajax({
          url: '/billing/paypal/agreement/token',
          type: 'POST',
          data: JSON.stringify({ plan_id: plan_id }),
          contentType: 'application/json',
          dataType: 'json',
          success: function(data, status, xhr) {
            agreement_id = data.agreement_id;
            resolve(data.token); // resolve the promise so onAuthorize is called
          },
          error: function(xhr, status, error) {
            console.log('checkout error', error);
            reject(error)
          },
});        
    },

    onAuthorize: function(data, actions) {
         $.ajax({
          url: '/billing/paypal/agreement/execute',
          type: 'POST',
          data: JSON.stringify(
            {
              payment_token: payment_token,
            }
          ),
          contentType: 'application/json',
          dataType: 'json',
          success: function(data) {
            // success!
          },
          error: function(xhr, status, error) {
            // failure! 
          },
    },
}, '#paypal-button');

If all is well, the success handler will be called and you can then direct the user to the appropriate page, do further requests or obtain additional information if necessary.

Conclusion

At this point you should have a very basic Paypal integration with support for recurring payments. Please note, the endpoints shown in Python are written under the assumption you have a working Flask application, to do that you will need to import additional modules not explicitly shown above. However, regardless of backend, the flow would be the same:

  1. User clicks Paypal button
  2. Fire request to server to get a payment token
  3. User accepts agreement
  4. Fire another request to server to execute it with the agreement token
  5. success or error is called, redirect appropriately

I hope this has been a helpful look at creating a basic Paypal subscription integration. Please do not hesitate to let me know if you have any questions, thanks!