Wupex — WPX (voucher)
Voucher vendor adapter — static API-key header, Cloudflare-fronted catalog with 429 backoff, USD-settled with currency inferred from product names.
Quick facts
Code WPX (vendor_type BOTH) · Line voucher · Auth static x-api-key header · Model sync request / async result (create then GetVouchers) · Webhook none · Delivery serial code+pin · Adapter services/external_vendors/vouchers/wupex_vendor.go
Wupex is integrated twice — a voucher adapter (here) and a separate top-up adapter — sharing only the upstream API + the single WPX DB row. Registered vouchers/factory.go:56. Seeded IsBulk:true, so it flows through the bulk-vendor checkpoints. See Order Lifecycle.
Auth
Static x-api-key header (+ Accept-Language: en-US); no login. merchant_code is sent as the merchant field in the order body, not a header. vendor_attributes: host, api_key, merchant_code (read from Extra, falling back to Username for legacy mis-seeded rows).
Catalog
GetCatalogBatched — POST /api/product/merchant/invited/list {page, pageSize:100}.
Cloudflare rate-limit handling. Wupex sits behind Cloudflare (error code: 1015 / HTTP 429, HTML body). The loop paces at 2s/page and on 429 backs off exponentially 30s<<attempt capped at 2m, up to 5 retries — checking the status before JSON unmarshal because the 429 body is HTML.
Currency inferred from product name. Wupex settles everything in USD even for MENA cards, so faceValue/exchangeRate collapse to USD. The face currency and denomination are parsed from the product name (regex + allowlist + symbol map); country strings map to ISO alpha-2 (unknown → "" to avoid overflowing country VARCHAR(10)).
Mapping: productCode→ID/SKU; productType→BrandName; discountRate→Discount; IsFixedDenomination:true.
Order create
POST /api/order/pull-codes?referenceId=<ReferenceCode> (referenceId = idempotency), body is an array with one object [{merchant, sku, quantity}]. Per docs the response omits serials, so it returns StatusAccepted with empty vouchers — the caller calls GetVouchers separately.
Order status / poll
GET /api/order/detail?orderName=<id>. No explicit status field — status is inferred: any line has serials → complete; zero order data → pending+retry. Not-found via error.code ORDER_NOT_FOUND/NOT_FOUND or a raw "File not found"/"not found" substring → ErrOrderNotFound.
Delivery
Same /api/order/detail endpoint. Each serials[] entry → Voucher{Code: serialCode, Pin: serialNumber}, amount split evenly across serials, reference WPX_<serial>_<idx>, expiry now+365d.
Balance
GET /api/customer/balance → uses creditBalance (= balance + credit) as available, VendorWalletID = merchantCode, default USD.
Quirks
CancelVoucherunsupported;ProductAvailabilitystubtrue.isRetryableErrorclassifies error codes; unknown codes default to not-retryable.- Reference codes
WPX_<unixnano>[_R<retry>][_<ref>].
Tests, mock, docs
Unit tests wupex_vendor_test.go (incl. the name-parsing helpers); contract test (build tag contract); orchestration suite test/clientapi/wupex_orchestration_test.go (~40 subtests). Mocky route frontend/mocky-balboa/src/routes/wupex.ts. Docs: docs/wupex/ (PDF + Postman); mapping/wupex_catalog.tsv.
NeoCurrency — NEO
Voucher vendor adapter — lazy OAuth2 bearer (24h JWT, reactive 401-retry), synchronous inline delivery, link-only vouchers, and heavy spec drift.
Grasshopper — GH
Voucher vendor adapter — Octopus's own inverted-flow claim system. Octopus mints code+PIN and ships only SHA-256 hashes to a Cloudflare Worker.