Payins Quickstart
Accept your first payment in sandbox: create a checkout, pay it with a test card on the hosted page, and confirm the result from your server.
This walkthrough collects 49.99 USD from a test buyer in sandbox, using the hosted checkout page. Run the steps in order. It takes about 5 minutes.
Before you start
- Sandbox API credentials with the Merchant Write permission (the User role or higher). See Permissions.
- USD enabled for payins on your sandbox account. Your Payment Labs contact sets which currencies you accept.
- A browser, to pay as the buyer.
- Your terms of service and privacy policy URLs. The buyer sees links to both on the payment page.
export BASE_URL="https://api-sandbox.paymentlabs.io"Step 1: Get a token
Call Create Access Token with your client ID and secret.
export TOKEN=$(curl -s -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d '{"method": "client-secret", "key": "<client-id>", "secret": "<secret>"}' | jq -r .token)const BASE_URL = "https://api-sandbox.paymentlabs.io";
const { token } = await fetch(`${BASE_URL}/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method: "client-secret", key: process.env.PL_CLIENT_ID, secret: process.env.PL_SECRET }),
}).then((r) => r.json() as Promise<{ token: string }>);This is the single Payment Labs login: the same operation and token serve payins, payouts and every other API. See Environments and Authentication.
Step 2: Create a checkout
A checkout is a request for one payment from one buyer. Create it with Create Checkout.
curl -s -X POST "$BASE_URL/checkouts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"alias": "Annual membership",
"referenceId": "order-50213",
"amount": 49.99,
"currency": "USD",
"buyer": { "email": "[email protected]", "referenceId": "customer-881" },
"termsOfServiceUrl": "https://shop.example.com/terms",
"privacyPolicyUrl": "https://shop.example.com/privacy",
"redirectLinks": {
"successRedirectUrl": "https://shop.example.com/orders/50213/thanks",
"failureRedirectUrl": "https://shop.example.com/orders/50213/payment-failed",
"expiredRedirectUrl": "https://shop.example.com/orders/50213/expired"
}
}'type Checkout = {
id: string;
status: "PENDING" | "PROCESSING" | "PAID" | "FAILED" | "EXPIRED" | "UNKNOWN";
links: { checkout: string; embeddable: string };
chargedAmount?: { value: number; currency: string };
};
const checkout = await fetch(`${BASE_URL}/checkouts`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({
alias: "Annual membership",
referenceId: "order-50213",
amount: 49.99,
currency: "USD",
buyer: { email: "[email protected]", referenceId: "customer-881" },
termsOfServiceUrl: "https://shop.example.com/terms",
privacyPolicyUrl: "https://shop.example.com/privacy",
redirectLinks: {
successRedirectUrl: "https://shop.example.com/orders/50213/thanks",
failureRedirectUrl: "https://shop.example.com/orders/50213/payment-failed",
expiredRedirectUrl: "https://shop.example.com/orders/50213/expired",
},
}),
}).then(async (r) => {
if (!r.ok) throw new Error(`Create checkout failed with ${r.status}: ${await r.text()}`);
return r.json() as Promise<Checkout>;
});The response is 200 with the checkout in PENDING:
id: the checkout ID, for exampleS-01JBXA3C7D9F2H4K6M8N0P1Q3R. Store it with your order.links.checkout: the hosted payment page. Send the buyer there.links.embeddable: the same page for an iframe in your site.amountis a JSON number in decimal notation (49.99), not a string.- Payment Labs creates the buyer from
buyer.emailif it is new to you.
export CHECKOUT_ID="<id from the response>"Step 3: Pay as the buyer
Open links.checkout in a browser and pay with a sandbox test card:
| Field | Value |
|---|---|
| Card number | 4111 1111 1111 1111 (Visa) or 5454 5454 5454 5454 (Mastercard) |
| CVC | 555 |
| Expiry | Any future date |
| Name | Any value |
When the payment succeeds, the page sends the buyer to successRedirectUrl. To test a decline, see Credit Card Payin Test Values.
Step 4: Confirm the payment on your server
The redirect only tells you the buyer finished the page. Before you fulfil the order, confirm the checkout status from your server with Get Checkout.
curl -s "$BASE_URL/checkouts/$CHECKOUT_ID" -H "Authorization: Bearer $TOKEN" | jq '{status, chargedAmount: .chargedAmount.formattedValue}'const current = await fetch(`${BASE_URL}/checkouts/${checkout.id}`, {
headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json() as Promise<Checkout>);
if (current.status === "PAID") {
// fulfil the order
}status | Meaning | What to do |
|---|---|---|
PENDING | Not paid yet. | Wait. |
PROCESSING | A charge is in progress. | Wait for the final status. |
PAID | The buyer paid. chargedAmount holds the amount charged. | Fulfil the order. |
FAILED | The charge failed. The hosted page does not offer the buyer another attempt on this checkout. | To let the buyer try again, create a new checkout. You can charge a saved card on the same checkout yourself; see Checkout Statuses and Failure Reasons. |
EXPIRED | The checkout passed expiresAt without being paid. | Create a new checkout. |
UNKNOWN | The charge was never confirmed as succeeded or failed. The checkout cannot be charged again. | Do not charge the buyer again or cancel the order. Contact Payment Labs support with the checkout ID. |
In production, do not poll. Register a webhook for checkout-status-changed and Payment Labs posts the checkout to your endpoint when its status changes. See Webhooks.
Errors and edge cases
| Status | Meaning | What to do |
|---|---|---|
400 VALIDATION_ERROR | A required field is missing or invalid. details names each field. | Fix the fields and retry. See Errors. |
400 other codes | Your account cannot accept this amount or currency. | Check the currency is enabled for your account. |
403 FORBIDDEN_INSUFFICIENT_TAGS | Your credential lacks the Merchant Write permission. | Use a credential whose role includes it. |
A timed-out create request may still have created the checkout. Search by referenceId before you create another. See Idempotency and Retries.
Next steps
- Hosted Checkout Page and Iframe: hosted page, iframe, redirects and page events.
- Payin SDK: embed the payment form with full control of the layout.
- Webhooks: confirm payments without polling.
- Subscriptions: charge buyers on a schedule.
Updated 1 day ago
What’s Next
Next: Errors.
