Invoices vs orders: These guides walk through invoices (formal billing, line items, publish). The collect flow is the same for orders — use /v1/orders/* instead of /v1/invoices/* and set source_type: "ORDER" on the checkout session. See Orders and Parties & counterparties .Hosted checkout: To skip party, invoice, session, and execute steps entirely, see Hosted checkout .
Fastest path for Next.js: Hosted checkout — create a checkout source server-side and redirect to url. No Stripe or payment-account setup required.
Call the Dollr API from server-only code for API-embedded flows. Never expose your Client Secret in the browser.
Prerequisites
Next.js 14+ (App Router)
Environment variables in .env.local (not NEXT_PUBLIC_ except Stripe publishable key for card UI):
DOLLR_CLIENT_ID=...
DOLLR_CLIENT_SECRET=...
Option A — Hosted checkout (recommended)
// app/actions/checkout.ts
"use server" ;
const BASE = "https://api.heydollr.app" ;
async function dollrHeaders () {
const { access_token } = await fetch ( ` ${ BASE } /v1/jwt/client/obtain/token` , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
client_id: process . env . DOLLR_CLIENT_ID ,
client_secret: process . env . DOLLR_CLIENT_SECRET ,
}),
}). then (( r ) => r . json ());
return { Authorization: `Bearer ${ access_token } ` , "Content-Type" : "application/json" };
}
export async function createHostedCheckout () {
const headers = await dollrHeaders ();
const checkout = await fetch ( ` ${ BASE } /v1/checkouts/create` , {
method: "POST" ,
headers ,
body: JSON . stringify ({
mode: "HOSTED" ,
source_kind: "ORDER" ,
party_name: "Amara Kamara" ,
party_phone: "231771234567" ,
currency: "USD" ,
items: [{ name: "T-Shirt" , currency: "USD" , amount: 25 }],
success_url: ` ${ process . env . SITE_URL } /checkout/success` ,
cancel_url: ` ${ process . env . SITE_URL } /checkout/cancel` ,
}),
}). then (( r ) => r . json ());
return checkout . url ; // redirect customer here
}
On your success page, verify server-side with GET /v1/status/source before fulfilling — see Hosted checkout .
Option B — API-embedded collection
Route Handler — obtain token
// app/api/dollr/token/route.ts
const BASE = "https://api.heydollr.app" ;
export async function POST () {
const res = await fetch ( ` ${ BASE } /v1/jwt/client/obtain/token` , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
client_id: process . env . DOLLR_CLIENT_ID ,
client_secret: process . env . DOLLR_CLIENT_SECRET ,
}),
cache: "no-store" ,
});
const data = await res . json ();
return Response . json ({ access_token: data . access_token });
}
Server Action — full collect flow
Follow Quick Start steps: party → counterparty → invoice → publish → session → payment account → execute. Or use Collect with card for Stripe Elements.
Poll from a Route Handler
// app/api/dollr/status/[referenceId]/route.ts
export async function GET (
_req : Request ,
context : { params : Promise <{ referenceId : string }> }
) {
const { referenceId } = await context . params ;
const headers = await dollrHeaders ();
const res = await fetch (
`https://api.heydollr.app/v1/status/collection/ ${ referenceId } ` ,
{ headers , cache: "no-store" }
);
return Response . json ( await res . json ());
}
Do not expose DOLLR_CLIENT_SECRET to Client Components. Use Route Handlers or Server Actions only.
Try it yourself
Hosted checkout Mobile money and card on a Dollr-hosted page — fastest path.
Orders Same collect flow with source_type: ORDER and /v1/orders/*.
Direct checkout One API call to create source — API-embedded flow.