OctoWiki
Vendor AdaptersVoucher Vendors

NeoCurrency — NEO

Voucher vendor adapter — lazy OAuth2 bearer (24h JWT, reactive 401-retry), synchronous inline delivery, link-only vouchers, and heavy spec drift.

Quick facts

Code NEO (NeoCurrency / Your Digital Reward) · Line voucher · Auth OAuth2 password-grant bearer (lazy, 24h JWT) · Model synchronous (codes inline) + recovery-by-reference · Webhook none (polling-only) · Delivery activation link (no PIN) · Adapter neo_vendor.go, neo_errors.go

Synchronous, polling-only. POST /createordernow returns codes inline; lost responses recovered via GET /get-orders-rfid?reference_id=. Registered factory.go:64. See Order Lifecycle.

Auth

Lazy OAuth2 password-grant → bearer. POST /get-token with {client_id, client_secret, email, password}; token cached in memory (double-checked sync.RWMutex).

24h JWT, reactive refresh. Docs claim non-expiring but production returns a JWT with exp = iat+24h. Rather than proactive refresh, every authed call goes through authedDo which on a 401 clears the token, re-mints, and retries exactly once. Do NOT add proactive refresh. Minting invalidates the prior token for that user — run multiple processes against distinct API users.

vendor_attributes: host, client_id, client_secret, email (all in Extra), password, optional prepaid_client_id (Bancorp/Pathward for Visa/MC brands).

Catalog

GetCatalogBatched only — GET /brands?page=N&per_page=100 (1-indexed, no pagination metadata; stop on a short page). Brand DTO uses integer 0/1 for active/is_percent; value>0 = fixed denomination (min==max), value=0 = open range. Exactly one Product per brand.

Order create — synchronous

POST /createordernow. Codes returned inline; no async/polling on the happy path. Idempotency via unique_id — an integer (≤10 digits) derived by stripping the _R<n> retry suffix then FNV-1a hashing the reference (all retries share one key → duplicate retries rejected as 422, not double-charged). prepaid_client_id attaches to the brand item, not top-level. ProductAvailability is a no-op (true) — real failures surface at order time as 422.

Order status / recovery

No get-by-vendor-order-id endpoint. GetOrderStatus/GetVouchers key on the Octopus reference_id via GET /get-orders-rfid. A non-empty result implies COMPLETED (the API only stores fulfilled orders). 404/empty → ErrOrderNotFound.

Webhook

None — polling-only. Inherits the base normaliser (unused for NEO in prod).

Always a URL, never a PIN. neoClaimURL builds <base>/[sandbox/]activate-code/<code> from the host. Code set, Pin="", reference = uuid (or the code itself on the RFID recovery path). CancelVoucher unsupported (dashboard-only void).

Balance

GET /funds → flat array → one BalanceResponse per currency (unallocated pool).

Spec drift & error taxonomy

Production ≠ OpenAPI spec — the headline

The spec at docs/neocurrency/openapi.json is unreliable. Confirmed divergences (see Memory Appendix):

  • /get-token success is an array [{access_token}], not an object.
  • token is a 24h JWT (spec says non-expiring).
  • step_size is often null; expiry_in_months is sometimes a number, sometimes an empty string.

Rule: fields not actively consumed → json.RawMessage to tolerate drift (trusting the spec caused cannot unmarshal panics during catalog sync). Prod wins; document the divergence in the DTO comment.

Error kinds (neo_errors.go): 401→auth (handled by the one-shot retry); 403→insufficient-funds (terminal); 406/422→validation (terminal, re-classified to insufficient-funds if the message matches); 429→rate-limit (retry, honors Retry-After); ≥500→server (retry 10s). Two envelope shapes handled permissively ({error} and {errors:[{field:msg}]}).

Tests, mock, docs

Contract (neo_contract_test.go) + chaos (neo_chaos_contract_test.go, incl. the 401 auto-refresh) + orchestration (test/clientapi/neo_orchestration_test.go, incl. Sync_HappyPathDeliversInline_NoPolling and the opt-out-of-link-path policy). Mocky route frontend/mocky-balboa/src/routes/neocurrency.ts deliberately reproduces the spec drift (array-wrapped success, step_size:null). Docs + Postman under docs/neocurrency/; mapping/neo_currency_catalog.tsv.

On this page