feat: WeChat Pay V3 integration (fill credentials to activate)

Server:
- New lib/wxpay.js: native crypto RSA-SHA256 signing, JSAPI prepay,
  AES-256-GCM notify decryption, order query (no npm deps)
- New dao/payment-order.dao.js: createOrder, markPrepay,
  markPaidAndActivateSubscription (idempotent + transactional)
- New routes/payment.js: GET order status, POST order sync
- New routes/payment-notify.js: WeChat async callback handler with
  signature verification, amount/appid/mchid validation
- Modified subscription/purchase: auto-detects wxpay config, returns
  real payment_params or mock fallback
- Schema: payment_orders table with out_trade_no unique key
- app.js: express.raw() for notify path, payment routes mounted
- config.js: wxpay block with 7 env vars
- .env.example: all WeChat Pay fields documented
- .gitignore: certs/, *.pem, *.p12

Miniprogram:
- subscribe-plans doPurchase: calls real purchase API, falls back to
  mockPurchase only when server returns mock:true
- Added syncAndRedirect for post-payment order confirmation
- api.js: getPaymentOrder, syncPaymentOrder
- Removed "模拟支付"/"测试环境" from UI text
这个提交包含在:
Guoguo
2026-05-18 03:07:59 -07:00
父节点 92416261ee
当前提交 78ea1a03c5
修改 12 个文件,包含 449 行新增13 行删除
+3
查看文件
@@ -29,6 +29,7 @@ const uploadLimiter = rateLimit({
message: { code: 2001, message: 'too_many_attempts' }
})
app.use('/api/v1/payment/wechat/notify', express.raw({ type: 'application/json' }))
app.use(express.json())
app.use((req, res, next) => {
const origin = config.nodeEnv === 'production'
@@ -57,6 +58,8 @@ app.use('/api/v1', require('./routes/subscription'))
app.use('/api/v1', require('./routes/treatment'))
app.use('/api/v1/admin', require('./routes/admin'))
app.use('/api/v1', require('./routes/firmware'))
app.use('/api/v1', require('./routes/payment'))
app.post('/api/v1/payment/wechat/notify', require('./routes/payment-notify'))
app.use((req, res) => res.status(404).json(fail(404, 'not_found')))