Files
ruoyi-vue-pro/docs/education/migration/issues/EDU-021-legacy-pay-account-import.md

5.7 KiB

EDU-021 — Import legacy tenant payment accounts into native Pay

  • Status: done — bounded single-account import, audit, and native Pay UI entry implemented and verified
  • Type: legacy data bridge / payment security / tenant isolation
  • Phase: 4 / tenant operations
  • Blockers: EDU-020, native Pay App/Channel runtime, PostgreSQL Flyway

Problem

The legacy tenant_payment_accounts and app_private.tenant_secrets records cannot be copied directly into native Pay. Provider aliases, collection modes, channel variants, callback ownership, credential shapes, and status values are not one-to-one. Guessing any of them can route money or callbacks to the wrong party.

The migration also needs durable evidence without creating an Education payment shadow model or persisting a second plaintext credential copy.

Delivered contract

  • The bridge is Pay-owned and creates native pay_app and pay_channel rows through PayAppService and PayChannelService; Education owns neither a payment account nor a credential table.
  • POST /pay/legacy-account-import/import imports exactly one explicitly reviewed manifest. It requires both pay:app:create and pay:channel:create.
  • GET /pay/legacy-account-import/page exposes tenant-filtered audit history and requires both Pay App and Channel query permissions.
  • The existing pay/app/index Vben page adds a 迁移旧支付账号 action and JSON manifest modal. The button uses explicit AND permission visibility, matching the controller.
  • Import request-body logging is disabled for database access logs, non-production request logs, and unexpected-error logs so the manifest does not become a plaintext logging side channel.
  • Only tenant_collect is accepted. platform_collect and service_provider fail closed because their settlement and merchant ownership semantics are not equivalent.
  • Historical WeChat and Alipay aliases normalize to wechat_pay or alipay. Non-equivalent providers such as XPay/Xunhu fail closed.
  • Operators must explicitly select a native channel such as wx_lite, wx_pub, or an Alipay variant. Provider family and channel family must match; the importer never guesses a WeChat client type.
  • WeChat V3 and Alipay public-key configurations map into native Pay configuration objects. Multiple rotating WeChat platform keys require an explicit choice. Alipay accepts only the native production or sandbox official gateway.
  • Old provider callbacks are not reused. The manifest must provide new business order/refund callbacks and may provide a transfer callback.
  • active maps to enabled. disabled and pending map to disabled with an audit note.
  • Within the current target tenant, sourceAccountId is the idempotency key. A replay with the same source SHA-256 returns the existing mapping; a different checksum is rejected rather than overwriting it.

Audit and database contract

V4330 creates tenant-scoped pay_legacy_account_import with source identifiers/checksum, normalized provider/config digest, target App/Channel IDs, mapping notes, operator, and timestamp. It deliberately has no config_public, secret_json, raw config, or secret-value column.

Composite foreign keys (tenant_id,target_app_id) and (tenant_id,target_channel_id) prevent an audit row from pointing across tenants. A target tenant may import the same legacy UUID independently, while duplicate active source IDs inside one tenant are rejected. An existing global audit table without tenant_id causes migration failure and requires explicit disposition.

Credentials still enter native pay_channel.config using Pay's existing configuration storage. EDU-021 prevents an extra audit copy; it does not introduce generic encryption or key rotation for native Pay credentials.

Verification

  • Nine focused importer tests pass for WeChat/Alipay mapping, aliases, disabled-state mapping, replay, checksum conflict, unsupported modes/providers, channel mismatch, unsafe endpoints/rotating keys, and audit redaction. A controller contract verifies dual write permission and request-body logging suppression.
  • The combined Pay selection passes 29 tests, including the prior tenant App/Channel contracts.
  • All 45 PostgreSQL Flyway integration tests pass through V4380. V4330 coverage verifies tenant-independent legacy UUID reuse, no raw credential columns, cross-tenant composite-FK rejection, mode constraints, migration history, and fail-closed global-table adoption.
  • The Vben @vben/web-antd typecheck passes with the import API, modal, and explicit dual-permission button.

Explicitly open

  • Audit history currently has a backend/Vben API contract but no dedicated history table in the account-import modal. EDU-023 provides its own recent transaction-import history table on the native order page.
  • A controlled export job from the legacy database and operator runbook are still required before production bulk migration. The UI template contains placeholders and must never be submitted unchanged.
  • Concurrent first imports of the same source account rely on the database unique constraint and transaction rollback; a friendly concurrent-replay response is not claimed.
  • Platform/service-provider settlement, XPay/Xunhu replacement, generic credential encryption/rotation, and tenant PNVS require separate decisions.
  • EDU-022 activates tenant-scoped native Pay order, refund, and notification ledgers; EDU-023 adds bounded terminal legacy transaction import; EDU-024 activates empty native Transfer/Wallet ledgers. Production bulk tooling and reviewed opening-balance migration remain open.
  • EDU-025 delivers native Mall Product activation. Explicit legacy product import, Promotion/Trade, coupon import/redemption, purchase fulfillment, refunds-to-entitlement revocation, and reconciliation remain separate commercialization work.