Short answer: Your app or website opens a payment:// URL, Payment for Stripe takes the payment, then opens your callback URL with the result. No SDK. No server work. It works the same on iPhone and Android, with a few differences called out below.
The full reference lives at paymentforstripe.com/api.
How it works
- Your app opens a
payment://URL with parameters. - Payment for Stripe opens and sets up the sale. The merchant takes the payment with a reader, Tap to Pay, or a typed card.
- When it's done, the app opens your
callbackURL with the result added as query parameters.
The merchant needs to be signed in. If a link arrives while another charge is running, the app turns it down and your callback gets error=charge_in_progress.
Create a charge: payment://new
A one-time payment for an amount you set.
| Name | Type | Required | Description |
|---|---|---|---|
amount |
integer | Yes | Amount in the currency's smallest unit, greater than 0. 1000 = $10.00. |
currency |
string | No | Three-letter ISO code, like usd or eur. Leave it out and the app uses the currency it's already set to. |
description |
string | No | Charge description. URL-encode it. |
customer |
string | No | Existing Stripe customer ID (cus_…). customer_id works too. When set, email and name are ignored. |
email |
string | No | Customer email. Creates a new customer. |
name |
string | No | Customer name. URL-encode it. Creates a new customer. |
metadata |
string | No | URL-encoded JSON object of key-value pairs, added to the payment. Stripe's metadata limits apply. |
callback |
string | No | URL to open when the sale finishes. |
autoprocess |
boolean | No |
true skips the keypad and starts the charge right away. Default false. |
auto_return |
boolean | No |
true opens the callback as soon as the result is known. Default false: the callback waits until the merchant closes the result screen. |
payment://new?amount=2500¤cy=eur&description=Coffee%20and%20pastry
payment://new?amount=3500&autoprocess=true&auto_return=true&callback=myapp://payment-complete
A link with a missing, zero, or non-numeric amount is ignored.
Create an invoice from your catalog: payment://cart
Fills the cart with prices from your Stripe product catalog. The merchant then charges as usual, and the sale is saved in Stripe as a paid invoice with those line items. It doesn't email an invoice to your customer.
| Name | Type | Required | Description |
|---|---|---|---|
prices |
string | Yes | Comma-separated price_id:quantity pairs, like price_abc:2,price_xyz:1. line_items works too. |
coupon |
string | No | Stripe coupon ID to apply to the cart. |
customer |
string | No | Existing Stripe customer ID. customer_id works too. |
email |
string | No | Customer email. Creates a new customer. |
name |
string | No | Customer name. URL-encode it. |
metadata |
string | No | URL-encoded JSON object of key-value pairs. |
callback |
string | No | URL to open when the sale finishes. |
autoprocess |
boolean | No |
true starts the charge once every price (and the coupon) has loaded. |
auto_return |
boolean | No | Same as above. Default false. |
payment://cart?prices=price_coffee:2,price_muffin:1&email=customer@example.com
Don't send amount with prices. The total comes from the prices, and a link with both is ignored. Prices hidden with payment_hidden still work here.
Pay an existing invoice: payment://invoice
Collects an in-person payment for an open Stripe invoice. The amount, currency, customer, and tax all come from the invoice. On success, the app marks the invoice paid. Works on iPhone and Android.
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Stripe invoice ID. Must start with in_. Send an open invoice. On iPhone, a draft is finalized first. On Android, a draft is turned down with error=invoice_state_changed. |
metadata |
string | No | URL-encoded JSON object, added to the PaymentIntent. |
callback |
string | No | URL to open when the sale finishes. Gets invoice_id and payment_intent_id on success. |
autoprocess |
boolean | No |
true starts the charge after the invoice checks pass, only when a card reader is connected. |
auto_return |
boolean | No | Same as above. Default false. |
payment://invoice?id=in_1ABC123xyz&autoprocess=true&callback=myapp://invoice-paid
The invoice sets the sale, so any of amount, currency, customer, customer_id, email, name, description, coupon, prices, or line_items gets the link turned down with error=invoice_invalid_param. Invoices that are paid, void, uncollectible, $0, tied to a subscription, or set to charge a default payment method are turned down too, each with its own invoice_… error.
The callback
The app adds these to your callback URL:
| Name | Type | Sent | Description |
|---|---|---|---|
status |
string | Always |
success, error, or cancelled. |
charge_id |
string | On success | The payment's ID. On iPhone it's the Stripe charge (ch_…). On Android it's the PaymentIntent (pi_…). |
stripe_charge_id |
string | Android only | The Stripe charge (ch_…). |
payment_intent_id |
string | On success | The PaymentIntent. iPhone sends it for payment://invoice only. Android sends it for every card payment. |
invoice_id |
string | Invoice links | The paid invoice, for payment://invoice. |
amount |
integer | On success | Amount charged, in the smallest unit. |
currency |
string | On success | Lowercase ISO code. |
error |
string | On error | A short code like charge_in_progress or invoice_already_paid, or a readable message for a declined card. |
myapp://payment-complete?status=success&charge_id=ch_3Qx…&success=true&amount=3500¤cy=usd&chargeId=ch_3Qx…
Need the same ID on both platforms? Read stripe_charge_id first, then fall back to charge_id.
For older integrations, the app also sends success=true, chargeId, cancelled=true, and error[message]. New code should read status.
A cancel opens the callback right away. A charge's success or error waits for the merchant to close the result screen, unless you pass auto_return=true. On iPhone, a cash sale never opens the callback, since nothing was charged.
URL encoding
- Encode every value that has spaces or symbols. Spaces become
%20,{and}become%7Band%7D,"becomes%22,:becomes%3A. - If your callback URL has its own query string, encode the whole callback so its
&doesn't split yourpayment://link. - The commas and colons in
pricescan stay as they are.
Metadata {"order_id":"1234","location":"Store A"} becomes:
payment://new?amount=10000&description=Invoice%20%231234&metadata=%7B%22order_id%22%3A%221234%22%2C%22location%22%3A%22Store%20A%22%7D
The older metadata[order_id]=1234 form works too.