# Huifu mini-program payment integration ## Implemented server flow 1. The talent application calls `uni.login` and the WeChat phone-number authorization API during sign-in. 2. The server exchanges those short-lived codes with WeChat, verifies the pre-enrolled mobile number, binds the returned `openid`, and issues a talent access session. 3. The talent application creates a payment for a pending benefit order with that access session. 4. The server validates the order, price version, expiry time, owner, session and WeChat `openid`. 5. The server records a payment transaction with an idempotency key and calls Huifu standard API `POST /v3/trade/payment/jspay` with `trade_type=T_MINIAPP`. 6. The server parses Huifu `pay_info`; the client receives its `appId`, `timeStamp`, `nonceStr`, `package`, `signType`, and `paySign` fields and calls `uni.requestPayment`. 7. Huifu posts `resp_data` and `sign` to the callback URL. The server verifies the original `resp_data` with `SHA256WithRSA`. 8. Only a verified successful callback marks the payment transaction and order as paid. Repeated callbacks are idempotent. ## Application endpoints - `POST /api/app/pending-orders/{orderNo}/payment` - Requires `Authorization: Bearer {talentAccessToken}` and requires the session talent to equal the body `talentId`. - Header: `X-Idempotency-Key` (required, 1-128 characters) - Body: `{ "talentId": 1, "priceVersion": 1 }` - Response: `paymentNo`, `paymentStatus`, and provider `paymentParams`. - `GET /api/app/pending-orders/{orderNo}/payment?talentId={id}&paymentNo={paymentNo}` - Returns `NOT_STARTED`, `PAYMENT_PENDING`, `PAYMENT_CREATE_FAILED`, or `PAID`. - `POST /api/payments/huifu/notify` - Huifu callback endpoint. It only accepts `application/x-www-form-urlencoded` data with `resp_data` and `sign`. - A successful processing response is `RECV_ORD_ID_{req_seq_id}`. - `POST /api/app/auth/login` - Body: `{ "wechatCode": "...", "phoneCode": "...", "agreementVersion": "v1", "agreedAt": 0 }`. - Exchanges WeChat codes server-side, binds the authenticated OpenID to the existing talent record matched by mobile number, and returns `talentId`, `accessToken`, `refreshToken`, and `expireAt`. - `POST /api/app/auth/refresh` - Rotates the talent access and refresh tokens. The legacy `/pay` endpoint now rejects calls so a client cannot convert an order to paid state without a verified payment result. ## Deployment configuration Store these values in the deployment secret manager and expose them as environment variables. Do not add keys to `application.yml`. ```text HUIFU_ENABLED=true HUIFU_ENVIRONMENT=test|prod HUIFU_SYSTEM_ID=... # sys_id from Huifu provisioning HUIFU_PRODUCT_ID=... # product_id from Huifu provisioning HUIFU_MERCHANT_ID=... HUIFU_MINI_PROGRAM_APP_ID=... HUIFU_PRIVATE_KEY=... HUIFU_PLATFORM_PUBLIC_KEY=... HUIFU_NOTIFY_URL=https://api.example.com/api/payments/huifu/notify HUIFU_DELAY_ACCOUNT=false # Enable only after delayed-transaction confirmation is implemented. WECHAT_MINI_PROGRAM_APP_ID=... # Usually the same app id used by the Huifu mini-program product. WECHAT_MINI_PROGRAM_APP_SECRET=... # WeChat Mini Program AppSecret, from the WeChat admin console. ``` The callback address must be publicly reachable over HTTPS. Configure the same address with Huifu. Enable the provider only after the Huifu product, merchant, mini-program relationship, signing keys and callback reachability have been verified in the sandbox. ## Manual test procedure 1. In the WeChat Mini Program backend, copy the AppSecret from **Development > Development Management > Development Settings**. Set it as `WECHAT_MINI_PROGRAM_APP_SECRET` on the server process. Do not put it in the client or commit it to source control. 2. Restart the service. The startup migration creates `talent_session`; verify `GET /actuator/health` is `UP`. 3. Compile the talent application in HBuilderX and log in with a WeChat account whose authorized mobile number already exists in `talent_user`. 4. Open **Usage Records > Pending Payment**, choose an unpaid order, and tap payment. The service creates one `payment_transaction` and the app opens the native WeChat payment sheet. 5. Complete payment in the Huifu sandbox. The order must remain pending until the verified callback arrives, then the client status query redirects to payment success. 6. Verify server state without exposing customer data: ```sql select payment_no, status, amount, provider_transaction_no, paid_at from payment_transaction order by id desc; select provider_request_no, signature_valid, process_status, received_at from payment_callback_event order by id desc; ``` Expected successful values: transaction `SUCCEEDED`; callback `signature_valid = 1`, `process_status = PROCESSED`; corresponding `benefit_order.order_status = 1`. ## Reconciliation and exception handling The server records verified callback audit events, then uses the provider query API as a second confirmation path for payments and refunds that remain pending. It never converts an order to paid from a client request or a local timer result alone. Set these values only after sandbox verification. The scheduler is deliberately off by default so a local startup does not call the real provider: ```text HUIFU_RECONCILIATION_ENABLED=true HUIFU_RECONCILIATION_FIXED_DELAY_MS=300000 HUIFU_RECONCILIATION_PENDING_AFTER_MINUTES=5 ``` The scheduler checks up to 100 old pending payments and 100 old pending refunds per run. It writes non-sensitive exceptions to `payment_reconciliation_issue`: pending timeout, provider-declared failure, and query failure. It does not store callback bodies, signatures, OpenID values, card numbers, or keys. Administrators can use **Payment reconciliation** in the management console to inspect open issues, run a one-off check, and mark a confirmed issue as resolved or ignored. Marking a row resolved does not alter payment or refund state; it is an audit action only. ## Deployment and acceptance checklist 1. Rotate the Huifu RSA private key and WeChat AppSecret if either was ever committed, pasted into chat, copied into a log, or otherwise exposed. Put the replacement values only in the deployment secret store as the environment variables listed above. 2. Configure `HUIFU_NOTIFY_URL` as a public HTTPS URL in both deployment configuration and the Huifu console. Confirm that the ingress/WAF permits `POST /api/payments/huifu/notify` with form data and does not rewrite `resp_data` or `sign`. 3. Confirm the Huifu merchant, product, mini-program AppID, callback signing public key, and the WeChat mini-program AppID/AppSecret belong to the same target environment (sandbox or production). 4. In the WeChat console, configure the server request domain and complete the production domain verification. The mini-program must use the same AppID that is configured in Huifu. 5. Run a successful payment and verify `payment_transaction.status = SUCCEEDED`, a callback event with `signature_valid = 1`, and `benefit_order.order_status = 1`. 6. Send a duplicate callback from the provider test facility. The status and paid amount must remain unchanged while the callback audit timestamp updates. 7. Temporarily block or delay the callback in sandbox, wait past the configured threshold, then run reconciliation. The provider query must either settle the local transaction or create an open reconciliation issue. 8. Run a refund, repeat the delayed-callback test, and confirm that only a verified callback or provider query can mark `order_refund.refund_status = 2`. 9. Test invalid callback signatures, provider timeouts, duplicate payment button taps, expired orders, refunded orders, and provider-declared failures. No case may expose payment parameters, OpenID, keys, signatures, or full callback payloads in logs or API responses. ## Remaining provider-dependent work - Delayed-account confirmation must only be enabled after the corresponding Huifu product and confirmation API are provisioned and tested. - Merchant onboarding, bank-card verification, merchant settlement/withdrawal callbacks, platform bill download, and accounting reconciliation still require the exact Huifu product contracts and callback specifications for the merchant account. ## Merchant withdrawal (encashment) Merchant withdrawal uses Huifu `POST /v2/trade/settlement/encashment`, not the mini-program payment product. The implementation creates a withdrawal only after an administrator has recorded both values returned by Huifu provisioning: - the approved merchant's `huifu_id` on the onboarding application; - the approved withdrawal card's `token_no` from Huifu card binding. In the admin settlement page, approving onboarding asks for `huifu_id`; approving a bank card asks for `token_no`. Approving a withdrawal now means **submit to Huifu**, not mark paid locally. The server stores the idempotent withdrawal number as `req_seq_id`, records the provider response, and uses `processing` until the provider confirms success. Configure these deployment secrets before enabling withdrawal: ```text HUIFU_WITHDRAWAL_PRODUCT_ID=... # Product that has encashment permission, may differ from payment product HUIFU_WITHDRAWAL_NOTIFY_URL=https://api.example.com/api/payments/huifu/withdrawal-notify HUIFU_WITHDRAWAL_INTO_ACCOUNT_DATE_TYPE=D0 ``` `HUIFU_WITHDRAWAL_NOTIFY_URL` must be publicly reachable HTTPS and configured with Huifu. The callback verifies `resp_data` with Huifu's public key before updating a withdrawal. The only terminal success is `status=paid`; `processing` never means funds arrived.