Add Nomba sandbox banking and indicative valuation adapter - #97
Gift-Stack wants to merge 5 commits into
Conversation
| ); | ||
| // Acceptance without an ID cannot be safely retried: requery the durable merchant reference. | ||
| if (!data.id) throw new NombaError('NOMBA_SUBMISSION_UNCERTAIN'); | ||
| return this.operation(data, input); |
There was a problem hiding this comment.
Nomba has already accepted this transfer and returned data.id, but operation() can still throw NOMBA_TRANSACTION_MISMATCH or NOMBA_INVALID_AMOUNT, for example when the synchronous response has no meta.bankCode or meta.accountNumber, or reports the amount or type differently. The caller then loses the provider id that getPayout needs, and an orchestrator that treats anything except NOMBA_SUBMISSION_UNCERTAIN as a definite failure can resubmit. The tests show Nomba creates a new id for the same merchantTxRef, so a resubmit pays the recipient twice. Once data.id is present, validation failures should become NOMBA_SUBMISSION_UNCERTAIN and carry the id.
| } catch { | ||
| throw new NombaError('NOMBA_AUTH_UNAVAILABLE'); | ||
| } | ||
| if (!response.ok) { |
There was a problem hiding this comment.
The token is only cleared when a refresh fails with 401/403 (HTTP or body code). If refresh fails any other way, such as a 400 or a non-00 code for an expired or invalid refresh token, this.token stays set, so every later getAccessToken() retries /refresh with the same dead refresh token and never falls back to /issue. All Nomba calls then fail until the process restarts. Clear this.token on any failed refresh, or fall back to the client_credentials issue flow.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3965a1c10a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (data.accountNumber !== accountNumber) | ||
| throw new NombaError('NOMBA_RECIPIENT_MISMATCH'); |
There was a problem hiding this comment.
Reject bank-code mismatches in recipient lookup
When the lookup response contains the requested account number but echoes a different bankCode, this check accepts it and returns the requested code paired with the other bank's account name. Because ten-digit account numbers are scoped to a bank rather than globally unique, this can display the wrong recipient identity before payout; validate the response bank code alongside the account number.
Useful? React with 👍 / 👎.
| if (!response.ok) | ||
| throw new NombaError( | ||
| response.status >= 500 && body !== undefined | ||
| ? 'NOMBA_SUBMISSION_UNCERTAIN' | ||
| : 'NOMBA_REQUEST_REJECTED', |
There was a problem hiding this comment.
Classify failures by operation instead of request body
When a read such as getPayout, getTransaction, or retrieveAccount receives an HTTP 5xx, this condition reports NOMBA_REQUEST_REJECTED rather than the retryable NOMBA_UNAVAILABLE; conversely, POST-based reads such as recipient lookup and quote conversion are labeled NOMBA_SUBMISSION_UNCERTAIN. Inferring mutation semantics solely from whether a body exists gives callers the wrong recovery behavior during provider outages, so the request helper needs an explicit operation/read-write classification.
Useful? React with 👍 / 👎.
| if (!response.ok) { | ||
| if (response.status === 401 || response.status === 403) | ||
| this.token = undefined; |
There was a problem hiding this comment.
Recover after a refresh token is rejected
If the refresh endpoint reports an expired or revoked refresh token as HTTP 400, this branch throws NOMBA_AUTH_REJECTED but retains the cached token. Every later getAccessToken() therefore selects /refresh again with the same unusable refresh token and never falls back to /issue, leaving authentication wedged until the process restarts; clear the cached token on definitive refresh rejection rather than only on 401/403.
Useful? React with 👍 / 👎.
Adds the Nomba sandbox banking adapter with server-side OAuth refresh, account provisioning and recovery, recipient validation, payout submission/requery, and indicative NGN/USD valuation. Account recovery requires the exact saved reference, configured parent, active status, NGN currency and bank coordinates. The bank directory uses the documented
/v1/transfers/banksroute.Authenticated testing created a per-user virtual account and successfully read it back, while a never-created account returned 404. A single ₦100 sandbox payout was accepted as pending, but its requery returned different destination details. Never-submitted transaction references also returned SUCCESS. The adapter rejects mismatched evidence and continues to classify sandbox results as fixtures; this does not enable customer settlement or NGN↔USDC execution.
Adds a normalized authenticated transaction observation reader for signed webhook identities. It uses exact minor units and rejects mismatched IDs or malformed responses; an observation is not settlement evidence.
Validation: 74 Nomba authentication, adapter, transaction-observation and valuation tests passed on this layer. The integrated consumer flow, webhook receiver and database migration are in #98. Nomba is now the selected test provider; Paga remains an interchangeable adapter.
Stack (bottom → top): foundation #93 → Paga #96 → Nomba #97 → consumer integration #98. Native GitHub stack #99. Merge from the bottom upward.