Add agreement for recurring payment

To enable customers to approve or cancel an agreement for a recurring payment for a service:

1. Create a billing plan.
2. Create a listener.
3. To start payment processing, create the interactive callback.
4. To enable customers to approve or cancel the agreement, create an approval endpoint and a cancellation endpoint.

Create a billing plan

Before you can process billing agreements for customers, you must set up a one-time billing plan with agreement details.

To integrate with the bot, create a /createplan endpoint that, when triggered, creates a plan with your details and returns a billing plan ID that customers use to subscribe to the agreement.

/*******************************************************************
* Create a billing plan
*******************************************************************/
controller.webserver.get('/createplan', function(req, res) {
  // Billing plan configuration
  var billingPlanAttribs = {
    name: 'Membership: Standard',
    description: 'Monthly plan for my bot service',
    type: 'fixed',
    payment_definitions: [{
      name: 'Standard Plan',
      type: 'REGULAR',
      frequency_interval: '1',
      frequency: 'MONTH',
      cycles: '11',
      amount: {
        currency: 'USD',
        value: '19.99'
      }
    }],
    merchant_preferences: {
      setup_fee: {
        currency: 'USD',
        value: '1'
      },
      cancel_url: redirect + '/cancel',
      return_url: redirect + '/process',
      max_fail_attempts: '0',
      auto_bill_amount: 'YES',
      initial_fail_amount_action: 'CONTINUE'
    }
  };
  
  // Attributes for PATCH call to activate billing plan
  var billingPlanUpdateAttributes = [{
    op: 'replace',
    path: '/',
    value: {
      state: 'ACTIVE'
    }
  }];

  // Create billing plan
  paypal.billingPlan.create(billingPlanAttribs, function (error, billingPlan) {
    if (error) {
      console.log(JSON.stringify(error));
      throw error;
    } else {
      // Activate plan by changing status to active
      paypal.billingPlan.update(billingPlan.id, billingPlanUpdateAttributes, function(error, response) {
        if (error) {
          console.log(JSON.stringify(error));
          throw error;
        } else {
          res.send('Billing plan created under ID: ' + billingPlan.id);
        }
      });
    }
  });
});

When the /createplan endpoint is triggered, the app:

  • Creates the billing plan configuration details, which include payment information, processing and cancel endpoints, and so on.

  • Creates the billingPlanUpdateAttributes variable for the update billing plan call.

  • Calls billingPlan.create(...) with billing plan information to create the plan.

  • Calls billingPlan.update(...) with the billingPlanUpdateAttributes variable to activate the billing plan.

Note the returned billing plan ID because you need it later on.

Create a listener

Set up your app to listen for subscribe messages from customers:

/****************************************************************
* If a customer approves an agreement for a service, provide a
* confirmation before pushing to callback for processing
****************************************************************/
controller.hears('subscribe', 'direct_message', function(bot, message) {
  bot.reply(message, {
    attachments: [{
      title: 'Would you like to subscribe to the service?',
      callback_id: '333',
      attachment_type: 'default',
      actions: [{
        name: 'yes',
        text: 'Yes',
        value: 'yes',
        type: 'button',
      },{
        name: 'no',
        text: 'No',
        value: 'no',
        type: 'button',
      }]
    }]
  });
});

When a customer types subscribe in a direct message to the bot, the app provides interactive yes and no buttons that the customer uses to approve or cancel the agreement.

Create the interactive callback

After the customer clicks yes or no, the app handles that activity through the interactive message callback. For no, simply cancel the agreement. For yes, create a link to redirect the customer to PayPal to approve the agreement:

/****************************************************************
* After the customer approves the agreement, handle payment 
* requests in the callback
****************************************************************/
controller.on('interactive_message_callback', function(bot, message) {
  // If customer clicks no for payment, cancel, otherwise process payment
  if (message.actions[0].value === 'no') {
    bot.replyInteractive(message, 'Payment process canceled');
  } else {
    bot.replyInteractive(message, 'Generating your payment link. Hold on a moment...');
    
    // Prepare base payment message for bot to respond with
    var reply = {
      attachments: [{
        fallback: 'Payment initiation information failed to load',
        color: '#36a64f',
        pretext: 'Click the link below to initiate payment',
        title: 'Make payment to COMPANY',
        footer: 'PayPal Payment Bot',
        footer_icon: 'https://s3-us-west-2.amazonaws.com/slack-files2/avatars/2016-08-17/70252203425_a7e48673014756aad9a5_96.jpg',
        ts: message.ts
      }]
    };
  
    var billingPlan = 'YOUR ACTIVATED BILLING PLAN ID';
        
    // Build isodate for billing agreement
    var isoDate = new Date();
    isoDate.setSeconds(isoDate.getSeconds() + 4);
    isoDate.toISOString().slice(0, 19) + 'Z';
        
    // Create billing agreement configuration
    var billingAgreementAttributes = {
      name: 'Standard Membership',
      description: 'Membership Description',
      start_date: isoDate,
      plan: {
        id: billingPlan
      },
      payer: {
        payment_method: 'paypal'
      }
    };

    // Use activated billing plan to create agreement
    paypal.billingAgreement.create(billingAgreementAttributes, function (error, billingAgreement) {
      if (error) {
        console.error(JSON.stringify(error));
        throw error;
      } else {
        // Capture HATEOAS links
        var links = {};
        billingAgreement.links.forEach(function(linkObj) {
          links[linkObj.rel] = {
            href: linkObj.href,
            method: linkObj.method
          };
        })

        // If redirect url present, insert link into bot message and display
        if (links.hasOwnProperty('approval_url')) {
          reply.attachments[0].title_link = links['approval_url'].href;
          bot.replyInteractive(message, reply);
        } else {
          console.error('no redirect URI present');
        }
      }
    });
  }
});

After the customer clicks yes, the app:

  • Creates an immediate, interactive reply to notify the customer that processing has begun.

  • Creates an interactive reply that houses the link to PayPal because you cannot automatically redirect the customer to a web address.

  • Populates the billingPlan variable with the plan ID that was returned when the billing plan was created.

  • Creates the billing agreement attributes.

  • Calls billingAgreement.create(...) with the agreement attributes to get the PayPal redirect.

  • If the agreement is created successfully, extracts the approval URL. Redirects the customer to this URL, adds it to the bot reply, and displays the reply to the customer.

After the customer clicks that link, the app redirects him or her to PayPal to approve the agreement.

Create an approval endpoint

After the customer confirms the agreement on PayPal, redirect him or her to the /processagreement endpoint in your application.

To handle this redirect, add an HTTP GET /processagreement endpoint.

/****************************************************************
* After customer is redirected, process the billing agreement 
****************************************************************/
controller.webserver.get('/processagreement', function(req, res) {
  // Extract validation token needed to process agreement
  var token = req.query.token;
  
  // Attempt to complete the billing agreement for the customer
  paypal.billingAgreement.execute(token, {}, function (error, billingAgreement) {
    if (error) {
      console.error(JSON.stringify(error));
      throw error;
    } else {
      res.send('Billing agreement created successfully');
    }
  });
});

After that endpoint is triggered, extract the subscription token that PayPal sends. To complete the payment, call billingAgreement.execute(...) with the token.

Create a cancellation endpoint

If the customer cancels the agreement during the PayPal agreement confirmation step, redirect him or her to the /cancel endpoint in your application.

To handle this redirect, add an HTTP GET /cancel endpoint:

/****************************************************************
* Payment incomplete: Customer canceled the transaction on PayPal
****************************************************************/
controller.webserver.get('/cancel', function(req, res) {
  res.send('Payment canceled');
});

This example displays a Payment canceled message. Your app should provide a properly handled experience.