12
Paying for pizza

This chapter covers

  • Processing payments with serverless applications
  • Implementing payments to your serverless API
  • Understanding the PCI compliance in payment processing

Enter your card number and your card’s expiration date. Now, enter your card’s security code. Everyone knows this sequence. Receiving payments for products or services is the most valuable step for almost every business. So far, you’ve been learning mostly how to develop serverless applications that provide useful services, such as pizza ordering and delivery. But you should also know how to receive payments from Aunt Maria’s customers.

This chapter starts by analyzing how to enable online payments for Aunt Maria’s pizzeria. You’ll see how a payment travels from your customer, to your payment processor, and then to Aunt Maria’s company. Then, you’ll learn how to implement a payment service for Aunt Maria. Afterward, you’ll examine the safety of your serverless payment service and discover how standards compliance helps with that.

12.1 Payment transactions

According to Aunt Maria, “Everything should revolve around customer needs.” Her business has begun to expand, and she has received more than a hundred requests from customers to enable online payments in both the mobile and web applications. Therefore, she has asked you to help her implement accepting payments with the serverless Pizza API.

Before implementing a payment service in your application, let’s briefly touch on how payment transactions work internally.

A payment is a financial transaction between a customer and a seller. The customer pays money to the seller for needed products or services. If the customer doesn’t have any money, the transaction isn’t possible. If the customer has the needed amount, the cash is transferred to the seller, after which the purchased product or service is transferred to the customer. The transaction flow is shown in figure 12.1.

figure-12.1.eps

Figure 12.1 A diagram illustrating a cash transaction between a customer and a seller

For credit and debit cards, the process is slightly different, because you’re not dealing with raw funds (cash). A customer connects a payment card to the seller’s card-reading device. The device checks if it’s a valid credit card, reads the card number, displays the charge amount, and asks the customer for the card’s confidential pin number to certify that the intended transaction is authorized. Then the device sends a request to the customer’s bank to transfer the funds from the customer’s account to the seller’s. If there are sufficient available funds, the bank reserves the amount from the customer’s account. This “reserving” process is also known as “charging” the customer. The bank creates a charge instead of immediately taking the money out of the account because there needs to be a delay in case a problem or an error occurs on either side. The flow for a credit or debit card transaction is shown in figure 12.2.

figure-12.2.eps

Figure 12.2 A diagram illustrating a credit card transaction between a customer and a seller

Online payments are different from in-person credit card payments processed with a card reader. First, because you can’t use a physical card reader, you need a payment processor that can perform online payment processing. Second, again because you don’t have a card reader, you need to verify the card with the payment processor directly. The verification process is necessary because you need to ensure that the card is valid and that it belongs to a valid authority (a bank, for example). Therefore, you need to send sensitive customer data to the payment processor for verification. If it’s valid, you then make a payment request. The third difference is that you can now optionally listen to possible payment status changes, as some charges may be checked by the customer’s bank and rejected a few minutes later (figure 12.3), but for the sake of this chapter’s length, this isn’t covered.

figure-12.3.eps

Figure 12.3 A diagram illustrating an online credit card transaction between a customer and a seller

The online payment process looks complicated, but from your side, it’s quite simple. You have three responsibilities:

  1. Securely send the payment information to the payment processor.
  2. Create a charge on the card upon verification.
  3. After a charge has been created, update the payment-related information.

Seems simple enough. Now that you’ve had a brief overview of the process, let’s see how to implement it for Aunt Maria.

12.1.1 Implementing an online payment

As Aunt Maria has explained, when an order is placed, the customer can currently pay only when the pizza is delivered. Most customers are happy to do it that way, but she’d like to enable customer payment in advance with a card. To enable this capability, her web application needs a page with a payment form, where the customer types in the necessary information. After filling in the form, the customer taps Pay and a charge is made to the customer’s credit card via a payment processor.

Try to visualize the steps of the payment transaction, corresponding to the transaction diagrams. The following actions need to happen:

  1. Show the customer the form with the amount due.
  2. After the customer taps Pay, invoke the function to charge the customer’s card via the payment processor’s API.
  3. After a charge is created, update the order in the database.

The flow is illustrated in figure 12.4.

figure-12.4.eps

Figure 12.4 . A detailed flow diagram illustrating your payment service porcess

Before you start implementing your payment service, you need to choose your payment processor. There are many online payment processors to choose from; in some cases, your bank can do it for you. But the ones that are most renowned and most used are Stripe and Braintree. We could have used either of these, but we chose Stripe: it’s quick and easy to set up and provides multiple-platform support.

Now that you’ve chosen your payment gateway, you need to set up your Stripe account and get your Stripe API keys (as described in appendix C, in the section “Setting up your Stripe account and retrieving Stripe API keys”). If you have a Stripe account already, please continue; if not, create one now.

Now that you’ve got your Stripe account set up, log in to Stripe and open the Stripe Test Dashboard in your browser (or type https://dashboard.stripe.com/test/dashboard in your browser’s address bar). Click the “Accept your first payment” link, which opens a Stripe documentation page explaining how to set up card payments.

As the Stripe card payments setup page explains, there are two crucial steps:

  1. Securely collect payment information using tokenization.
  2. Use the payment information in a charge request.

These steps actually change the previously described payment service process flow. Instead of you requesting the customer’s payment details and sending them to Stripe, you’ll show only the Stripe payment form, which will send the sensitive payment information directly to Stripe. After the payment information is verified and stored, Stripe will send you a secure token—a hexadecimal string that represents this information. This secure token has a limited lifespan of a few minutes and can be used only once. You won’t store the one-time token, but use it to charge the card. The updated flow is shown in figure 12.5.

figure-12.5.eps

Figure 12.5 The updated flow diagram illustrating the Stripe payment process with the use of its secure token to create a card charge

As you can see from the flow, your responsibility has been reduced to a minimum. Your next step is to plan out what you need to implement. Let’s take a look at this step-by-step.

  1. Show the payment form and send information to Stripe —This means you’re going to need to display an HTML page, so you’ll need an HTML document or file. Usually this is done on the front end of a web application, but you’ll implement a basic page so you can test your service and see it in action. Stripe offers a couple of ways to create your own payment form:
    • Mobile SDK
    • Checkout
    • Stripe.js and Elements

    Because you want the form displayed in the browser, the Mobile SDK option doesn’t suit you. Checkout is an already prepared, embedded HTML form, simple and quick to use, whereas using Stripe.js and Elements enables you to create your own style of form. Because you’re using the form just to test, the best choice is the Checkout option.

  2. Receive the secure token from Stripe —The Stripe payment form, in your case Checkout, requires a web service endpoint. This means that in addition to the payment form, you’ll also need to implement a serverless payment API endpoint to receive the secure Stripe data. You’ll create a serverless payment function that will receive the token.
  3. Create a charge using the secure token —Upon receiving the token, your serverless payment service will invoke the Stripe API to charge the customer’s card with the specified amount, currency, and token.
  4. Update the pizza order information based on the payment —If the charge is successfully created, you’ll need to find the customer’s order in the DynamoDB table and update its status to paid (figure 12.6).
figure-12.6.eps

Figure 12.6 The flow diagram illustrating the Stripe payment process with the use of its secure token to create a card charge

This all seems easy enough, so let’s get started.

12.2 Implementing your payment service

Your payment process consists of a payment service and an HTML document, so the first step is to choose which one to start with. We recommend that you start with the payment service, because you’ll need to specify the URL of your deployed payment service on the Stripe Checkout form in the HTML document.

To start, create a new top-level project folder named pizzeria-payments and navigate to it in your terminal.

While in the folder, create your NPM package.json file by running the command npm init -y. Then install the required Claudia API Builder and AWS SDK libraries by running the npm install -S claudia-api-builder aws-sdk command. Because you’re using Stripe as a payment processor, you’ll also need to run npm install -S stripe to install Stripe’s open source Node.js SDK—a library to simplify making requests to Stripe.

Your payment service will receive a request with the Stripe secure token along with the other information required to charge the customer, such as the currency, amount, and the pizza order ID. It will then make a charge request to Stripe using the Stripe Node.js SDK and the received payment information. If the charge is successful, Stripe returns the charge made, so your service should then make another request to the DynamoDB table pizza-orders to update the pizza order corresponding to the provided order ID. After that, your service should return a success message.

Before we show you the implementation, take a few minutes to think about how you would write a testable serverless payment service using Hexagonal Architecture from the start. Which boundary objects will you need?

This exercise should bring you to the conclusion that you need three boundary objects:

  • PaymentRequest—A boundary object with values coming from the Stripe charge request
  • PaymentRepository—A boundary object with a createCharge method to create a charge in the DynamoDB pizza-orders database table
  • PizzaOrderRepository—A boundary object with an updateOrderStatus method to update the DynamoDB pizza-orders database table

You’re going to have the following four files in your service:

  • The main service file, payment.js, which is the starting file, with an exposed POST endpoint built with Claudia API Builder
  • A create-charge.js file, which is responsible for the business logic of creating a payment charge
  • A payment-repository.js file, which is responsible for communicating with Stripe and has only one method, createCharge
  • An order-repository.js file, which is responsible for communicating with AWS DynamoDB to update the pizza order with the new processed payment information

First, you’ll create your payment.js file. In this file, you’ll first define a charge request to Stripe containing the Stripe token, charge amount, and currency, and the order ID inside a metadata attribute. The reason for this request is because Stripe doesn’t allow additional parameters to be sent through its calls. It allows only a metadata property to pass a string. Stripe does not look into that property. Then you need to invoke the createCharge function imported from the create-charge.js file. If it’s successful, send a success message. If not, send a message containing the error report. The contents of the payment.js file are shown in the following listing.

Listing 12.1 The payment.js file with a POST endpoint to accept incoming Stripe requests

'use strict'
const ApiBuilder = require('claudia-api-builder')
const api = new ApiBuilder()    ①  
const createCharge = require('./create-charge')    ②  

api.post('/create-charge', request => {    ③  

    let paymentRequest = {    ④  
      token: request.body.stripeToken,
      amount: request.body.amount,
      currency: request.body.currency,
      orderId: request.body.metadata    ⑤  
    }

    return createCharge(paymentRequest)    ⑥  
        .then(charge => {    ⑦  
            return { message: 'Payment Initiated!', charge: charge }
        }).catch(err => {    ⑧  
            return { message: 'Payment Initialization Error', error: err }
        })
})

module.exports = api    ⑨  

Next, create the create-charge.js file inside the project root. It should first load the payment-repository.js file (for dealing with Stripe API protocols) and the order-repository.js file (for dealing with AWS DynamoDB pizza order protocols), then expose the function to accept a payment request. You should provide a payment description explaining the charge to the customer, then invoke the paymentRepository.createCharge function with the provided token, amount, and currency values to create a payment charge. When this is done, invoke the orderRepository.updateOrderStatus method with the provided orderId representing the ID of the order for which the payment was made. The contents of the create-charge.js file are shown in the following listing.

Listing 12.2 The create-charge.js file that contains the business logic

'use strict'
const paymentRepository = require('./repositories/payment-repository.js')    ①  
const orderRepository = require('./repositories/order-repository.js')    ②  

module.exports = function (paymentRequest) {
  let paymentDescription = 'Pizza order payment'    ③  
  return paymentRepository.createCharge(paymentRequest.token, paymentRequest.amount, 
  paymentRequest.currency, paymentDescription)    ④  
    .then(() => orderRepository.updateOrderStatus(paymentRequest.orderId))    ⑤  
}

Now let’s continue with the implementation of the createCharge method in the payment-repository.js file. First, though, you need to organize the project properly, so create a repositories folder in your project root and navigate to it. Then create the payment-repository.js file, which defines an object containing a single method to create a Stripe charge by invoking the stripe.charges.create method. The parameters passed to the stripe.charges.create method are the stripeToken (the token corresponding to the customer transaction), the amount to charge the customer, the desired currency (the amount is in cents if the provided currency value is usd or eur), and a description of the transaction. The contents of the file are shown in the following listing.

Listing 12.3 The payment-repository.js file defining the createCharge method

'use strict'
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)    ①  

module.exports = {
  createCharge: function (stripeToken, amount, currency, description){
      return stripe.charges.create({    ②  
            source: stripeToken,
            amount: amount,
            currency: currency,
            description: description
        })
  }
}

The implementation of the Stripe protocol in the payment-repository.js file could easily be replaced with the implementation for Braintree or some other payment processor, but that’s left as an exercise for the reader.

Now let’s unwrap the last piece: the order-repository.js file, which is responsible for updating the status of the order in the pizza-orders DynamoDB table to paid.

Listing 12.4 The order-repository.js file that updates the order status in the DynamoDB database table

'use strict'
const AWS = require('aws-sdk')
const docClient = new AWS.DynamoDB.DocumentClient()    ①  

module.exports = {
  updateOrderStatus: function (orderId) {    ②  
    return docClient.put({
        TableName: 'pizza-orders',
        Key: {
          orderId: orderId
        },
        UpdateExpression: 'set orderStatus = :s',    ③  
        ExpressionAttributeValues: {    ④  
            ':s': 'paid'
        }
        }).promise()
    }
}

By applying the principles of Hexagonal Architecture, not only did you make this payment service more testable, but you can now easily replace DynamoDB and try out Amazon Aurora or even the Amazon Relational Database Service (Amazon RDS).

The last step in implementing your payment service is to use Claudia to deploy your new API. In your terminal, while in the project root folder, run the following command to return the URL of your newly created API:

claudia create --region us-east-1 --api-module payment --set-env STRIPE_SECRET_KEY=<your-stripe-secret-key>

Copy and save the URL in a temporary document to use for the HTML form you’re going to make. The only thing that remains is to create your HTML document.

Because you want this serverless payment service to be reusable and have a single purpose, you can’t put the HTML document inside it. So, create a separate project folder named payment-form, and inside it create an HTML document named payment-form.html. In the HTML body element, you’ll create a form element with the action attribute pointing to the URL of your newly created serverless payment service. Inside it, create a script element to load the Stripe Checkout form. This script element needs

  • A data-key attribute (starting with pk_test_)
  • A data-amount attribute, representing the amount you want to charge (in cents, for the USD or EUR currencies)
  • A data-name attribute, showing the name of your Stripe payment window
  • A data-description attribute, describing your Stripe payment transaction
  • A data-image attribute, if you want to include a URL for a logo or image to display in the loaded form
  • A data-locale attribute specifying the locale (you can set this to auto to display the form in the user’s preferred language, if available)
  • A data-zip-code attribute indicating whether to collect the customer’s zip code (a Boolean value)
  • A data-currency attribute, representing the currency short code for the transaction

The contents of the payment-form.html file are shown in the following listing.

Listing 12.5 The payment-form.html file representing the payment page

<html>
<head>
</head>
<body>
<form action="<paste-your-function-url-here>" method="POST">    ①  
  <script
    src="https://checkout.stripe.com/checkout.js" class="stripe-button"    ②  
    data-key="<your-stripe-public-key>"    ③  
    data-amount="100"    ④  
    data-name="Demo Site"    ⑤  
    data-description="2 widgets"    ⑥  
    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"    ⑦  
    data-locale="auto"    ⑧  
    data-zip-code="true"    ⑨  
    data-currency="usd">    ⑩  
  </script>
</form>
</body>
</html>

After you’ve created the payment-form.html file, open it in your browser. You’ll see the Pay button from Stripe in the loaded window. Click it, and the Stripe payment form will appear with the $1 price defined in the example (data-amount="100"). The payment form that loads should look something like figure 12.7.

figure-12.7.tif

Figure 12.7 The payment form

To test your payment service in addition to the payment form, use the following data:

  • A test card number of 4242 4242 4242 4242 (take a look at https://stripe.com/docs/testing#cards, too)
  • Any future month and year for the expiration date
  • Any three-digit number for the card verification code
  • Any random zip code
  • Any email address

Then tap “Pay $1”.

That’s it. After a moment or two, your payment should process. Take a look at your pizza-orders DynamoDB table, and you should see that the pizza order status was updated to paid. Also, be sure to look at your Stripe Dashboard (https://dashboard.stripe.com/test/dashboard) to see the payment. You can look at your CloudWatch logs, too, to see what went right or, in the case of errors, what went wrong.

As you can see, developing and maintaining a serverless payment service is quite easy with Claudia.js and Claudia API Builder. But what about security?

12.3 Could someone hack your payment service?

Having no control over your infrastructure or your environment may be troubling. How do you know there isn’t a malicious service running in the background, stealing your customers' credit card details? And what about the risk of a big data breach or fraud that could ruin your business?

You can’t know what’s going on in the background with your serverless provider. These fears are plausible, because breaches or hacks can occur and wreak havoc in your organization. But two factors are often overlooked that can play a big part in security:

  • Standards
  • Competence

12.3.1 Standards

Having a safe and secure payment processing service is essential, not just for you but for your customers as well. Therefore, security is one of the top priorities in almost every company—at least on paper. Security is evolving constantly, with new issues discovered every day. Naturally, over time, most of the common best practices have converged into a standard, and a body for standards has appeared as well.

The standards body is the Payment Card Industry Security Standards Council, or PCI SSC, a security body responsible for defining and enforcing secure and safe payments and customer data handling practices. The main standard enforced for payment security is the Payment Card Industry Data Security Standard, or PCI DSS.

Services that adhere to the standard are termed PCI DSS-compliant.

What is PCI DSS compliance?

PCI DSS sets the requirements for organizations and sellers to safely and securely accept, store, process, and transmit cardholder data during credit card transactions to prevent fraud and data breaches. Being PCI DSS-compliant means that you’re securely handling cardholder data during a transaction.

You need to meet many requirements to be PCI DSS-compliant, such as setting up firewall configurations, encrypting transaction data, restricting physical access to data, implementing internal company security policies, and so on. To read more about it, download the standard directly from https://www.pcisecuritystandards.org/documents/PCI_DSS_v3-1.pdf.

Currently, almost all the most-used serverless providers are PCI DSS-compliant, including

  • AWS Lambda
  • Microsoft Azure Functions
  • Google Cloud Functions
  • IBM OpenWhisk

To read more about PCI DSS compliance, see the main portal at https://www.pcisecuritystandards.org.

12.3.2 Competence

Security breaches and frauds are almost always possible. Many companies and engineers question the security competence of their infrastructure providers, or in this case their serverless providers. Some even try to develop security themselves, despite the strictness required to achieve PCI compliance.

Even if this effort might be valid in some cases, if you’re tempted to do this, give some thought to whether you or your company are likely to be more competent at securing your data or implementing security than the most-used serverless providers, such as Amazon AWS, Microsoft Azure, Google Cloud, or others.

The security responsibilities involved in payment processing are massive, and getting it wrong can cause a significant blow to you or your customers. Therefore, having a competent and PCI-compliant serverless provider should be one the top priorities when developing your serverless applications.

12.4 Taste it!

As you can see, implementing a serverless payment service is easy and doesn’t take much time. But it’s again time for you to try out what you’ve learned!

12.4.1 Exercise

Your exercise for this chapter is to create a new serverless function that will return a list of the charges you’ve previously created. You must do this by applying Hexagonal Architecture. Before you start, here’s some information about Stripe’s API:

  • To retrieve all previously created charges, you’ll need to use the Stripe listCharges method; you can learn more about it at https://stripe.com/docs/api#list_charges.
  • You need to set it up with your STRIPE_SECRET_KEY and deploy the serverless function using Claudia’s --set-env config option.
  • The charge-listing service should return an empty list if there are no charges.

If that’s enough information, go ahead and try it on your own. If you need additional tips, here are a few more:

  • You need to create an API endpoint called GET /charges using Claudia API Builder.
  • You need to create a ChargeRepository.

If you need more help, or you want to see the solution, check out the next section.

12.4.2 Solution

It’s time to look at the solution. First, here’s an overview of the whole flow.

When a request arrives at your GET /charges API endpoint, you should parse it and call the getAllCharges method of ChargesRepository. The getAllCharges method should call stripe.charges.create with no passed parameters. Afterward, it should parse the Stripe response object and return the list from the data attribute. This list should be sent as an array to the client.

First, create a project folder named charges. Inside that folder, run the command npm init -y and then the command npm install -S claudia-api-builder stripe. Then create the following two files:

  • payment.js, in the project root
  • payment-repository.js, in the repositories folder inside your project

The following two listings are the complete charge-listing code. First is the payment.js file, with a POST /charges endpoint to accept the incoming Stripe requests. The endpoint handler needs to call the paymentRepository.getAllCharges method to get all charges. If it’s successful, it needs to return them, without any additional logic. If not, send a message back to the client, with the error property containing the error.

Listing 12.6 The payment.js file

'use strict'
const ApiBuilder = require('claudia-api-builder')
const api = new ApiBuilder()    ①  
const paymentRepository = require('./repositories/payment-repository')    ②  

api.get('/charges', request => {    ③  

    return paymentRepository.getAllCharges()    ④  
        .catch(err => {    ⑤  
            return { message: 'Charges Listing Error', error: err }
        })
})

module.exports = api    ⑥  

The following listing shows the content of the payment-repository.js file, responsible for retrieving all the Stripe charges you’ve made. It exposes a getAllCharges method, which invokes the stripe.charges.list method without any parameters, because you need to display all charges.

Listing 12.7 The payment-repository.js file

'use strict'
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)    ①  

module.exports = {
  getAllCharges: function (){
      return stripe.charges.list()    ②  
        .then(response => response.data)    ③  
  }
}

Summary

  • Knowing how to implement payments is essential for every application, regardless of whether it’s serverless or not.
  • Implementing payment processing as an independent serverless service is important, because you want it to be stable and independent from the other services within your application.
  • Integrating Stripe with your payment service on AWS Lambda is easy.
  • A nicely designed and independent payment service can later be reused by your other products or services.
  • Having no control over your infrastructure is not an excuse for having less security.
  • A good indicator of whether your payment service is safe is whether your serverless provider is PCI DSS-compliant.
  • PCI compliance is necessary when using a serverless provider, because it provides the level of safety and security you need.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset