Add person-to-bot payments

To add in functionality to allow a user to make a payment directly to your bot, you have to add in a few pieces of functionality:

  • Create a listener to hear the pay command from a user.
  • Create an interactive callback to start the payment processing.
  • Create web server endpoints to allow for finalizing the payment once the user returns from PayPal, and for handling cancellations during the payment.

Listen for the pay command

Set up a controller to listen for user pay command messages to your bot.

/****************************************************************
* If user has requested a direct user payment, provide a
* confirmation before pushing to callback for processing
****************************************************************/
controller.hears('pay', 'direct_message', function(bot, message) {
  bot.reply(message, {
    attachments:[{
      title: 'Would you like to pay for this service?',
      callback_id: '111',
      attachment_type: 'default',
      actions: [{
        name: 'yes',
        text: 'Yes',
        value: 'yes',
        type: 'button',
      },{
        name: 'no',
        text: 'No',
        value: 'no',
        type: 'button',
      }]
    }]
  });
});

Call the controller hears method to prompt the user with an interactive button to confirm the payment. The bot will reply with yes/no buttons asking if the user would like to pay for the service.

Create the interaction callback to start payment

Once the user clicks on the yes or no buttons, handle that activity through the interactive message callback. If no was clicked, simply cancel out. If yes was clicked, create a link to go to PayPal to begin the payment.

/****************************************************************
* Handle all payment requests in callback, once the user has 
* selected that they want to process a payment / subscription
****************************************************************/
controller.on('interactive_message_callback', function(bot, message) {
  // If user clicks no for payment, cancel, otherwise process payment
  if (message.actions[0].value === 'no') {
    bot.replyInteractive(message, 'Payment process cancelled');
  } 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
      }]
    };
  
    // Build PayPal payment request
    var payReq = JSON.stringify({
      intent:'sale',
      redirect_urls: {
        return_url: redirect + '/process',
        cancel_url: redirect + '/cancel'
      },
      payer: {
        payment_method: 'paypal'
      },
      transactions: [{
        description: 'This is the payment transaction description.',
        amount: {
          total: '10',
          currency: 'USD'
        }
      }]
    });
         
    // Create payment request before PayPal redirect to approve
    paypal.payment.create(payReq, function(error, payment) {
      if (error) {
        console.error(error);
      } else {
        // Capture HATEOAS links
        var links = {};
        payment.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');
        }
      }
    });
  }
});

There are a few steps you go through in the above code, once the user clicks the yes button:

  • Create an interactive reply immediately to let the user know that the processing has started, so that they don't think that something is going wrong.
  • Create the interactive reply that will house the link to PayPal, sine you can't automatically redirect the user to a web address.
  • Create the payment request object, including all payment information needed.
  • Call payment.create(...), passing in the payment request object, to start the payment process and obtain a PayPal redirect link.
  • If the payment is started successfully, extract the approval URL (where you need to redirect the user to), add it to our bot reply, and display the reply to the user.

Create an endpoint to finalize payment

Once the user confirms payment, they will be redirected back to our application, to the /process endpoint. To handle this redirect, add a /process HTTP GET endpoint.

/****************************************************************
* Process a direct PayPal payment once user is redirected back 
****************************************************************/
controller.webserver.get('/process', function(req, res) {
  // Extract payment confirmation information needed to process payment
  var paymentId = req.query.paymentId;
  var payerId = { payer_id: req.query.PayerID };

  // Attempt to complete the payment for the user
  paypal.payment.execute(paymentId, payerId, function(error, payment) {
    if (error) {
      console.error(JSON.stringify(error));
    } else {
      if (payment.state == 'approved') { 
        res.send('Payment completed successfully');
      } else {
        res.send('Payment not successful');
      }
    }
  });
});

Once that endpoint is triggered, extract the payment ID and payer ID that is sent from PayPal. Once extracted, call payment.execute(...), passing in those variable, to complete payment.

Create an endpoint to handle payment cancellations

If the user cancels during the PayPal payment confirmation step, they will be redirected to the /cancel endpoint in our app. To handle this redirect, add a /cancel HTTP GET endpoint.

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

This example simply displays a payment cancelled message, but your app should provide a properly handled experience.