Lecture
Integrating payment systems on web, mobile and desktop platforms requires accounting for a range of technical and business aspects: from the choice of API and payment methods to ensuring security and user convenience. In practice, the key differences relate to UX, security (PCI DSS, HTTPS, tokenization) and support for local payment methods.
Choosing a provider: fees (2.5–4%), card/wallet support, geography, API quality.
APIs and SDKs: Stripe, PayPal, Adyen, YooKassa and Robokassa provide ready-made libraries for popular languages.
Webhooks/IPN: asynchronous notifications about transaction status.
Security: HTTPS, card tokenization, verification of notification signatures, storing keys in secure vaults.
Testing: sandbox environments, verification of successful/failed payments and refunds.
UX: minimizing the number of steps to payment, support for «one-click payment».
Integration: RESTful API, redirect to a payment page or embedded forms.
Examples: Stripe Checkout, PayPal Smart Buttons.
Consideration: it is important to account for cross-browser compatibility and a responsive interface.
SDKs: ready-made libraries for iOS/Android (Stripe SDK, PayPal Mobile SDK).
UX: Apple Pay, Google Pay, biometric authentication.
Consideration: integration with system APIs for a seamless experience (Face ID, Touch ID).
Risks: the need for strict control over the storage of tokens and keys on the device.
Integration: via embedded browser components (WebView) or direct API calls.
Examples: enterprise ERP systems with a payment module.
Consideration: more common in the B2B segment, where reporting and transaction control matter.
Risks: SDK updates and OS compatibility.
| Platform | UX | Integration | Specifics |
|---|---|---|---|
| Web | One-click payment, redirect | REST API, webhooks | Cross-browser support |
| Mobile | Apple Pay, Google Pay, biometrics | SDKs for iOS/Android | Integration with system APIs |
| Desktop | Less critical | WebView, API | B2B orientation, reporting |
These problems need to be solved at the architecture level:
Cart Lockout
These directly describe the problem: the user has started checkout, but in the meantime the cart may change, or even two checkout processes may start simultaneously. The solution is a combination of cart versioning (optimistic locking) and moving the cart into the SUBMISSION_IN_PROGRESS state.
During checkout it would be dangerous if the user could make changes to their cart. They could change prices or other important information, which would let them complete checkout in a way that should not have been possible (for example, overpaying or underpaying, skipping a validation step, and so on). In addition, multiple checkout processes could be started simultaneously for a single cart, which would lead to a failure because they compete for the same resources, or potentially even to charging the user multiple times.
To prevent this, Broadleaf uses an implicit locking mechanism whereby users lose control over their cart during checkout. This is achieved through a combination of optimistic locking (see Cart Versioning) and a change of cart status (see Cart Statuses).
Thanks to optimistic locking, we know that the state of the cart in the database will never be overwritten by a stale version. This helps guarantee that if multiple or duplicate requests to modify the cart arrive, only one of them will succeed and the cart state will remain valid. The status change to SUBMISSION_IN_PROGRESS, which happens at the first stage of the checkout process, effectively takes control of the cart away from the user, and only the currently running checkout process should be able to make changes to it. If multiple checkout processes are started, only one of the write operations (and checkout processes) will succeed thanks to optimistic locking. The combination of optimistic locking and the change of ownership guarantees that:
Only one checkout process will run at a time for a single cart.
Users cannot change the contents of the cart during checkout.
Also useful:
Stripe — Dynamically update line items
Stripe writes separately about inventory checks and dynamically changing the composition of an order during a Checkout Session. This shows that the cart and the payment session are treated as different entities.
To understand the race problem itself:
Race Condition / TOCTOU
The classic description of the situation "checked the data → the data changed → used a stale result". This is exactly what can happen between creating a payment session and finalizing the order.
Regulatory requirements: PCI DSS, PSD2, GDPR.
Multi-currency support: support for local payment methods.
Security: tokenization, mutual API authentication.
Conversion optimization: fewer steps, saved cards, «one-click payment».
Redirect — the user is redirected to the provider's page (PayPal, YooKassa). Simple and secure, but the UX is worse.
Embedded forms — an iframe or JS widget on the site. Convenient, but requires protection against XSS.
API/SDK — direct interaction with the provider's server. Flexible, but with higher PCI DSS requirements.
Native methods — Apple Pay, Google Pay, Samsung Pay. The most convenient UX on mobile.
Payment initiation — the customer selects a payment method.
Transaction creation — the server generates an order and sends a request to the payment gateway.
Authentication — 3-D Secure, biometrics, tokenization.
Processing — the gateway checks the card, balance and limits.
Notification — the status (success, decline, refund) arrives via webhook/IPN.
Order update — the server changes the status and executes business logic (subscription activation, delivery).
False negatives — a genuine payment is declined. Causes: suspicious patterns, bank limits, validation errors.
False positives — a fraudulent payment is accepted. Causes: weak anti-fraud rules, insufficient data.
How to improve:
Use anti-fraud systems (Riskified, Sift).
Apply machine learning to analyze patterns.
Configure dynamic rules: limits by country, IP and device.
Introduce multi-factor authentication (SMS, push, biometrics).
Why: to make sure the notification came from the payment provider and not from an attacker.
Verification methods:
HMAC signature — the provider signs the payload with a secret key.
Certificates — verification of TLS and root certificates.
IP allowlist — accept requests only from trusted addresses.
Re-validation — the server makes a request back to the provider's API to confirm the transaction.
When integrating payment systems, design and the brand book play a key role: the payment interface must be visually consistent with the company's corporate style in order to increase trust and conversion, while still complying with the provider's requirements (for example, Yandex Pay or Apple Pay) regarding the use of their logos and elements.
Brand consistency: colors, fonts and logos must match the main site or application. This reduces the feeling of a «foreign» interface and increases trust.
UX and simplicity: minimizing steps, clear buttons («Pay», «Confirm»), no unnecessary forms.
Localization: support for the languages and currencies relevant to the target audience.
Responsiveness: correct rendering on mobile phones, tablets and desktops.
Logo usage rules: for example, Yandex Pay requires the payment button to carry the branded badge and not be altered in color or shape.
Interface elements: buttons, badges and popups must be embedded strictly according to the guidelines (size, padding, placement).
A unified experience: the brand book requires the user to see the same payment scenario across the different sites and applications where the system is used.
No modifications allowed: you may not change the brand colors or add shadows or animations to payment system logos.
| Approach | Pros | Cons |
|---|---|---|
| Full customization (own gateway) | Full control over design, UX in the company's style | High security requirements, PCI DSS |
| BaaS platforms | Seamless integration under the company's brand, ready-made infrastructure | Functional limitations, dependence on the provider |
| Ready-made SDKs/widgets | Fast integration, compliance with the provider's brand book | Less design freedom, mandatory elements |
Failure to follow the brand book may result in certification being refused or the integration being blocked.
Reduced trust: if the design of the payment page differs sharply from the main site, users may suspect fraud.
Legal requirements: using someone else's logos without consent violates the rules and may lead to fines.
The most common scenario.
The user:
Examples:
Characteristics:
The merchant initiates the charge without the user's participation at the moment of payment.
Examples:
The flow:
Characteristics:
The simplest type of integration.
The flow:
Application
↓
Backend
↓
Payment Provider
↓
Bank
After a successful payment:
Critical points:
Subscriptions are substantially more complex than one-off payments.
The main entities:
For example:
{
"plan": "Premium",
"period": "monthly",
"nextBillingDate": "2026-07-01"
}
The billing period:
The trial period:
7 days free ↓ automatic charge
When a user signs up for a subscription:
First payment ↓ Saving the payment method ↓ Automatic charges
Technically, it is usually not the card that is stored but a token.
For example:
card ↓ payment token ↓ merchant
Advantages:
A very important part of subscriptions.
Causes:
The standard scenario:
Day 1: charge attempt ↓ Error ↓ Retry after 1 day ↓ Error ↓ Retry after 3 days ↓ Error ↓ The subscription becomes Past Due
This is called retry logic or dunning management.
So as not to cut the user off immediately.
Example:
The subscription has ended ↓ The charge failed ↓ Access is kept for another 7 days ↓ If payment is not recovered — access is switched off
Very common in SaaS.
Subscriptions on iOS follow Apple's model.
The user buys a subscription through:
the Apple App Store.
The developer does not receive card data.
Apple manages:
If the renewal failed:
The subscription has expired ↓ Apple keeps trying to charge the money ↓ For several days or weeks ↓ If it succeeds — the subscription is restored
During this period the user may remain in a special state.
Apple allows you to enable a Grace Period.
Example:
The subscription has ended ↓ A problem with the card ↓ The user keeps using the service ↓ Apple tries to collect payment
If the money is charged:
The subscription status is Active again
Apple can automatically restore a subscription after a successful retry charge.
Events arrive via:
For the backend it looks roughly like this:
DID_FAIL_TO_RENEW ↓ GRACE_PERIOD ↓ DID_RENEW
Similar logic exists in:
Google Google Play.
States:
ACTIVE ↓ IN_GRACE_PERIOD ↓ ON_HOLD ↓ CANCELED
If the user tops up the card:
ON_HOLD ↓ ACTIVE
without signing up for the subscription again.
Many people do it like this:
The client said: "Payment successful" ↓ We grant access
This is insecure.
The correct way:
Provider ↓ webhook (with digital signature verification) Backend ↓ Database update ↓ The client receives the status
The source of truth is always the server.
ACTIVE ↓ PAST_DUE ↓ GRACE_PERIOD ↓ SUSPENDED ↓ CANCELED
or
TRIAL ↓ ACTIVE ↓ FAILED_PAYMENT ↓ RECOVERED
Such a state machine usually underpins any serious subscription integration.
User Subscription Payment Method Token Invoice Payment Attempt Webhook Event
For example:
{
"subscriptionId": "sub_123",
"status": "active",
"nextBillingDate": "2026-07-01",
"paymentProvider": "apple"
}
11.1. The scenario:
This is what the purchase restoration mechanism is for.
Example:
A user bought Premium for $50.
A month later:
Deleted the application ↓ Installed it again ↓ Signed in to the account
If restoration is not implemented, the application may display:
Buy Premium
even though the user has already paid.
This is a poor user experience and may violate app store requirements.
In Apple's ecosystem, purchases belong not to the application but to the user's Apple ID.
Therefore the application can ask Apple for a list of previously purchased products.
The flow looks like this:
User ↓ Taps Restore Purchases ↓ App Store ↓ Returns the purchase history ↓ The application restores access
Usually:
Examples:
Premium Unlock No Ads Pro Features Monthly Subscription
Consumable items (consumables):
100 coins 500 gems 1 extra life
Once used, they are considered consumed.
Apple recommends having a button:
Restore Purchases
especially if the application sells:
The application requests data from the App Store.
Apple returns:
{
"productId": "premium_monthly",
"transactionId": "123456",
"purchaseDate": "...",
"expiresDate": "..."
}
The backend validates the data.
If the purchase is valid:
User ↓ Premium = true
Many applications additionally have their own account system.
For example:
The purchase is made ↓ Linked to user_id ↓ Saved in the backend
Then restoration happens in two ways:
If the user changed devices.
If the user simply signed in to their account again.
For example:
Login ↓ Backend ↓ You already have Premium ↓ Access granted
Google works with a similar scheme.
Purchases are tied to the Google account.
After reinstalling the application:
Google Play ↓ Returns purchase details ↓ The application restores access
A reliable scheme usually looks like this:
Purchase ↓ Store ↓ Receipt / Purchase Token ↓ Backend ↓ Validation with the Store ↓ Saving the access entitlement
On restoration:
Restore Purchases ↓ Store ↓ Receipt ↓ Backend validation ↓ Entitlement restoration
The key concept here is the entitlement (the right of access).
The application should not be asking:
Was there a purchase?
It should be asking:
Is there currently a right to use the feature?
For example:
{
"userId": "123",
"premium": true,
"expiresAt": "2026-08-01"
}
Storing the information only on the device.
UserDefaults SharedPreferences
After a reinstall everything disappears.
Granting access based only on a local flag.
isPremium = true
Not supporting restoration for lifetime purchases.
The user once bought:
Premium Forever
After changing phones, they see the purchase screen again.
This is a frequent cause of complaints and refunds.
As for testing payments in payment systems, it has a number of specifics.
For modern mobile applications, the usual approach is:
This approach is considered the most reliable and matches the recommendations of the mobile platforms.
From the point of view of mobile application architecture, today you usually have to support three different classes of payments at the same time:
It is precisely the handling of subscription states, retry attempts, grace periods and recovery that usually accounts for 70–80% of the complexity of a payment integration, rather than the initial payment itself.
Comments