5.2 KiB
EDU-023 — Import reconciled terminal legacy Pay transactions
- Status: done — bounded terminal aggregate import, redacted audit, and native Pay order-page UI verified
- Type: legacy data bridge / financial reconciliation / privacy
- Phase: 4 / commercialization foundation
- Blockers: EDU-021, EDU-022, PostgreSQL Flyway
Problem
The legacy orders, payments, payment_events, and commerce_refund_requests rows cannot be copied into native Pay independently. Their statuses, identifiers, and totals form one aggregate; importing a live or inconsistent aggregate could make native jobs, callbacks, or operators charge or refund it again. Raw provider payloads and error bodies also contain data that does not belong in a second audit store.
Delivered contract
POST /pay/legacy-transaction-import/importimports one explicitly reviewed aggregate and requirespay:legacy-transaction:import.GET /pay/legacy-transaction-import/pageexposes tenant-filtered audit history underpay:legacy-transaction:query.- Only terminal order, payment, and refund states are accepted. Amounts are already denominated in cents and must reconcile exactly: one verifiable successful payment at most, payment amount equals order price, successful refund sum equals
refundedPrice, and order/refund status agrees with that sum. - Every aggregate must reference an EDU-021
pay_legacy_account_importfrom the same source tenant. All payment and refund provider families must match that reviewed mapping. - Imported data is written into Pay-owned
pay_order,pay_order_extension, andpay_refund; Education does not gain a financial shadow ledger. - Import does not call a provider SDK, enqueue notification tasks, invoke business callbacks, or copy provider credentials.
raw_payload, event payloads, channel notification bodies, and error originals are excluded. Payment-event evidence is reduced to a count and SHA-256 digest. - A target tenant uses source order UUID as its idempotency key. The same checksum replays the audit result without rewriting native ledgers; a different checksum is rejected. Native merchant order, payment extension, and refund-number collisions fail with a reconciliation error before writes.
- The legacy model has no reliable client IP or channel fee. Native rows therefore use
0.0.0.0and zero fee, and the audit records that limitation. targetUserIdis an explicit optional native Member ID. The importer does not infer a UUID-to-Member mapping and Pay does not depend directly on Member, avoiding a module cycle. Export tooling or the operator owns that reviewed mapping.- Request-body access logging is disabled. The existing native
pay/order/indexpage exposes a permission-aware JSON manifest modal and the most recent 50 audit rows; no raw provider payload is rendered.
Database contract
V4350 adds tenant-scoped pay_legacy_transaction_import, pay_legacy_transaction_payment_import, and pay_legacy_transaction_refund_import. Composite tenant foreign keys bind audit rows to the reviewed account mapping and native App, Channel, Order, Extension, and Refund targets. Existing global tables fail closed. The same legacy source UUID may be imported independently by distinct target tenants.
The schema enforces terminal status sets, non-negative counts/totals, event-count/digest consistency, lowercase SHA-256 shapes, tenant-scoped source uniqueness, and exact expected table shape. None of the three tables contains a raw payload, credential, notification body, or error-original column.
Verification
- Nine focused service/controller tests pass, covering reconciled full-refund import, redaction, replay, checksum conflict, provider mismatch, refund/status mismatch, event digest mismatch, refund-without-payment rejection, native-number collision, permission, and request-log suppression.
- All 45 PostgreSQL Flyway integration tests pass through V4380. V4350 coverage proves composite cross-tenant rejection, per-target-tenant source reuse, event digest consistency, sensitive-column absence, exact menu/permission shape, migration history, and fail-closed global-table adoption.
- Vben lint, formatting, and
@vben/web-antdtypecheck pass for the API, import/history modal, and native order-page integration.
Explicitly open
- A controlled legacy export tool, Member-ID mapping artifact, operator runbook, dry-run report, backup, and production reconciliation sign-off are required before bulk import. The UI template contains placeholders and is not an unattended bulk migrator.
- Concurrent first imports and native-number races still rely on database uniqueness and transaction rollback; a friendlier conflict replay is not claimed.
- Failed/cancelled historical attempts are preserved as closed native extensions/refunds only inside a reconciled terminal aggregate. No live state is resumed.
- EDU-024 activates native Pay Transfer/Wallet with empty tenant-owned ledgers, and EDU-025 activates an empty native Product catalog. Reviewed opening balances, explicit legacy product import, Promotion/Trade, automatic purchase fulfillment, refund-to-entitlement revocation, settlement reconciliation, coupons, commissions, referrals, dunning, generic credential encryption/rotation, tenant PNVS, and production browser/API evidence remain separate slices.