Get Paid IRL (Part 3) Auth and capture card payments
Tags: tutorial, payments, stripe, webdev
Published: 2022-05-27 18:38:36

*Did you know that you can accept in-person payments with Stripe? In this series, we’re going to dive headfirst into building an in-person payments integration using [Stripe Terminal](https://stripe.com/docs/terminal).*

*In our [last post](https://dev.to/stripe/get-paid-irl-how-to-accept-money-with-stripe-terminal-part-24-f4a), we started building a web-based point-of-sale application on [Replit](https://replit.com/@charlesw-dev/terminal-starter#.replit). As an initial step, we created a backend route for retrieving Stripe Terminal card readers. We finished up by using our newly created backend route to list our online readers in our reader dropdown, connecting our app to our active card readers.*

*In this blog post, we’re going to learn how to use the [Payment Intents](https://stripe.com/docs/payments/payment-intents) and [Terminal APIs](https://stripe.com/docs/terminal) to create and handoff payments to our card readers, so that we can process them. We’ll also learn how to simulate tapping a credit card on a simulated reader.* 

*That’s right, by the end of the blog post, you’ll be able to process and complete in-person payments with Stripe Terminal!* 

*New to this series?  Be sure to check out [our first post](https://dev.to/stripe/get-paid-irl-how-to-accept-money-with-stripe-terminal-part-14-1ljd) on how to set up a card reader for testing and development and [our second post](https://dev.to/stripe/get-paid-irl-how-to-accept-money-with-stripe-terminal-part-24-f4a) on connecting them to a web application.* 

# Processing payments with your point-of-sale app

If you’ve been following along with the series, you‘ve registered [real or simulated card readers](https://dev.to/stripe/get-paid-irl-how-to-accept-money-with-stripe-terminal-part-14-1ljd) and [started a web application that lists which readers](https://dev.to/stripe/get-paid-irl-how-to-accept-money-with-stripe-terminal-part-24-f4a) are online. Now, we’ll finally give our app the ability to process payments. 

A successful Stripe Terminal payment has three steps:

**First, you set the reader to payment acceptance mode**. In this step, the cashier enters an amount and pushes a button so that the reader transitions to the payment acceptance screen.

**Next, you authorize the payment.** At this stage, the cardholder taps, dips, or swipes their card device while the card reader is in payment acceptance mode. The card reader then securely forwards the card details to the card network for temporary authorization. 

**Finally, you confirm the payment.** The cashier finalizes the transaction by manually confirming (or capturing) the charge. At this step, the charge has actually been finalized.

The additional step of confirming the transaction manually after it has been authorized may seem redundant, but it helps reduce fraud or unintended transactions. It’s also why you may see cashiers pressing a button on their point-of-sale console even after you’ve tapped your credit card.

## Setting the reader to payment acceptance mode

At this point, our app has a form for submitting a reader ID and amount, so let's create an API route for processing a payment using that information. This route will ensure that when a cashier helps a customer checkout at the counter, the right amount will appear on the right card reader so that they can complete their payment.

![Horizontal rule](https://res.cloudinary.com/practicaldev/image/fetch/s--XRaNCJc4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kwi5h27a7elyignm8iie.png)

💡**If you need a starting point** for your app, feel free to start off from [this repl](https://replit.com/@charlesw-dev/terminal-process-payments). It has all the code from the previous post. 

**If you want to see the end result**, check out [this repl](https://replit.com/@charlesw-dev/terminal-complete). 
![Horizontal rule](https://res.cloudinary.com/practicaldev/image/fetch/s--XRaNCJc4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kwi5h27a7elyignm8iie.png)

**On the backend**, we’ll create a `POST`  `/process-payment` route that will expect a request body with an [`amount`](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-amount), representing the price in cents, and a [`reader_id`](https://stripe.com/docs/api/terminal/readers/object#terminal_reader_object-id), representing the reader’s unique identifier. We’ll tell Stripe to create a payment by passing the `amount` to [`stripe.paymentIntents.create()`](https://stripe.com/docs/api/payment_intents/create?lang=node) along with the required [`currency`](https://stripe.com/docs/api/payment_intents/create?lang=node#create_payment_intent-currency), [`capture_method`](https://stripe.com/docs/api/payment_intents/create?lang=node#create_payment_intent-capture_method), and [`payment_method_type`](https://stripe.com/docs/api/payment_intents/create?lang=node#create_payment_intent-payment_method_types) parameters. This will create a [Payment Intent](https://stripe.com/docs/payments/payment-intents), a special object that Stripe uses for managing payment states. We’ll destructure and alias the Payment Intent’s `id` as `paymentIntentId`.  

We can tell Stripe to prompt a specific reader to payment acceptance mode for our payment by calling [`stripe.terminal.readers.process_payment()`](https://stripe.com/docs/api/terminal/readers/process_payment_intent?lang=node) with the card reader’s ID (`readerId`) and the payment’s ID (`paymentIntentId`) as arguments. 

{% embed https://gist.github.com/charlesw-stripe/4fa8012ba9c1d70941e1a33dc77f3e4b %}

Now, when `/process-payment` is called with a valid `amount` and `readerId`, Stripe will create a payment of the specified amount and forward it to the specified card reader. Now we just need to update our frontend, so that we can send it a `readerId` and `amount` when we submit our form to `/process-payment`.

**On the frontend**, we’ll create a submit event listener that passes our amount and reader ID to `/process-payment`. If we receive an error, we’ll add it to our `#messages` `div` just below our form and exit the function. Otherwise, we’ll add a message to `#messages` indicating that we’ve successfully created our payment for our reader and redirect to the `/readers.html` page, a page for controlling the reader after it’s prompted.  

{% embed https://gist.github.com/charlesw-stripe/9a468d8648a4fbccc578851bcddc2224 %} 

Now when we submit our form, we’ll create a payment and send it to our reader. Once that's done, we'll transition the web app to the `/readers` page. 

If you look for your payment in the Stripe dashboard, you’ll notice that it has created a new payment. We’ve successfully passed a payment to our Stripe Terminal reader. Huzzah! 🥳

![Now submitting the form on the home page of point-of-sales app creates a payment and hands it off to the card reader. We also can view the incomplete payment in our Stripe dashboard.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdepft788cn14yl37qo5.gif)

Now we just need to test a cardholder actually tapping or dipping their card against our physical or simulated reader.

---
## Authorizing a payment in test mode

**If you have a [physical BBPOS WisePOS E](https://stripe.com/docs/terminal/payments/setup-reader/bbpos-wisepos-e)** card reader, testing a payment attempt is easy because the reader will actually transition to the payment acceptance screen. Tap your test card against the reader and it’ll pretend to authorize the payment.  


![Physical WisePOS E device transitions to payment acceptance once you confirm a payment; tap your test card to authorize the payment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ve6tmyik320ui6eu139.jpg)

**If you’re using a simulated reader**, you’ll need to use the [Terminal Reader test helper](https://stripe.com/docs/api/terminal/readers/present_payment_method?lang=node) to simulate a cardholder dipping or tapping their card against the simulated reader. This is helpful for development without a reader, but it’s also a good tool for integration tests. You should probably learn to use it even if you have a physical WisePOS E reader on hand.

Let's build a route for authorizing simulated payments on simulated readers. **On the backend,** in `/server/server.js`, add another `POST` API route. Here we’ll destructure `readerId`  from the request body and pass it as an argument [`stripe.testHelpers.terminal.readers.presentPaymentMethod()`](https://stripe.com/docs/api/terminal/readers/present_payment_method?lang=node). This will tell Stripe to simulate a cardholder tapping or inserting their card on the reader.

{% embed https://gist.github.com/charlesw-stripe/495fcf4ffc15a63ce92d94fea888f7fa %} 

**On the frontend**, in `/client/reader.js`, we’ll add another event listener for `DOMContentLoaded` and get the `reader_id` and `payment_intent_id` parameters from the URL and assign them to `readerId` and `paymentIntentId`, respectively. We’ll need the `readerId` for reader actions like simulating the payment. The `paymentIntentId` will come in handy when it’s time to capture the payment in the next section. 

Add a `click` event listener to our **Simulate Payment** button. Make a `POST` request to `/simulate-payment` with the `readerId`.  As before, add a message if there are any errors. Otherwise, we’ll add a message to our `#messages` `div` to show that the simulated payment was successful. 

{% embed https://gist.github.com/charlesw-stripe/91aa6089c5bdacadd417d5bb2e832d57 %}

Go try out your **Simulate Payment** button in your app. 

![Clicking the simulate button now simulates a cardholder tapping their card on the simulated reader. The associated payment transitions to authorized but uncaptured in the Stripe dashboard.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1361s86pstzgep7l9r53.gif)

If you click on the link that’s generated by the `addMessage` helper, it’ll take you to the payment in the Stripe Dashboard. You’ll see that the payment has a card attached to it and is uncaptured, which means that the transaction has been authorized but not finalized. 

Congratulations: you’ve successfully simulated your first test Terminal authorization using the simulated card reader!🎉 

Now we just need to add our own capture functionality to our point-of-sale app.🤔

---
## Capturing authorized payments 

[Stripe Terminal authorizations only last 48 hours](https://stripe.com/docs/payments/capture-later). After that, the authorization drops off and is released back to the card’s balance.  **Remember: if you don’t capture your payments, you won’t get paid!**

Let’s create one last API endpoint for telling Stripe to finalize the payment. **On the backend**, in `/server/server.js`, we’ll create a new `POST` route, `/capture-payment`. This route will expect a request body with a `paymentIntentId`. We’ll capture the payment by calling [`stripe.paymentIntents.capture()`](https://stripe.com/docs/api/payment_intents/capture?lang=node) passing in the `paymentIntentId` as the sole argument. 

{% embed https://gist.github.com/charlesw-stripe/3bee6d34a3888bae80a28b370b118040 %}

On the frontend, in `client/reader.js`, we’ll add a `click` event listener for our **Capture** button. It’ll use the `payment_intent_id` from the URL parameters. If the attempt succeeds, we’ll forward the user to `/success.html`, passing along the Payment Intent ID, so that we can render the payment details.

{% embed https://gist.github.com/charlesw-stripe/aa629b78575fd612530442103998f6e5 %}

Now we’re able to finalize our payment by clicking the **Capture** button after once a payment has been authorized. Remember how we mentioned that sometimes cashiers need to press a button to complete the transaction? In the context of our point-of-sale app, that’s the **Capture** button.

![Creating a payment on a simulated reader, simulating a payment with the Simulate Payment button, and immediately capturing the payment finalizes the payment. We the same in the Stripe Dashboard.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a5ows1hwm7k0mnp9ncy.gif)

We’re officially able to accept and finalize in-person payments with Stripe Terminal.🚀

---
## Next up: Canceling in-flight in-person payments 

Creating and completing payments with a Stripe Terminal reader is all well and good, but occasionally a customer will change their order mid-payment. In our final post, we’ll learn how to cancel in-flight payments. 


## Stay connected

Want to stay up to date on Stripe’s latest integrations, features, and open-source projects?
  
📣 Follow us on [Twitter](https://twitter.com/StripeDev)
💬 Join the official [Discord server](https://stripe.com/go/developer-chat)
📺 Subscribe to our [Youtube channel](https://www.youtube.com/StripeDevelopers)
📧 Sign up for the [Dev Digest](https://go.stripe.global/dev-digest)


## About the author

![The author, Charles Watkins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqzlent7mkotc9hnl8k7.png)

[Charles Watkins](https://charleswatkins.dev/) is a Developer Advocate at Stripe where he writes, codes, and livestreams about online payments. In his spare time, he enjoys drawing, gaming, and rewatching the first five seasons of Game of Thrones.