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.

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:
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:

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.