Skip to content

Add Paga sandbox NGN accounts and banking adapter - #96

Open
Gift-Stack wants to merge 1 commit into
mainfrom
codex/paga-fiat
Open

Gift-Stack wants to merge 1 commit into
mainfrom
codex/paga-fiat

Conversation

@Gift-Stack

@Gift-Stack Gift-Stack commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Adds a sandbox-only Paga banking adapter for per-customer NGN subsidiary accounts, balance reads, account-to-account transfers, bank recipient validation, and payout submission/status checks. The adapter keeps exact minor-unit amounts, signs requests on the server, matches returned references, and keeps banking separate from currency conversion.

This is the first fiat layer, based on foundation PR #93. The consumer API, database ownership, recovery flow, and mobile screens are in the top integration PR. Paga's latest authenticated probe returned HTTP 401; account creation and funded transfers are not yet verified. This PR does not provide NGN↔USDC conversion or enable production execution.

Validation: adapter contract tests pass as part of the 303-test backend fiat run on the integrated stack. Provider responses in automated tests are controlled doubles, not proof of settlement.

Stack (bottom → top): foundation #93 → Paga #96 → Nomba #97 → consumer integration #98. Native GitHub stack #99. Merge from the bottom upward.

input.recipient.accountNumber,
],
);
this.match(data, input.reference);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once Paga has accepted a money-moving call, a failed check here throws INVALID_RESPONSE, not UNKNOWN_OUTCOME. That covers match(), the currency/name comparison, toMinor() and the body parse at L143. If depositToBank returns responseCode 0 but destinationAccountHolderNameAtBank differs in casing or spacing, or the body read hits the 15s abort, the caller never gets the transactionId and can mark the payout failed and retry or refund, so the recipient gets paid twice. transferSubsidiary (L302-311), charge and topup have the same problem. After the request succeeds, validation failures should become UNKNOWN_OUTCOME so the operation goes to reconciliation.

} catch {
throw new PagaError('INVALID_RESPONSE');
}
if (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every non-zero responseCode becomes REJECTED, including on the read-only /transactionStatus lookup in getPayout. If the status query comes back non-zero (the transaction is still processing, the reference is not indexed yet, or there was a transient error), getPayout throws the same REJECTED that submitPayout throws for a declined deposit. Reconciliation could then release or refund funds for a payout that still settles. getPayout should map a non-zero status to status: 'unknown' or UNKNOWN_OUTCOME.

@Gift-Stack
Gift-Stack marked this pull request as ready for review September 24, 2026 22:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T22:44:51.416307Z 797fac1 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 797fac1adb

ℹ️ 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".

Comment on lines +142 to +145
try {
data = parse(await response.text());
} catch {
throw new PagaError('INVALID_RESPONSE');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify unreadable mutation responses as unknown outcomes

When Paga accepts a payout, transfer, charge, or top-up but the 2xx response body is truncated, unreadable, or malformed, the money may already have moved; this catch instead reports INVALID_RESPONSE, losing the ambiguity signal needed to prevent unsafe retries and trigger reconciliation. Distinguish mutating requests here and return UNKNOWN_OUTCOME when their successful HTTP responses cannot be decoded.

Useful? React with 👍 / 👎.

Comment on lines +160 to +161
const reference = ref.parse(input.reference);
const accountReference = ref.min(12).max(30).parse(input.accountReference);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize schema failures to INVALID_INPUT

For malformed caller input such as an invalid reference, account reference, email, amount, bank code, or account number, these direct Zod parses throw ZodError rather than the adapter's declared PagaError('INVALID_INPUT'); only the self-transfer check currently emits that code. Consumers therefore cannot consistently distinguish a bad request from an unexpected adapter failure, so public-method validation failures should be translated to the typed error.

Useful? React with 👍 / 👎.

@Gift-Stack
Gift-Stack deleted the branch main September 24, 2026 22:50
@Gift-Stack Gift-Stack closed this Sep 24, 2026
@Gift-Stack
Gift-Stack removed this pull request from stack #99 September 24, 2026 22:56
@Gift-Stack Gift-Stack reopened this Sep 24, 2026
@Gift-Stack
Gift-Stack changed the base branch from harden/production-readiness to main September 24, 2026 22:57
@Gift-Stack
Gift-Stack added this pull request to stack #104 September 24, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant