Here, at Maxcode, we believe that knowledge sharing should be free, with nothing expected in return. In other words, we pay it forward. And when it comes to payments, you could say that we know a thing or two about them. Or more. Over the years, we have refined this knowledge to the point of making it accessible for everyone who might be interested in this subject, even if that person is not an expert… yet.
The goal with this article is to walk you through the usual preparation steps and activities before integrating a payment service provider with your web application. You’ll learn how the information flows, what you need to consider and a self-assessment checklist before writing production-ready code. So let’s get started.
First, let’s meet the actors
Not actual actors, as in movie stars, even though for us they are quite like celebrities. No, these guys are payment actors.
The most important one is the user of the application, also known as the customer. He is willing to transfer money in exchange for a service or a product. Our goal is to have a happy customer, by enabling him with a quick and secure payment process. And you need to take into account that a customer can also change his mind in the middle of a transaction, which is completely normal and the user’s right to do so. From a technical standpoint, the user is represented by the browser.
Next comes the merchant, who is responsible for managing the payment flow from the initiation to the final confirmation of the transaction. In our technical context, he is represented by the web application in which we, as web developers, put our blood, sweat and tears into It offers the user the possibility to pay for his goods with the help of the last actor which is…
…the payment service provider. His friends call him PSP. No one knows how he does it (well, we do), but everyone trusts that he can securely transfer money from the customer to the merchant. On the technical end, it provides a user interface or a mobile app for the customer, whereas for the merchant it provides an API and a backend administration portal.
Let’s check our actors’ plot from a high distance. The customer has decided that he wants a specific last-gen device and he is willing to pay using it. He is already on the merchant web application. (1) The customer presses the Pay button. Things start to happen behind the scenes at a very fast pace, although for the user it looks instant. (2) During this flash moment, the merchant quickly initiates a payment at the PSP, specifying the amount and the order ID, (3) the PSP replies to the merchant with an acknowledgement and a payment URL to redirect the customer, and (4) the merchant redirects the customer to the payment page provided by the PSP. (5)Then, the user lands to the payment page and completes the transaction. The next two steps (6) happen asynchronously: (6’) the customer is redirected back to the merchant and (6’’) the merchant receives the status of the payment. (7) In the end, the merchant’s web app turns green meaning that the transaction completed successfully and that the user’s most wanted product) will start being shipped.
Now let’s dive into each of these steps:
➡️ Step 1: Press “Pay”
This is the trigger of the whole process. It means that the customer has decided to transfer money to the merchant’s account. Here are the things to be considered.
First, the page should display at least the amount, the identifier of the purchase and preferably the product description. You’d want the customer to have the context in every page from the following journey. Also, make sure that the “Pay” button is disabled until the initiation result comes back from the PSP.
After the request lands in the backend, you must handle the following two cases:
- None or Failed Previous Attempts
You can safely go further and register an internal transaction entity, referencing the purchase identifier.
- Pending Previous Attempt
It’s Schrödinger’s cat, but with payments. You don’t know the state of the previous attempt and you can’t risk duplicate payments; so I suggest stopping the flow, retrieve a suggestive message to the customer and check the status later. As you’ll see further, an attempt can’t live forever and needs to have an expiration date.
Here’s a quick checkbox for you:
☐ Display amount, currency, purchase identifier and description
☐ Disable pay button after being pressed
☐ Handle scenarios for previous payment attempts
➡️ Step 2: Initiate the payment
Now it’s time to bring the PSP into the scheme. This means that you register an attempt at the PSP, providing him with at least the following information: amount, currency and the purchase identifier. Depending on your business needs, you could send other useful information during the payment process, such as the language of the customer, preferred payment method, or a return URL.
Security wise, most of the time you need the following for the HTTPS request: API URL, a merchant identifier and a secret. Other service implementations require an extra call in which a token is obtained. In any case, this should be something straightforward and well-documented on the PSP side.
Another general aspect is handling the expiration of the attempt. Some PSPs require to be set during initiation, others allow you to set it in their portal. The value should be high enough to allow the customer to finalize the payment process and low enough so that pending attempts don’t become an issue.
I recommend a value between 10 minutes and half an hour. Remember our previous section? Once this is set on your internal transaction, you need to create a job which monitors and marks the attempt as expired, otherwise the customer can’t perform other attempts.
Here’s another quick checkbox for you:
☐ Include basic information about the payment in the request
☐ Send request in a secure way using SSL and secrets
☐ Set payment timeout and add an internal job which handles expiration
➡️ Step 3: Handle initiation response
If the payment initiation got accepted by the PSP, look for the following in the reply: payment URL and PSP identifier. I’ll talk in the next section about the former. As for the latter, you need to persist it in your internal transaction for mapping the information the PSP will send you in the following phases of the process.
Here is also the place to add exception handling. If errors occur, then you should terminate the current flow and display a message to the customer. One example is a temporary unavailability of the PSP, which cannot handle transactions.
In a nutshell:
☐ Identify PSP internal transaction and persist it in the database
☐ Handle errors received from the PSP
➡️ Step 4: Redirect the customer to the payment page
Now it’s time to send the customer to the payment page. As a merchant, you forward the payment URL to the browser via the 302 Redirect HTTP status code.
☐ Redirect user to the payment
➡️ Step 5: Payment page
This is the realm of the payment itself. It’s the responsibility of the PSP to provide a secure method to achieve this. In the end, that’s why you are using a provider to handle this.
For simplification purposes, you can assume that the PSP has the payment method built in-house, and both customer and merchant have their accounts on it. The reality is different because the provider usually offers multiple external payment options, such as PayPal, iDEAL or credit cards. The merchant’s responsibility is to configure their account in the portal of the provider. As a developer, you don’t have to deal with this unless explicitly requested by your client.
For the development stage, the payment provider offers a simulated payment page that handles virtual transactions in test mode. You can access it usually by setting a different API URL or set a test flag during initiation.
The simulated payment application should support triggering two major cases:
- Success, payment was confirmed.
- Fail/Cancelled, it can be caused by the insufficient funds, a cancellation by the customer or any other error.
So don’t forget to:
☐ Find how to set payments in test mode
☐ Use simulated payments application to set up scenarios such as confirmation, cancellation or failures
➡️ Step 6’: Back to the merchant
Once the payment is completed, the PSP redirects the customer back to the merchant using a return URL that includes the identifier of the transaction. I’ve seen two types of implementation in my experience working on payment applications. Some PSPs allow you to set the return URL at initiation, others will allow the merchant to set one in the portal and will append their internal transaction identifier. The point is that the implementation must be built accordingly in order to map the return URL to the internal transaction.
So make sure you:
☐ Design how to set and process the return URL
➡️ Step 6’’: Merchant receives the status
Handling the status of the transaction is arguably the area a developer should be the most careful about. That’s because it is at this point where we decide if a payment was confirmed and if the purchase can be shipped.
It involves exposing an endpoint to the PSP, in which webhooks get processed. Make sure to set URL and secrets in the payments portal settings. A security layer is mandatory because you want only the PSP to be able to send you a payment confirmation. I have encountered multiple options, depending on the service preference. Most would send you a secret in the POST request header. Others would send a signature. There’s not yet a standard between the providers.
At this point, the status has arrived, we know that it’s from our trusted third-party, so now we can process the status. Remember we persisted the PSP internal transaction identifier during initiation? Now it’s time to use it in order to map the webhook to our transaction entity. Next, update the payment status accordingly based on the information we received. There are two major cases, success and failure, which have multiple insights you may want to store for extra analysis. I don’t recommend storing more details than necessary, such as customer personal information because you have to manage them carefully.
Now you can trigger other actions: mark the purchase as completed, start shipping the product, send confirmation emails, etc. Design wise and don’t do them in the same process; instead, you can post an event in the system so that other jobs pick up these responsibilities.
Last but not the least, you need to take into consideration handling your maintenance period or merchant app unavailability. Losing payment confirmation events is not something you want to end up with. The PSPs support resending webhooks, provided you haven’t replied with a 200 Success HTTP status code. (the verb in this sentence?) If you do this right, the webhooks will be retried at an exponential time so that your application can process them again when it gets back online.
Step by step, check if you:
☐ Expose and set processing endpoint in the payments portal
☐ Validate that the webhook is from the trusted party
☐ Map webhook to the internal transaction
☐ Send payment confirmation event in your system
☐ Handle merchant web app unavailability based on webhook retry mechanism
➡️ Step 7: The return of the payment
It’s the last step of our journey. The customer returned to his product page and can see all the details of his payment. If everything went well, he should see a green status with the purchase ID, transaction ID, payment date, amount and any other relevant information. At this point, the payment flow is completed.
A smart developer handles all the possible scenarios, so that’s what we’re going to do. One situation may be that no webhook or confirmation has been received from the PSP. In this case, we invite the user to check the status later. In case of a failed transaction, such as insufficient funds, we invite him to make another attempt as soon as he is ready.
Before you’re done, make sure to:
☐ Display the status of the transaction and the relevant payment information
☐ Handle non-success statuses and invite the customer to try again
The knowledge I shared with you was built during multiple PSP integrations that served thousands of users and millions of transactions. However, I do need to remind you that this article should be considered a generic reference. Your implementation might deviate from it or include additional steps and considerations. However, from my experience, I learned that payment processes evolve, making the entire experience even easier.
Understanding how payments work is key. Add my insights, pay attention to them and build your own payments integration, the proper way. Good luck!
About Ciprian Moroșanu
A graduate of Computer Science BA and Software Engineering MA, Ciprian has over 9 years of experience in developing .NET applications. During this period, he has acquired extensive business knowledge in the domains of payments, healthcare and insurance, and has also taken the role of Scrum Master in his team for the past 5 years, making sure that Scrum is enacted in the team so that they deliver the best results with the available resources.
9 November 2023
Navigating the Risks and Impact of Web Security in the Fintech Sector
A Comprehensive Developer Guide to Web Security Challenges
Navigating the Complex World of Web Vulnerabilities