# nigig-pay-platform The seam between the payment domain and the device. `nigig-pay-domain` decides what a payment *means*. `nigig-pay-storage` decides how it is *remembered*. Neither may know that the provider is driven by an Android AccessibilityService typing digits into a USSD menu. This crate is where that knowledge is allowed to live, and it is the only crate in the payment stack permitted to name a platform SDK. Design rationale and the unresolved AccessibilityService policy question are in [`REVIEWS/adr/0007-payment-platform-boundary.md`](../../REVIEWS/adr/0007-payment-platform-boundary.md). ## What it owns | Module | Job | Review items | |---|---|---| | `correlation` | Single-flight session registry. Refuses out-of-order, duplicate and foreign callbacks. | 2.7, 5.3, 8.2 | | `classify` | Fail-closed error classification driven by dispatch progress. | 2.8, 5.3, B3 | | `events` | Typed platform events that cannot exist without a correlation id. | 5.3 | | `sms` | Strict M-Pesa reader. Emits untrusted evidence, never settlement. | D1, D2, 0.4, 7.5 | | `mock` | Test-only gateway that cannot compile into a release build. | 5.4, 0.5 | | `web` | Honest refusal instead of a fake success path. | 5.6, U3 | | `ussd` | Android adapter. The only module that names `robius_ussd`. | 5.1, 5.5 | ## The rule that matters most When a platform call fails there is one question: **did the provider see it?** ```rust classify_failure(&failure, progress) ``` `DispatchProgress` is the authority, not the error kind. The same `TemporarilyUnavailable` is safely retryable before the dial and *ambiguous* once the menu is being driven, because a session that died after the transfer was submitted may have moved money. Adapters must round progress **up** when unsure. Over-reporting costs a reconciliation entry. Under-reporting costs a duplicate payment. ## Features | Feature | Default | Purpose | |---|---|---| | `ussd` | off | The real Android USSD adapter. Needs `robius-ussd`. | | `mock` | off | Test doubles. **Refuses to compile in a release build.** | | `allow-mock-in-release` | off | Deliberate escape hatch for an integration harness. Named explicitly so nobody enables it by accident. | ## Testing ```bash TEST_TARGET=platform ./tools/test-rust-clean.sh ``` The isolated runner installs the toolchain from `rust-toolchain.toml`, runs `--locked` tests with and without the `ussd` feature, gates on `clippy -D warnings`, asserts the mock gateway cannot be built into a release, and deletes its temporary environment on any exit. ## Invariants worth preserving - `#![forbid(unsafe_code)]` — the JNI surface stays in `robius-ussd`. - `UssdGateway` is `!Send`/`!Sync`, so the main-thread requirement is a compile error rather than a comment. - The adapter never populates a PIN, and a test asserts it. - Nothing here returns a settled `PaymentOutcome` from scraped or SMS input. A confirmation may only come from an authorised provider channel via `PaymentCoordinator::record_provider_outcome`.