diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 9a215d9..3b97e91 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -520,6 +520,22 @@ jobs: exit 1 fi + - name: Probe the /pay/ callback route reaches the gateway + run: | + set -u + # /pay/robokassa/result must reach the gateway, not fall to the landing catch-all. An + # unsigned probe is rejected downstream, so the gateway answers a 4xx/5xx (never a 200 or + # a 404 landing.html), which proves the edge route is wired. + out="$(docker run --rm --network edge alpine:3.20 wget -S -q -O /dev/null http://scrabble/pay/robokassa/result 2>&1 || true)" + echo "$out" | grep -E "HTTP/" || true + if echo "$out" | grep -qE "HTTP/1\.1 (4|5)[0-9][0-9]"; then + echo "ok: /pay/ reaches the gateway (non-landing response)" + else + echo "FAIL: /pay/robokassa/result did not reach the gateway (landing catch-all?)" + docker logs --tail 50 scrabble-gateway || true + exit 1 + fi + - name: Probe the /dict edge route reaches the gateway run: | set -u diff --git a/PLAN.md b/PLAN.md index f182eb9..3817dc0 100644 --- a/PLAN.md +++ b/PLAN.md @@ -34,7 +34,7 @@ status — without re-deriving decisions. | E2 | Currency + benefit core | 1 | DONE | | E3 | Wallet UI | 1 | DONE | | E4 | Durability (PITR) | 2 | DONE | -| E5 | Payment intake | 2 | TODO | +| E5 | Payment intake | 2 | WIP | | E6 | Ads | 2 | TODO | | E7 | Admin & reports | 2 | TODO | | E8 | Guest limits | — | TODO | @@ -504,7 +504,19 @@ maintenance window). Migrations stay expand-contract so image rollback remains D ## E5 — Payment intake -**Status:** TODO · **Release 2** · depends on: E0, E1, E2, E4 · mechanics: PAYMENTS §9, §12. +**Status:** WIP · **Release 2** · depends on: E0, E1, E2, E4 · mechanics: PAYMENTS §9, §12. + +**Delivery & baked decisions.** Shipped as a linear PR stack (owner's choice), Robokassa first. +Resolved: match the order by a Robokassa **`Shp_order`** custom parameter, not the numeric `InvId` +(an order id is a uuid); idempotency key = the order id (`provider_payment_id = order_id`); the НПД +receipt is formed **shop-side in the Robokassa cabinet**, so no `Receipt` parameter is sent; a +chargeback **never drives the balance negative** (D27 stands, `balances_chips_chk` kept), so E5 is +**schema-free** (no migration, no contour wipe). Delivered on `feature/payment-intake-robokassa`: +the offer page (`/offer/`), the order/`fund` engine (idempotent, honours an expired order), the +`internal/robokassa` adapter, the `POST /wallet/order` + internal Result-callback handlers (a D36 +confirmed-email gate on `direct`), the pending reaper, the `wallet.order` edge wire + the public +`/pay/*` routes, the Wallet purchase CTA, and the contour deploy env (IsTest forced). Remaining: +the `payment_events` dispatcher (delivery), then the VK and TG-Stars rails and refunds. **Goal.** Accept real money on all three rails into the payments domain: order-flow, verified provider callbacks, idempotency, the TG bot SQLite outbox, the event dispatcher,