Q1When you take payments through a payment service provider, where does the card data travel?
How Billing Works — What Stripe Handles, and Granting Entitlements by Webhook
This article is part of the IT Foundations course, which builds up from scratch the practical IT knowledge you need at a minimum for programming and vibe coding.
Card data stops at the payment service provider and never passes through your own server. Diagrams show why a webhook notification is what justifies unlocking paid features.
This article covers how billing works.
It splits into two parts: keeping card data from ever reaching your own server, and reflecting the result of a payment in your own app.
The card number stops on the upper branch and never reaches my-app.
The only thing that justifies unlocking paid features is the notification that arrives on the lower branch.
Receiving the card number and exchanging data with the card company both happen on the payment provider's side, and all that is left for my-app is writing the result into a record.
The payment service provider receives the card data, not your own server
Processing a payment requires the card number, expiry date, and security code.
PCI DSS (Payment Card Industry Data Security Standard) is the standard that businesses handling card data must follow, and a payment service provider is an outside service that handles card payments on your behalf.
Stripe and PayPal are the best-known payment service providers.
Where you put the input fields determines where the card number travels.
| Where the input fields sit | Where the card number travels | In scope for PCI DSS |
|---|---|---|
| Received and stored by my-app | my-app and the payment provider | my-app is in scope too |
| Received by my-app and forwarded | my-app and the payment provider | my-app is in scope too |
| The payment provider's page | The payment provider only | my-app's scope is minimal |
Only the third row keeps the card number off my-app's server.
In that case all my-app holds is the value that identifies the payment, while the first two rows leave the card number itself or a record of the traffic behind.
Even if you forward it without storing it, the value still passes through your own server, and the moment it passes through, my-app falls inside PCI DSS scope as well.
To keep the area my-app has to protect as small as possible, you hand the input fields themselves over to the payment service provider.
- Card number, expiry date, security code
- Records of the exchange with the card company
- The value that identifies the payment, pay_88
- The amount, and whether the payment completed
- Which user the payment belongs to (u_1024)
The card number never passes through my-app
If you never receive the card number yourself, the responsibility for protecting it never lands on your side either.
Whether you store it or forward it straight on, the same responsibility applies once it passes through your own server, so you hand the input fields themselves to the payment service provider and let only the fact that the payment completed reach my-app.
The payment finishes on a checkout page on the payment provider's domain
A hosted checkout page puts the entry screen itself somewhere else so that card data never passes through your own server: it is a payment screen the payment service provider prepares and shows on its own domain.
Moving the user to a page on a different domain is a redirect.
In the second stage you pass the amount and the return URL, and the payment service provider returns the URL of a checkout page dedicated to that payment.
This exchange happens by calling a web API, and you attach an API key.
- The paid plan sign-up page
- /thanks, opened after the payment
- The checkout page where the card number is entered
- my-app cannot see what is inside this page
The checkout page is also offered as a frame placed inside my-app's own page.
It looks different, but the values entered go to the same place.
The payment service provider provides the checkout page
The page where the card number is entered belongs to the payment service provider, not to my-app.
A redirect moves the user to that page, and after the payment they come back to /thanks; even if you place it as a frame in your own page, the values entered still go to the same place.
An entitlement is granted on the webhook notification, not on the page the user returns to
my-app, not the payment service provider, decides whether paid features may be shown, and that record is the entitlement — a record of the range of features a user who has paid can use.
The user's browser landing back on /thanks does not by itself tell my-app whether the payment completed.
Look at what happens after a payment as two separate paths.
The upper path stops right there if the user closes the browser just after paying.
The lower path runs server to server, so if the notification does not arrive it is retried for a set number of times over a set period.
Depending on which one you treat as the justification, the same three people end up with different entitlements.
| What happened to that user | If you judge by /thanks | If you judge by /webhook |
|---|---|---|
| Paid and opened /thanks | Entitlement granted | Entitlement granted |
| Paid, then closed the browser at once | No entitlement granted | Entitlement granted |
| Opened /thanks without paying | Entitlement granted | No entitlement granted |
If you judge by /thanks, the person who closed the browser gets nothing while the person who never paid gets an entitlement.
The only thing you use to justify granting an entitlement is the notification on the lower path.
In payments, applying the same notification twice extends the usable period twice over.
Record the value that identifies each notification, and leave the entitlement unchanged when the same notification arrives a second time.
A notification whose signature does not match is discarded without changing the entitlement.
The only difference between the remaining two is whether that notification has already been applied.
- The value that identifies the user, u_1024
- Password hash
- Plan type (paid)
- Expiry date
- Which user it belongs to (u_1024)
- The value that identifies the notification, evt_7
- When it was applied
Even if you hide the paid button on the page, that code still reaches the user's device.
Checking the entitlement on the server every time is the same authorization covered in How Login Works.
The card number stops inside the middle box and never enters the box on the right.
The entitlement is granted when the notification on arrow 2 arrives, not when the user's browser comes back.
A notification from the server reports the payment
A completed payment is reported by the notification arriving from the payment service provider's server, not by the page the user comes back to.
You verify the signature on what arrives, leave the entitlement unchanged if the notification has already been applied, write the result into your own database, and decide from it whether to show paid features.
With a subscription, a notification arrives each period and the entitlement changes
A contract charged monthly or yearly is a subscription — a contract in which payment repeats automatically at a set interval.
A repeating payment does not succeed every time.
On the failure row, the payment service provider waits a few days and retries the charge.
my-app decides whether to leave access in place during that time or cut it off right away.
Test mode is what lets you check things before going live: a state the payment service provider prepares for checking behavior without creating a real charge.
The API keys are separate for test and production, so you switch between them with the value of an environment variable.
Decide the notification that removes an entitlement before you go live
With a subscription a payment happens every time the period comes around, and a notification arrives each time.
If you handle only the notification that grants access, users whose payments have stopped keep their access, so decide granting, extending, and removing for each notification that arrives, and run through the whole flow in test mode before going live.
Knowledge Check
Answer each question one by one.
Q2Which of these do you rely on to decide that a payment completed and grant the user an entitlement?
Q3When a notification reporting a failed payment arrives for a recurring charge, which of these does your app do?