Stripe Checkout Tutorial

The easiest way to integrate with Stripe is to use their hosted Checkout. It’s SCA compliant and regularly update with new features. It supports Apple and Google pay for compatible browsers.

1. Create Checkout Session

For one time payments we need to create a checkout session. To do this create a server action folder called stripe and then a new action file within called checkout.

This will assume that you already have a way setup to insert details of the order to the database. (For example lines of an order)

  1. Database Connection
  2. Database Query to find the total price
  3. API Action
  4. Database Update

Database Query

The database query will depend on your setup but in my case I have another server action that inserts products from a shopping cart in data store to the database, and inserts a new record in the table order that contains the user details.

A query is required to find out how much you would like to charge. This wouldn’t be required if you simply only have a single product or service to charge for.

API action

Create an API action after the database query (if it was required) and set the id as stripe.

Turn on output and pass errors.

Set the URL as: https://api.stripe.com/v1/checkout/sessions

Change the method to POST and the Data Type to Form.

Under Input Data we need to create a few fields:

Name Value
success_url https://mydomain.com/success
cancel_url https://mydomain.com/cancel
mode payment
payment_method_types[0] card
customer_email ***Note: Not required but pre-fills Stripe checkout
line_items[0][currency] usd
line_items[0][amount] {{totalPrice[0].TotalPrice * 100}} - Change for your query or price
line_items[0][name] Give this a description your user will recognise
line_items[0][quantity] 1

Lastly we need to set two Headers:

Name Value
Authorization Bearer sk_test_xxxxYour_Stripe_Key_xxxxx
Idempotency-Key {{NOW_UTC.sha1("16dUk6RsXSat18")}}

Copy and paste your Stripe key into the Authorization value field, be sure to prefix it with the word Bearer like in the example above.

We set an Idempotency Key to safely retry requests without accidentally performing the same operation twice, for example if there is a network error on the client and the request fails, Stripe will retry the same request without making multiple charges. I just set this to NOW_UTC and generate a random SHA1 hash.


Now this is setup, we need to click on Define API Structure.

In the popup window you’ll need to press ‘Fetch Schema’ and once this is complete you will see the API schema in the right-hand panel.

Press Save and you’ll now be able to access the Stripe api schema throughout Wappler data bindings.


Database Update

The last step of this action file is to save the Payment Intent. In my case I will save this to my customer_order table in the database. The reason we need to save the PaymentIntent is because we will need to verify it later in our webhook.

If the Fetch Schema works correctly in the previous step then should be able to access the PaymentIntent under the dynamic data picker as:

{{stripe.data.payment_intent}}

2. Redirect to Stripe

On our page we need to include Stripe.JS in our header. Copy this script tag and include it before the closing </head> tag.

<script src="https://js.stripe.com/v3/"></script>

We must also create a “checkout or pay” button on our page for the user to click.

First create a ServerConnect form (I’ll call this cartForm) that POST’s data to our server connect action file that we just created.

<form id="cartForm" method="post" is="dmx-serverconnect-form" action="dmxConnect/api/stripe/checkout.php" dmx-on:success="run({runJS:{function:'runStripe'}})">

As you can see from the snippet, we will also set an onSuccess dynamic event to run a flow. Inside the flow we select Run Javascript and we call the function runStripe.

You can now place a button inside of the form and make sure to set its Type as Submit.


Finally, we need to place some javascript just before the closing </body> tag on our page.

  <script>
		var stripe = Stripe('pk_test_xxxxYour_Stripe_Key_xxxxx');
		function runStripe() {
			stripe.redirectToCheckout({
  			  sessionId: dmx.parse('cartForm.data.stripe.data.id')
		  });
		}
  </script>

This is the javascript function that is run from our Flow.

Replace the Stripe key with your own and you may need to change cartForm to the ID of your form.

sessionId: dmx.parse('cartForm.data.stripe.data.id')

With:

sessionId: dmx.parse('yourFormID.data.stripe.data.id')


That’s it. You should now have a working Stripe Checkout integration that takes your user to https://checkout.stripe.com on button click.

But we’re not quite done.

Unless you want to manually poll the Stripe dashboard to verify someone has paid you, we will setup an automatic webkook server action to update our database upon successful payment.



3. Create an Endpoint

  1. Create a new server action within the stripe folder and call it endpoint. It should then be accessible at:
dmxConnect/api/stripe/endpoint.php

  1. Next we need to create a few $_POST variables under Globals.
Variable: id
Variable: created
Variable: type

Object: data
    Object: object
            Variable: payment_intent

Mine looks like this:

You can add more variables from the Stripe API scheme if you need them. For example I’ve also added the customer_email field too.


  1. Add a Database connection followed by a Database Query.

In my case the query will be the customer_order table and I will set a condition that matches the payment intent stored agains the payment intent provided by the Stripe webhook.


  1. In my case I now insert a Database Update step to update the customer_order as paid.

  2. And finally I insert a Response and set it to 200. This is to alert Stripe that we have received and accepted the webhook.

Note - It has been reported that by adding a 200 Response may cause an error on Stripe’s end. You may not need to add this step.


4. Tell Stripe about our Endpoint

We now need to enter the URL of our endpoint within the Stripe dashboard.

  1. Go to Stripe Dashboard > Developers > Webhooks

  2. Click the Add Endpoint button under the section ‘Endpoints receiving events from your account’

  3. Set the endpoint URL as your server action location, e.g.

https://mydomain.com/dmxConnect/api/stripe/endpoint.php
  1. Under the Events to send dropdown, select
checkout.session.completed
  1. Click Add Endpoint and you’re done.

Stripe will now send the checkout.session.completed webhook to your server connect endpoint action we just created. You can also use the Stripe dashboard to access the webhook logs.



Advanced:
You could go one step further by adding a condition at the beginning of your endpoint server action to check that

$_POST.type == checkout.session.completed

And if that condition fails you could add a 500 Response.

You could also take it one step further and check that the webhook originated from Stripe by verifying the webhook signature.

15 Likes

Thanks Max, keep em coming, your’e on a roll.

2 Likes

So I read through your post and have an issue:

I built the datastore cart and created a checkout page that has a table populated from the data store with a button called “pay for items”. I would like this button to tie back to your tutorial to use the checkout system from stripe.

I can’t seem to figure out how to create a sever action to take that data from the Data Store: cart back to the server? I can’t find a place to enter another “server action”? That

1 Like

So I have a checkout page that has a Repeat on a row, and inside of the row are columns that contains the product details, price and delete button.

On each repeated row there are two hidden form fields, one for the product ID and another for the quantity.

When a user clicks the checkout button I’ve added a database insert for a new order (that creates the order ID) and then a repeat to my Stripe server action with a database query for stock level and price, and then finally a database insert to a table called order_lines which contains the product ID, quantity and order_id.

The server action then proceeds to the Stripe steps listed in this tutorial.

1 Like

Great tutorial @max_gb, thanks for the effort it took to explain this so extensively! :+1:

2 Likes

Max I am having some issues with stripe checkout and I think it has to do with my lack of understanding with the pats of server side process in the tutorial. Do you have any suggestions for me to look at or study to better understand? Also, it might help if I could see some screen shots of your server actions that you use in the tutorial.

Thank you

Hi Daniel, which part are you stuck on?

So I think it is the API connection but it could also be the variables, as I am not sure how to handle the array data, lastly,

it could be the way I am sending my price. I am using the price ID. Here are some shots of how I have things set op.

Thank you!

Shot3_SA_database_update|690x386 Uploading: shot2_SA_API.jpg…

This is my checkout server action:

My repeat has two steps, the query is to obtain the item price from the database and the insert step is to insert that record in to a table called order_lines. It contains details such as the order it relates to, product ID, quantity and price.

After the repeat there is another query (totalPrice) which SUMs the order_lines. This is then used to pass the price to Stripe in the API action step. In my case I needed to multiply this by 100.

Have you checked to see if your data is inserting to the database before the Stripe API connection?

1 Like

No, that’s not working either. The question, for your initial, insert how are you inserting the array data without a multi-insert? Or are you sending the data from another server action?

The shopping cart data is sent through a repeat all within the same server action as the Stripe API action:

<form id="cartForm" method="post" is="dmx-serverconnect-form" action="dmxConnect/api/stripe/checkout.php" dmx-on:success="run({runJS:{function:'runStripe'}})">
    <div class="row" is="dmx-repeat" id="rCart" dmx-bind:repeat="cart.data">

        <div class="col-4">
			<input id="productID" name="productID" type="hidden" class="form-control" dmx-bind:value="product_id" dmx-bind:name="record[{{$index}}][productID]">
		</div>

        <div class="col-4">
			<input id="lineQty" name="lineQty" type="hidden" class="form-control" dmx-bind:value="qty" dmx-bind:name="record[{{$index}}][lineQty]">
     	</div>

        <div class="col-3">
			<input id="linePrice" name="linePrice" type="hidden" class="form-control" dmx-bind:value="(price.toNumber() * qty)" dmx-bind:name="record[{{$index}}][linePrice]">
        </div>

        <div class="col-1">
			<p dmx-on:click="cart.delete({$id: $id})"><i class="far fa-trash-alt"></i></p>
		</div>

    </div>
</form>
1 Like

whats your query look like to get the total price?

[edit] I see the aggregate now, I just set that to sum. Hadn’t seen that before.

1 Like

Yes I just set the Aggregate field to SUM.

1 Like

on the last section (webhook) how do you determine which order ID to update? Are you querying by email address? What if there are more than one order by same email? Is there a way to pass the order ID through the API? Or how did you do that?

Hi Baub,

I store the payment intent in the orders table which is generated on the initial API call. I haven’t explored but you could make use of Stripe’s metadata function if you need to store the Order ID.

I think I have it figured out. When I create the user it creates a user ID at stripe and stores it in the local storage. So I just add that to my order table. And then when I get my webhook back from Stripe I just filter by the customer ID as it sends that back to me with the webhook data.

So you’ll see in my initial checkout server action screenshot that after the stripe API call, I have one last step that inserts the payment intent created by that API action in to the order table.

image

On the webhook server action my first step queries the orders table and has a condition set where

order_table.paymentint == {{$_POST.data.object.payment_intent}}

That then identifies the order.

1 Like

Dang, I’m so close! I’ve got my payment_intent updating correctly to the order. But on my endpoint.asp action script it doesn’t update my order to paid. I know the payment_intent on each is matching. So I’m guessing I have an issue with the way the $Post variables are being pulled in.

Am I missing something? Normally I know that on the globals you have to choose the form but in this case there isn’t a form? Any idea here? the insert1 step is a test step to copy the value of what the payment_intent is so that I can see the value of the variable. Which is nothing at this point.

Your globals are fine. You need to enter a step to query the order ID first, then you can run the database update step.

The database update step then uses a condition against the order ID rather than payment intent,

order_id == {{order_query[0].id}}

1 Like

This is an excellent tutorial, @max_gb. Huge thanks for putting it together. I had already integrated Stripe on a couple of projects but using a different method. I prefer their Checkout as it makes it really clear to the customer that it’s safe and shows exactly who is processing the payment.

Can I just point out a couple of things I found when implementing your instructions?

In the API Input Data, you show line_items[0][currency] twice. My initial attempt to get the API Schema gave an error because [quantity] wasn’t included so I removed the duplicate value and added line_items[0][quantity] and that worked.

Right at the end of section 3, the webhook, you added a Response 200. Bizarrely, this gave Stripe a 500 response so it set the call to be re-attempted in an hour. I went through everything and it was all fine so I removed that response action and Stripe then showed ‘Successful’.

Other than that, perfect instructions in a clear and easy to follow way which has helped me hugely so thank you once again.

2 Likes