Integrating a 3rd-party top-up / code API
TL;DR
By default it uses the built-in Mock vendor. To use a real upstream: set env vars, restart, change no code.
1. An upstream only needs five capabilities
| Capability | Method | Purpose |
|---|---|---|
| Pull catalog | pullCatalog() | Sync prices, stock, sold-out status |
| Check stock | fetchStock(ids) | Validate before ordering |
| Create order | createOrder(req) | Idempotency key; sync return or async fulfilment |
| Query order | queryOrder(id) | Polling fallback outside of webhooks |
| Callback | verifyWebhook() | Primary path for async delivery results |
2. Environment variables
UPSTREAM_ENABLED=true
UPSTREAM_ADAPTER=rest
UPSTREAM_BASE_URL=https://api.supplier.example
UPSTREAM_API_KEY=xxxxxxxx
UPSTREAM_API_SECRET=yyyyyyyy
UPSTREAM_SIGNATURE=hmac_sha256 # none|bearer|header_key|query_key|hmac_sha256|md5
UPSTREAM_PATH_CATALOG=/api/products
UPSTREAM_PATH_CREATE_ORDER=/api/order/create
UPSTREAM_PATH_QUERY_ORDER=/api/order/query
UPSTREAM_MAPPING_JSON='{"itemsPath":"data.list","fields":{"externalId":"id","title":"name","price":"price","stock":"stock","soldOutFlag":"sold_out"},"order":{"orderIdPath":"data.order_no","contentPath":"data.content"}}'
3. What the signature does
The hmac_sha256 strategy sorts the request body's params into k1=v1&k2=v2, appends the API Secret, then hashes. This is how the vast majority of domestic platforms work; md5 is a legacy variant.
Webhook verification uses a constant-time comparison and checks a timestamp tolerance window to prevent replay.
4. How to fill in the field mapping
itemsPath: the path to the array in the response, e.g.data.list.fields.externalId: the product's unique id, aligned with the site'supstream_ref.soldOutFlag/soldOutWhen: platforms express "out of stock" in countless ways (boolean, string, zero stock); these two fields unify them.order.contentPath: where delivery content lives; split a string by lines, or an array by elements.
5. How fast is switching vendors
If the new platform still fits those five capabilities: only env vars change, zero code changes.
If it's unusual (SOAP, a private binary protocol, session login required): add a file implementing UpstreamAdapter, register it in the factory, and every other layer stays unaware.
6. Self-check
GET /api/upstream/health
Returns the active config summary, health status, and catalog count. Check this first after wiring a new vendor.
7. Stability strategy
- Timeout + backoff retry: 10s default timeout, up to 2 retries.
- No aggressive order retries: avoid double-shipping; retry only once.
- Catalog cache: 60s default. On upstream jitter, return the previous result marked
staleso the page never goes blank. - Idempotency key:
order-no-line-no; one duplicate submit yields one upstream order.
8. Where the code lives
lib/server/upstream/types.ts # interface contract (five capabilities)
lib/server/upstream/mock-adapter.ts # local vendor
lib/server/upstream/rest-adapter.ts # generic REST adapter
lib/server/upstream/index.ts # facade: cache, degradation, error handling
lib/server/env.ts # all config
lib/server/http.ts # timeout/retry/idempotency
lib/server/signature.ts # signing & webhook verification
app/api/upstream/health/route.ts # self-check endpoint
app/api/upstream/webhook/route.ts # async callback entry