RC8 Sandbox Payment Adapters and Reconciliation¶
Governing issue: #261
Branch: integration/rc8-sandbox-payments
Replayed base: main@54d0129027b7f324272c4bcc94a0f2109318fd18
State: CLOSED — ACTIVE SYNTHETIC-ONLY MANAGED CANARY
Source replay receipt¶
RC7 closed through PR #487, and RC8 became the sole bounded repository lane through PR #488. The original PR #454 source checkpoint was replayed losslessly as one commit over main@54d0129027b7f324272c4bcc94a0f2109318fd18: all 16 net RC8 files were preserved, while stale pre-RC7 history was removed.
The source checkpoint was promoted through PR #454 at 6098b71f89d62fa059de298be11a8d9d8539c25e. It remains fail-closed in the application: no provider is registered as runtime-enabled, no API service secret binding, controller route, webhook endpoint or database executor was introduced. Managed proof is isolated to one private temporary Cloud Run Job.
Approved RC8 scope¶
RC8 is limited to sandbox adapters and reconciliation for DIREKT-owned provider commercial charges:
- provider subscriptions;
- verification-processing fees;
- renewal or re-verification fees;
- invoice and receipt state;
- refunds or accounting adjustments where the provider supports them;
- settlement and reconciliation evidence.
RC8 does not authorize:
- customer-to-provider service payments;
- escrow or marketplace payouts;
- stored value or a DIREKT wallet;
- real participant payment data;
- real money movement;
- production payment credentials or production provider endpoints;
- payment influence over verification, publication, trust or ranking.
Provider programme¶
| Provider | RC8 state | Source treatment |
|---|---|---|
| MTN MoMo Collections | Managed sandbox proven | Exact-main run 30241092949/1 proved Request to Pay plus independent success; application runtime remains disabled. |
| Airtel Money Zambia Cash-In | Provider pending | Registered fail-closed with no usable credential metadata. |
| DPO Pay by Network | Sandbox proven | Source adapter implemented and source-tested for createToken, hosted checkout and independent verifyToken; runtime remains unbound. |
| Stripe Checkout | Managed sandbox proven | Exact-main run 30241092949/1 proved unpaid Checkout creation plus independent requires_action retrieval; application runtime remains disabled. |
| PayPal Orders | Managed sandbox proven | Exact-main run 30241092949/1 proved unapproved order creation plus independent requires_action retrieval without capture; application runtime remains disabled. |
| Flutterwave | Deferred/blocked | Excluded from the executable RC8 provider catalogue. |
RC8A source foundation — green¶
The first bounded slice establishes:
- an explicit provider-neutral sandbox port;
- one immutable provider catalogue with capability and readiness metadata;
- exact allowed business flows;
- Secret Manager-only credential sourcing;
- synthetic-only, non-production and no-participant-data execution rules;
- real-money and customer-to-provider payment prohibitions;
- duplicate-registration rejection;
- provider-pending rejection for Airtel;
- runtime-disabled adapters for every registered provider;
- a permanent parser/static contract and unit regressions.
RC8A performs no network request and binds no provider credential. Exact-head Backend CI passed formatting, lint, typecheck, authorization checks, migrations, tests, build and OpenAPI together with the applicable container, integration, PWA, supply-chain, recovery, staging and Phase 11 regressions.
RC8B — MTN MoMo Collections source adapter — green¶
The MTN source adapter models the proven asynchronous Collections path while remaining absent from the application registry/runtime binding:
- OAuth through injected server-only sandbox credentials;
- the exact MTN sandbox host and sandbox target environment;
- Request to Pay using the DIREKT payment-intent UUID as
X-Reference-Id; - credential-free HTTPS callback configuration;
- HTTP
202 Acceptedmapped only toprocessing, never payment success; - independent status retrieval before success can be recorded;
- exact amount and currency comparison against the DIREKT intent;
- provider financial-transaction identifier required for a successful result;
- synthetic mobile-money account enforcement before any provider request;
- bounded external references, request timeout and retryable/non-retryable error separation;
- raw provider payloads and credentials excluded from returned evidence.
RC8B passed formatting, lint, typecheck, authorization, migrations, unit tests, build and OpenAPI. It has no environment/config registration, Secret Manager runtime binding or managed provider execution. The existing Phase 9 synthetic adapter remains the only executable payment-provider path.
RC8C — hosted checkout providers — green¶
Stripe Checkout¶
- an account-scoped
sk_test_credential is injected server-side; - the Stripe API version is explicit rather than silently drifting;
- Checkout Session creation uses the DIREKT payment-intent UUID as the idempotency key;
- the provider reference and bounded DIREKT intent reference are preserved in Checkout metadata;
- only the reviewed hosted-checkout business flows and non-ZMW currency path are accepted;
- only an HTTPS
checkout.stripe.comtest Checkout URL may be returned to a client; - browser success/cancel redirects do not create payment truth;
- the backend retrieves the Checkout Session independently;
- only
status=completewithpayment_status=paid, an exact amount/currency match and a valid PaymentIntent id maps tosucceeded; - open/unpaid sessions remain
requires_action, expired sessions becomecancelled, and malformed or mismatched facts fail closed.
PayPal Orders¶
- OAuth uses injected server-only sandbox client credentials and the exact PayPal sandbox API host;
- order creation uses the DIREKT payment-intent UUID as
PayPal-Request-Id; - only HTTPS approval links on
www.sandbox.paypal.commay be returned; - payer approval is not payment success;
- server capture is idempotent but returns only
processingwithindependentlyVerified=false; - only a later independent GET of the order may produce
succeeded; - the retrieved order must match the exact amount and currency and contain a valid capture id;
- refund and partial-refund states remain successful historical payments with explicit adjustment facts rather than rewriting the original transaction.
DPO Pay by Network¶
- token creation uses the reviewed API v6 XML endpoint;
- only the approved DPO hosted-payment URL is returned;
CompanyRefUniqueprevents reference reuse;- ZMW amount, currency, service type and payment-time limit are bounded;
- the browser return does not create payment truth;
- only independent
verifyTokenfacts can producesucceeded; - final amount, currency, approval, completion and provider transaction reference must all match;
- DPO result codes and HTTP failures are sanitized into retryable or rejected states.
Stripe, PayPal and DPO passed their source adapter unit regressions together with the complete applicable repository matrix. None has environment/config registration, a Secret Manager runtime binding, a provider webhook endpoint or managed sandbox execution.
RC8D — provider-neutral reconciliation and immutable adjustments — green¶
RC8D adds a pure provider-neutral reconciliation planner above the provider adapters and below the existing Phase 9 commercial state machines.
Immutable provider observations¶
Every normalized provider result becomes a bounded append-only observation containing only:
- provider key and provider reference;
- provider transaction identifier when present;
- normalized status;
- amount and currency;
- refund or partial-refund status and explicit adjustment amount when available;
- independent-verification flag;
- occurrence time, policy version and a SHA-256 observation fingerprint.
Credentials and raw provider payloads are never included. Identical provider facts produce the same fingerprint and are treated as duplicates without another payment event, ledger posting, reconciliation case or adjustment request.
Exact comparison boundary¶
The planner compares the immutable provider observation with:
- provider key and reference on the DIREKT payment intent;
- payment-intent provider, invoice, revision, status, amount and currency;
- invoice provider scope, status, amount and currency;
- append-only received, reversed, refunded and adjustment ledger totals.
A successful provider result can create a planned payment transition and one balanced payment_received posting only when the provider result is independently verified, the invoice is open, all references match, amount/currency match exactly and a provider transaction identifier exists.
The planner never silently repairs a mismatch. It opens a system-owned reconciliation plan for provider-reference, provider-key, verification, scope, amount, currency, invoice-state, terminal-status or ledger-net disagreement.
Immutable adjustment handling¶
A provider refund does not rewrite the original successful payment, invoice or historical ledger rows.
- a full refund uses the full independently verified provider amount;
- a partial refund requires an explicit bounded adjustment amount;
- invalid, missing or conflicting adjustment amounts open reconciliation cases;
- a new valid refund creates a
synthetic_refundadjustment request and a linked reconciliation-review plan; - no ledger mutation occurs at observation time;
- the request requires two independent approvers;
- the requester may not approve their own request;
- an already-posted matching refund is recognized without a duplicate adjustment;
- conflicting existing refund totals open a ledger reconciliation case.
Application of an approved adjustment remains governed by the existing Phase 9 database state machines and balanced append-only ledger functions.
Operations-only resolution¶
Reconciliation resolution plans require:
- an operations actor;
commercial.reconciliation.manageauthority;- the exact expected case revision;
- an allowed
open → investigating/resolved → closedtransition; - a bounded reason code, explanation and policy version.
Resolution produces an append-only event plan and never mutates the supplied case snapshot.
RC8D source state¶
The exact RC8D head passed repository formatting, lint, strict typechecking, route-authorization checks, migrations, all backend tests, build and OpenAPI, together with the RC8 contract, container, runtime audit, PWA, supply-chain, recovery, staging, documentation and Phase 11 gates.
RC8D still has no controller route, provider runtime binding, database executor or managed provider transaction. The permanent RC8 contract prevents removal of observation fingerprinting, mismatch queues, immutable history flags, two-person adjustment approval and operations-only reconciliation.
Managed runtime proof — closed¶
The reviewed runtime slice is source-controlled on feat/rc8-managed-sandbox-proof from 6098b71f89d62fa059de298be11a8d9d8539c25e. It:
- validates existing Secret Manager containers and pinned numeric versions without reading values through GitHub CI;
- requires secret-scoped
roles/secretmanager.secretAccessoronly fordirekt-api-runtime; - builds an immutable exact-main backend image;
- creates one private, synthetic-only Cloud Run Job with zero retries and bounded timeout;
- activates cloned descriptors only inside the canary process while
PAYMENT_PROVIDER_MODE=disabledremains true for the application; - proves MTN Request to Pay plus independent successful status;
- proves an unpaid Stripe Checkout and unapproved PayPal order remain
requires_action; - proves append-only success planning, balanced posting, duplicate suppression, mismatch review and immutable two-person refund adjustment planning;
- emits only sanitized receipts and deletes the temporary job on every outcome.
DPO remains runtime-unbound because no DIREKT private sandbox credential exists. Airtel remains provider-pending. Flutterwave remains deferred/excluded. No PayPal capture, browser approval, real money, participant data, production endpoint, customer-to-provider payment, escrow, wallet, payout or direct ledger mutation is authorized.
Preserved exact-main managed attempt 1¶
Exact-main run 30238926656/1 authenticated through WIF but failed before image build or provider mutation because the GitHub deployer lacked secret-container metadata access. Artifact 8642560395 (sha256:d64d9d1fc1934448a00c29ee6924ee34442d92a114ebdf2bb46bfb918404912e) preserves the sanitized failure receipt. No temporary provider transaction occurred; cleanup succeeded; real money, participant data, production authorization and customer-to-provider payments remained false.
Preserved exact-main managed attempt 2¶
Exact-main run 30238926656/2 reached the private Cloud Run Job after the owner least-privilege secret bootstrap. It failed before Stripe, PayPal or reconciliation because MTN returned HTTP 500 during Request to Pay. Artifact 8642921752 (sha256:f78da1c133b7d7dfa0e8397657052bc178250dbe7322c2e5a5404234ba9e80d6) preserves the sanitized failure receipt. The temporary job was deleted; cleanup succeeded; real money, participant data, production authorization and customer-to-provider payments remained false.
The correction removes the artificial callback-host dependency from this polling-only canary and uses the exact previously successful MTN sandbox payer 46733123470. The reusable adapter still includes X-Callback-Url when a reviewed matching callback URL is configured, while the managed proof relies on independent status polling as the payment-truth boundary.
Terminal managed closure receipt¶
Exact source ccc4e9463d810ddf554182b1607c22d3a7c8c8d3 passed run 30241092949/1. Artifact 8643323319 (sha256:bbb4600eb5a062552947e91c878dd09c6d1e4dc307ae4783c7fa1fb4cf6e4935) proves:
- least-privilege numeric Secret Manager versions and no deployer secret-value reads;
- MTN Request to Pay followed by independent authoritative
succeededstatus with transaction id, amount and currency agreement; - Stripe unpaid Checkout independently remained
requires_action, with browser redirect state non-authoritative; - PayPal unapproved order independently remained
requires_action, with no capture and browser approval non-authoritative; - append-only transition planning, balanced ledger posting, immutable observation deduplication, mismatch case creation and two-independent-approver adjustment planning;
- no requester self-approval, direct ledger mutation, historical payment/ledger rewrite or trust/ranking mutation;
- DPO, Airtel and Flutterwave runtime binding false;
- provider credentials/raw payloads absent from evidence;
- temporary Cloud Run Job deleted and
cleanup_failed=false; managed_result=PASS, while production authorization, participant data, real money and customer-to-provider payments remained false.
The one-shot trigger is consumed and automatic main-push execution is removed. RC8 closes without registering a payment provider in the application runtime.
Merge and runtime gates¶
The source checkpoint is merged. The managed proof may execute only after the runtime-source exact head passes all applicable repository regressions and the following controls remain enforced:
- explicit sandbox target environment;
- source-controlled provider allowlist;
- exact Secret Manager containers and least-privilege runtime binding;
- timeouts, bounded retries and idempotency;
- callback/webhook authenticity or independent status verification;
- sanitized receipts and no credential/raw-payload leakage;
- real money and production authorization remain false;
- Android and browser clients cannot declare payment success.