# Merchant integration examples

No SDK dependencies or wallet private keys. Keep API keys on your server.
Use Node.js 22+ or Python 3.11+. Persist one idempotency key per business operation,
including after application restarts; use a different key for a genuinely new invoice.
The clients retry transient failures up to three times and never follow redirects.
A timed-out POST has an unknown outcome: retry with the SAME body and key.
HTTP 402 means insufficient credits; do not retry it endlessly. HTTP 409 requires
reconciling the previous request. GET billing/quote provides a read-only forecast.

JavaScript:

```js
import {PaymentsClient} from './client.mjs';
const api = new PaymentsClient(process.env.PAYMENTS_API_BASE, process.env.PAYMENTS_API_KEY);
const invoice = await api.createInvoice({amount:'10', expiresIn:3600, externalId:'order-123'}, 'persisted-order-123-key');
// Persist invoice.id with order-123, then share API_BASE without /v1 + /i/ + invoice.id.
```

Python:

```python
from client import PaymentsClient
import os
api=PaymentsClient(os.environ['PAYMENTS_API_BASE'],os.environ['PAYMENTS_API_KEY'])
invoice=api.request('invoices','POST',{'amount':'10','currency':'USDC','expiresIn':3600,'externalId':'order-123'},'persisted-order-123-key')
```

Run `WEBHOOK_SECRET=... python3 receiver.py` behind your own public HTTPS proxy.
The example listens only on 127.0.0.1:8081 and persists a deduplicated SQLite outbox.
It verifies the signature against exact raw request bytes and rejects timestamps
outside five minutes. Synchronize the host clock. Test events are acknowledged but
never enqueue a delivery. A UNIQUE event_id plus a transaction prevents duplicate
jobs; use an idempotent fulfillment operation in your own worker too. Merely
marking an event processed before delivering goods is not crash-safe delivery.

Before fulfilling a job, GET `payment-intents/{paymentIntentId}/receipt` using a
payments:read key. Compare expected amount (decimal string), USDC currency,
recipient wallet and invoice/order identity against YOUR stored order. Treat a
pending/unavailable receipt as retryable without delivering. Never trust a browser
redirect or screenshot as evidence of payment. Persist fulfillment by order ID,
because retries or multiple event types may refer to the same order. The receiver
is an integration example, not a complete merchant application or delivery worker.

Tests: `node --test client.test.mjs` and `python3 -m unittest test_client.py`.

Batch helpers: JS `quoteBatch(items, budgetCredits, key)` / `createBatch(...)`,
Python `quote_batch(items, budget_credits, key)` / `create_batch(...)`. These use
normal invoice pricing. A 200 batch response can contain per-item failures: retry
with the SAME persisted key/body to resume, never recreate successful items.
`changes(after)` returns a free event feed with decimal string cursors. Handle 410
cursor_expired by reconciling receipts first. See `/assets/openapi.json` for the
versioned merchant core contract and `docs/MERCHANT_OPERATIONS.md` for limits.
