Anatomy of the Stripe payments in Power Pages

Just a few weeks ago, as I write this, Microsoft announced online payments with Stripe in Power Pages in preview. As I wrote a few posts on payment integration in Power Apps (with Stripe and Braintree) and Power Portals, I couldn’t help but roll up my sleeves and look at what’s behind the scenes of this integration.

You can find the basics in the official documentation on this feature (just one page). Nick Doelman, in his Power Platform Boost podcast, has a helpful video on how to set it up and use this feature. So you’ve got all to start using it!

In this post, I will focus more on what’s in this integration, how things happen and what limitations you may face. I have no insights from the development team, and I am writing below only on what I can see in the platform and tools available to a developer.

Set up

As per the first step, install the package from the Power Pages Studio Set up section. You should see the “Installed” and “Manage” labels below. Make sure you restart the Power Pages website (see the documentation and Nick’s video).

Once restarted, you can set up the keys required for the Stripe integration, as the documentation here. You need to have a Stripe developer account and then you can get your “Publishable key” and “Secret key” from the developers sections there:

Once you set up and created the multistep form with a payment step, it will look like the one below. Awesome!

What’s inside?

By the way, did you like disassembling toys in childhood? I did too 😅

Let’s have a look at the environment and what has changed. I can see that 3 managed solutions have been installed:

Let’s look inside:

  • “Power Pages Stripe Integration V2”. This looks like a place where I would expect to find most of the stuff, except… there is nothing visible there! 🧐 I guess the actual feature was included in the Power Pages web app itself! As you will see later in the “Architecture” part, the server side is an essential element of a secure integration with a payment gateway. I wrote about Stripe in Power Portals a while ago and used Azure Function as the server code infrastructure. Nevertheless, there is not much we can do here, so let’s move on.
  • “Power Pages Payments V2”. Here, we can find more interesting items. This is where the “Payment” table is defined (pp_payment) and two choices: Payment Status and Provider. Makes sense and looks good!
  • “Power Pages Encrypted Settings V2”. Here, I can find the only table “Encrypted Settings”. This looks like where the stripe configuration keys are stored. I opened the table view and I could not believe my eyes. Despite the table name and the column name “Encrypted Value”, the actual Stripe secret key value is stored as is in plain text! 😱 Microsoft, this is not good enough and I hope it will be fixed soon before the general availability of the feature.

What’s happening

The Stripe integration and flow diagram for card payments is explained in the Stripe documentation. I’ve highlighted a few interesting places regarding actual implementation with Power Pages. By the way, the architecture of Power Pages is briefly described here. From an infrastructure point of view, this is just an Azure web app and is depicted as “Power Pages (web app/server) below.

  • A. This starts when the step containing the payment form is loaded as a part of the multi-step form. As you specified the payment amount field in the step setup, Power Pages creates “Payment Intent” that locks the values for the payment. It’s created using the publishable key from the client (browser) and the secret key on the server (Power Pages app).
  • A1. The Power Pages platform calls Stripe for us to create a payment intent. It takes the publishable key provided by the client (from the page) and the secret key from the configuration of the solution (see the “Power Pages Encrypted Settings V2” solution above).
  • B1. On successful Payment Intent creation (which basically means the keys provided are correct and the amount is valid), a record in the “Payment” tale is created with the status “Created”, and a payment intent object is returned.
  • C. User enters credit card details and submits the payment form, along with the “retunr_url” value, which is where Stripe redirects on successful payment.
  • C1. The card details are sent directly to Stripe, bypassing Power Pages infrastructure! This is how it should be from the security point of view. There is a caveat, though, for failed payments – see below in the post.
  • D1. With the successful payment, Stripe returns the confirmation and redirects the user to the “return_url”, which is the URL of the payment step of the multi-step form. As part of this action on the server, the Payment record is updated in the successful payment scenario (see below on failed payments).
  • D. The user is presented with a successful payment message on the step, the Stripe form is hidden and the user can now submit the multi-step form if the payment was the last step or proceed to the next step, e.g. a confirmation or receipt page.

Failed payments

The above steps differ slightly for a failed payment scenario. When a card is declined (see the test card numbers for Stripe), step D1 is not invoked and the error is returned immediately from the “confirm” step C1 and displayed on the form.

The problem is that the Payment record is not updated and remains in the “Created” state. There is no direct way to update the payment record in Dataverse indicating a failed attempt and the reason code. In this scenario, any front-end script calls to do it are not secure by design. And this is where webhooks (would) come in handy!

Webhooks

A webhook is a way to add custom logic on certain payment events, specifically when a payment intent is created or payment is processed or failed. This seems to be unfinished in Power Pages, as webhook behaviour is not clear, consider the below:

The first call A to /WebhookEventListener endpoint on Payment Intent creation is successful as long as you’ve made your website public. What’s unclear is what it does, as the Payment record was created without it (when my site was still private), and I see no additional behaviour change from this event.

The second call B, when the payment was successful, is more puzzling. As you can see, it’s a call to a cloud flow endpoint, but it fails because the workflow is not enabled (it’s in an “Off” state). I wanted to see if I could enable it and what would happen, but I could not find the workflow with this ID in the environment (even via the admin’s Power Shell scripts). 🫤 I suspect this may be a shared endpoint for a cloud flow or logic app for the whole Power Pages payments feature. Or is it just a bug, and they forgot to update the call to point to the /WebhookEventListener endpoint???

And lastly, I was hoping that the webhook would handle the failed payment scenario, but… it didn’t. The record created with a failed payment attempt remained in the “Created”, despite that there was a call from Stripe to Power pages webhook in this scenario and with the failed information reason.

Summary

The good

It’s great to see native support for payment integration in Power Pages, especially for a popular payment gateway like Stripe. There are a few things to improve before general availability, but it’s a great start for a preview. You can build and test the solution right now, with no coding needed to facilitate payments with Stripe and don’t need to set up an infrastructure to facilitate server-side processing.

The bad

Webhooks are either not yet implemented properly or unclear how to use them.

Handling failed payments is a problem – they remain as records in the “Created” state in the Payment table. Again, webhook would be a help here – everything is needed to update the payment record!

There is not enough payment gateway data in the Payment table – payment references and reason codes are not recorded.

The Stripe payment form is injected automatically in a step of a multistep form. There is no control over the placement of the form on the page. While it may work OK for most cases, I expect more options here.

The ugly

The plain text secret key is stored in a Dataverse table! I hope this is fixed soon, but again, while the feature is in preview, you should not (technically) use it in production. 🤭


Stay tuned and I’m looking forward to the updates on this topic!