You can 🎥 watch a video to see how this server was implemented and read the transcripts.
CJ Avilla (00:00): In this episode, we cover the basic server side implementation for accepting a one-time payment with a custom form. If you're interested in a faster integration path using Stripe hosted checkout, head over to the checkout playlist in the Stripe developers channel. The payment flow you'll see today for collecting a one-time payment has two steps: first creating the payment intent on the server, and second confirming the payment on the client, using the client secret for the payment intent. In this episode, you'll learn how to add an endpoint to your server, to create the payment intent.
CJ Avilla (00:33): Then depending on whether you're using vanilla JS, React on the web, or Stripe iOS or Stripe Android on mobile, you can watch videos for those specific front-end implementations that will pair with the code we implement here. So rather Than start from scratch, we're going to jumpstart our server implementation using the developer office hours base sample. If you want to see how we go from zero to one, check out the starter episode, linked in the description. You might also be interested in an episode about working with the Stripe CLI it's a really handy tool for helping you build and test your Stripe integrations. From the terminal here, we're going to run Stripe samples create and we'll pass it developer office hours. And for this one, we'll give it the alias of tutorial. We can now pick Ruby and that'll download and scaffold a bare bones project with a simple server and client for us.
CJ Avilla (01:25): So in this episode, we'll spend all of our time in the server directory and in future episodes, we'll implement those front ends to confirm the payment. So let's change into the server directory install dependencies with bundle install, then open server dot R-B. Server dot R-B already contains a root route for rendering a basic HTML page and a skeleton for the web hook endpoint.
CJ Avilla (01:46): At a minimum, we need one new server end point to create the payment intent that we'll later confirm on the front end. This route will accept post requests, as by convention we're creating a new resource, a payment intent. So we'll stick with the same naming convention as the Stripe docs and name our route create payment intent. We'll set the content type of the response to application JSON and get to work creating the payment intent. The logic in this route is very simple, we'll create a payment intent using Stripe Ruby client library to make an API request to Stripe. Initially we'll hard code and pass just minimum required arguments. So amount and currency.
CJ Avilla (02:22): So we'll pass $19.99 and the currency as US dollars. Note, this amount value is denoted in the smallest denomination for a given currency. In this case, cents. Finally, we need to return some JSON with only the client secret property of this newly created payment intent. Let's jump into another terminal instance so we can start the server with Ruby server and experiment with the end point using curl to make a direct request to the payment intent endpoint. This will be a post request to local host 4242 with some application JSON and the request body. For now we're just going to pass just this empty JSON object. Notice the response is well formed JSON, and includes the client secret for the newly created payment intent.
CJ Avilla (03:05): Returning to our code. Let's talk about the API call for creating a payment intent. There's more than two dozen optional parameters for tailoring the payment experience for your customers. One of those optional parameters is payment method types, which takes a list of values for the payment method options you'd like to allow your customers to pay with. So by default, this is set to an array with one element, a string with the word card, making this payment intent only confirmable with a card payment method. In practice, you could hard-code a list of string values for all the different payment method types you want to accept here. In this series, you'll learn how to you accept a wide variety of payment method types.
CJ Avilla (03:44): So let's refactor our end point to accept some arguments by de-serializing, the JSON and the request body. We'll extract an argument for the payment method type. Furthermore, some payment method types only work with specific currencies. So let's allow the currency to be passed from the client as well. Okay, let's head back over to the terminal, restart our server and update our curl request to pass in the payment method type and the currency in the request body.
CJ Avilla (04:12): Great, that works. But if we try to pass A-U becs debit as our payment method type and US dollars as the currency notice this fails because A-U becs debit only works with Australian dollars. Also notice that the error was returned as a raw string and not well-formed JSON. If we update our curl request and pass AUD, the server responds with the client secret as expected, this is great. In the future we'll want to surface those failures in a nice format so that our front end can consistently parse and present errors to customers.
CJ Avilla (04:45): So let's go wrap that API call in a begin rescue block and render the error response with well-formed JSON in a failure case. So if the exception is a Stripe error we'll return a 400, and in any other case, we'll just return a 500. In both cases, following the structure where the response has an error property pointing at an object with the string message. This matches the shape returned directly from the Stripe API for client side calls. So it'll make implementing error handling logic on the clients a bit easier to reason about. After restarting our server and attempting to create a payment intent with the invalid combination of payment method type and currency. We now see well-formed JSON error response.
CJ Avilla (05:28): Now most payment methods types, complete asynchronously. For some payments completed nearly instantly like cards, but for others payments can take a few days or even more to complete, due to the asynchronous nature of the way that money flows through networks. We highly recommend implementing web hooks to automate fulfillment. We have an episode all about how to set up your webhook handler. So let's update our web hook end points skeleton to simply print to the server log. When payment events fire.
CJ Avilla (05:58): It's quite common to automate things like email notifications, updating your database, pulling from inventory, printing shipping labels, and more as part of your web hook handler. Also worth noting, you could use a third party tool like Zapier, or IFTTT as a low or no code solution for handling webhook notifications. For now, we'll simply check to see if the event type is one of payment intent dot created, and print a simple status update. From the terminal, we'll restart our server and test our new web hook logic with the Stripe CLI using the listen command. Stripe listen forms a direct connection from Stripe to this locally running server so that when events happen on the demo Stripe account, they're delivered to the development server without needing any tunneling software like ngrok. The Stripe listen command accepts a URL to which events should be forwarded.
CJ Avilla (06:45): Now, if we successfully create a new payment intent with A-U becs debit and Australian dollars, we'll see in the server log that the payment intent was created. And we see in the logs for Stripe listen here that the payment intent created event fired. Let's improve our log statement here to include the IDs of the event and the payment intent and the status of the payment intent, so that when receiving event types that start with payment intent dot, an instance of the payment intent is available on event dot data dot objects, we'll pull that off of there. We'll restart the server and fire another test event to confirm that our improved log statement looks good. Now we see the ID of the event, the ID of the payment intent and the status of the payment intent. Note, this payment was not created with a payment method explicitly, so it's status is requires payment method. We'll assign a new payment method when confirming the payment intent on the front end, again, covered in a future episode.
CJ Avilla (07:39): So as we add support for more payment methods in the future, it'll be interesting to see these status messages in the server logs and align those with what the customer is seeing in the various states for payment intents. Note that depending on which payment method types you plan to support and which automations you're planning on building, you likely won't need to handle every single one of these event types. All right, so at this point, the server is technically ready to add a front end, however, we'll add one last simple config route for fetching the publishable key so that we don't need to hard-code that on the front end. This simple helper is purely for serving our publishable key when receiving a get request to slash config. It's a best practice when working with mobile clients so that if, for some reasoning you need to roll your API key, the publishable key is not hard-coded into these production mobile apps that are shipped to users, which would require all of those mobile users to install an updated version of the app to get the new key.
CJ Avilla (08:34): So, as a quick recap, we added a new end point to create payment intents, the API call to create a payment intent passes the amount, currency, and now a list of the types of payment methods we want to allow. We also added some logging to the web hook handler for debugging and looking to the future when implementing application logic to automate fulfillment.
CJ Avilla (08:54): Next, we recommend heading over to one of the playlists that most closely fits your front end implementation. You can use the links in the description or head over to the Strike developers channel and take a look at those playlists. Thanks for watching. And we'll see you in the next one.