lollipayDocs

Accept a payment

Create a checkout session on your server, send the customer to the hosted page, and confirm the payment when they come back.

A checkout session is one attempt to collect a payment. You create it on your server with what the customer is buying, Lollipay hosts the payment page, and the session records the result.

1. Create the session

Send the line items from your server. Each item has a name and a unit amount in minor units, plus an optional quantity (1 to 100) and a square HTTPS image.

curl https://api.lollipay.cc/v1/stores/$LOLLIPAY_STORE_ID/checkouts \
  -H "Authorization: Bearer $LOLLIPAY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Upgrade to Pro",
    "items": [
      { "name": "Pro plan (yearly)", "amount": 4900 },
      { "name": "Extra seat", "amount": 1500, "quantity": 2 }
    ],
    "amount": 7900,
    "currency": "usd",
    "customer": { "email": "buyer@example.com", "name": "Emma Wilson" },
    "metadata": { "order_id": "A-1042" },
    "return_url": "https://example.com/thanks",
    "cancel_url": "https://example.com/pricing"
  }'
FieldNotes
items1 to 10 items. Together they must come to between 99 and 100,000,000 minor units ($0.99 at least).
amountOptional. The total you expect. If it doesn't match the items, the request fails with amount_mismatch, which catches pricing bugs before a customer sees them.
currencyThree-letter ISO code. Defaults to your store's currency.
customerPrefills the email on the payment page; the receipt goes there.
metadataUp to 20 string values (keys up to 40 characters, values up to 500). Use it to link the session to your order.
return_urlHTTPS page the customer goes to after paying. checkout_id is added to its query string.
cancel_urlHTTPS page behind the payment page's back link.

Store the session id with your order. The session stays open for 1 hour.

2. Send the customer to the payment page

Redirect the customer to checkout_url from the response, the session's own page at https://checkout.lollipay.cc/invoice/cs_…. On a server-rendered site that's a 303 redirect from the form handler; in a single-page app, set window.location to it.

app/checkout/route.ts
export async function POST() {
  const res = await fetch(`https://api.lollipay.cc/v1/stores/${process.env.LOLLIPAY_STORE_ID}/checkouts`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LOLLIPAY_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      items: [{ name: "Pro plan", amount: 4900 }],
      return_url: "https://example.com/thanks",
    }),
  });
  const { ok, data, error } = await res.json();
  if (!ok) return Response.json({ error: error.message }, { status: 502 });
  return Response.redirect(data.checkout_url, 303);
}

3. Confirm the payment

When the customer lands on return_url?checkout_id=cs_…, retrieve the session on your server and check it before you fulfil:

app/thanks/page.tsx
const res = await fetch(`https://api.lollipay.cc/v1/stores/${storeId}/checkouts/${checkoutId}`, {
  headers: { Authorization: `Bearer ${process.env.LOLLIPAY_SECRET_KEY}` },
});
const { data: session } = await res.json();

if (session.status === "complete" && session.metadata.order_id === order.id) {
  // Mark the order paid. session.payment_id identifies the payment in the dashboard.
}

Don't trust the URL alone

Anyone can type ?checkout_id=… into a browser. Always retrieve the session with your secret key and check its status, and that it belongs to the order you expect, before giving anything away.

Session statuses

StatusMeaning
openWaiting for payment. Declined attempts keep the session open so the customer can retry. A crypto payment that's on its way keeps the session open, even past expires_at, until it has enough confirmations. So does a skins trade, for the 7 days Steam holds it.
completePaid. payment_id and completed_at are set.
expiredNot paid within 1 hour. Create a new session if the customer comes back.

Crypto payments complete once the network confirms them: under a minute for Solana and Ethereum, 10 to 30 minutes for Bitcoin and Litecoin. The customer may close the page before that, so they don't always come back to return_url: a session that's still open when they leave can turn complete later. Retrieve it again before you decide an order wasn't paid.

Skins payments (game items, through Steam) complete about a week after the customer accepts the trade, when Steam's Trade Protection hold ends. The session stays open until then.

On this page