Install
openclaw skills install @jianqiaochen/cloudbase-wxpayThis skill provides battle-tested guidance for integrating WeChat Mini Program payment and refund flows using Tencent CloudBase. It covers cloud.cloudPay.unifiedOrder() for payments, cloud.cloudPay.refund() for refunds, cloud functions, deployment via CLI, and a catalog of critical pitfalls with fix
openclaw skills install @jianqiaochen/cloudbase-wxpay一站式指南:微信支付 + 原路退款 + CloudBase 云开发,含踩坑全记录。
This skill encodes a full payment-refund integration cycle on CloudBase: unified-order payment → order management → server-side refund → historical order backfill. It covers both the happy path and every trap encountered during a real production build, so future integrations skip the debugging marathon.
Trigger this skill when:
returnCode / resultCode confusion-501001 invalid wx openapi access_token during refund callstcb) installed and logged in: npm i -g @cloudbase/cliorder, payment (minimum)Mini Program (wx.cloud.callFunction)
↓
order 云函数 (business logic, status management)
↓
payment 云函数 (wraps cloud.cloudPay.* calls)
↓
CloudBase cloud.cloudPay.unifiedOrder() / refund()
↓
WeChat Pay API
Keep payment calls in a dedicated payment cloud function — never spread
cloud.cloudPay.* across multiple functions. This isolates the cloud-call
context dependency and makes debugging tractable.
In the payment cloud function, the core call:
const result = await cloud.cloudPay.unifiedOrder({
body: '商品描述',
outTradeNo: orderId, // unique order number
totalFee: 1, // integer fen (分), e.g. 1 = 0.01 CNY
spbillCreateIp: '127.0.0.1',
tradeType: 'JSAPI',
envId: 'your-env-id', // HARDCODE, never DYNAMIC_CURRENT_ENV
functionName: 'payment',
subMchId: '', // omit if not sub-merchant mode
});
Critical:
envId does NOT work with cloud.DYNAMIC_CURRENT_ENV — hardcode the
environment ID string.subAppId unless in sub-merchant mode. Case mismatches cause
cryptic API errors.totalFee is in fen (分), not yuan.DevTools auto-upload does not carry environment variables
(WX_MCH_ID, etc.) to the cloud → payment silently falls back to simulated
mode (no real charge).
Use CLI deployment:
tcb fn deploy payment --envId <your-env-id>
tcb fn deploy order --envId <your-env-id>
Verify deployment with:
tcb fn list --envId <your-env-id>
In cloudbaserc.json or CloudBase console, ensure:
{
"env": {
"WX_APPID": "wx...",
"WX_MCH_ID": "1...",
"WX_MCH_KEY": "..."
}
}
The payment cloud function must read these at runtime:
const mchId = process.env.WX_MCH_ID;
if (!mchId) {
// This is the symptom of DevTools deploy — abort with clear error
return { code: -1, message: 'WX_MCH_ID not set — use CLI deployment' };
}
In the payment cloud function, add a refund action:
async function handleRefund(orderId, totalFee, refundFee, outRefundNo) {
const result = await cloud.cloudPay.refund({
subMchId: '', // omit if not sub-merchant
transactionId: orderId,
outTradeNo: orderId,
outRefundNo: outRefundNo || generateOutRefundNo(orderId),
totalFee: totalFee, // original total in fen
refundFee: refundFee, // amount to refund in fen
envId: 'your-env-id', // hardcoded, same as payment
functionName: 'payment',
});
return result;
}
This is the #1 cause of silent refund failures. The refund return
object has two layers, both must be checked:
const refundResult = await cloud.cloudPay.refund({...});
// Layer 1: CloudBase wrapper
if (refundResult.returnCode !== 'SUCCESS') {
return { code: -1, errMsg: refundResult.returnMsg || 'refund wrapper failed' };
}
// Layer 2: WeChat Pay result
if (refundResult.resultCode !== 'SUCCESS') {
return {
code: -1,
errCode: refundResult.errCode,
errMsg: refundResult.errCodeDes || 'refund payment failed',
};
}
// Only now is the refund truly successful
// Store refundTransactionId from result.refundId or result.transactionId
return {
code: 0,
refundTransactionId: refundResult.refundId,
};
Never check only code and assume success — the old code path that caused
refundTransactionId to be empty did exactly this.
The order cloud function should:
payment cloud function's refund actionreturnCode AND resultCode on the returned resultrefundTransactionId and refundedAt when both succeeduser_cancelled — leave as-is
and surface the error// In order cloud function, processRefund action:
const refundRes = await callPaymentCloud('refund', { orderId, totalFee, refundFee });
if (refundRes.returnCode !== 'SUCCESS' || refundRes.resultCode !== 'SUCCESS') {
return { code: -1, errCode: refundRes.errCode, errMsg: refundRes.errCodeDes };
}
// Only now update the order document:
await db.collection('orders').doc(orderId).update({
data: {
status: 'user_cancelled',
refundedAt: new Date(),
refundTransactionId: refundRes.refundId,
},
});
For the full pitfall catalog with debugging commands and fix recipes,
load references/gotchas.md.
Quick reference of the top 5 pitfalls:
| # | Pitfall | Symptom | Fix |
|---|---|---|---|
| 1 | DevTools deploy loses env vars | Payment goes to simulated mode, WX_MCH_ID is empty | Use tcb fn deploy CLI |
| 2 | envId: cloud.DYNAMIC_CURRENT_ENV | unifiedOrder returns unexpected data | Hardcode env ID string |
| 3 | subAppId case mismatch | unifiedOrder returns resultCode: FAIL | Remove subAppId if not using sub-merchant |
| 4 | Refund return not checked properly | refundTransactionId stays empty, order marked "refunded" but money never returned | Double-layer check: returnCode + resultCode |
| 5 | CLI tcb fn invoke for refund | -501001 invalid wx openapi access_token | Cloud calls need mini-program context; use wx.cloud.callFunction from mini program side |
cloud.cloudPay.refund() and cloud.cloudPay.unifiedOrder() are
cloud calls (云调用). They require WeChat-side authentication context.
Only these invocation methods carry valid context:
| Method | Works? | Reason |
|---|---|---|
wx.cloud.callFunction from Mini Program | ✅ Yes | Carries user session + WeChat auth |
| Timer trigger (定时触发器) | ✅ Yes | Platform injects context |
| HTTP API trigger | ✅ Yes | Platform injects context |
tcb fn invoke (CLI) | ❌ No | No mini-program session → access_token failure |
| CloudBase console "Test" button | ❌ No | Same reason as CLI |
Rule: Any cloud call needing WeChat Pay access_token must originate from one of the three "✅ Yes" methods. When testing refunds or troubleshooting historical orders, trigger through the Mini Program UI, not CLI.
Before testing payment in production:
tcb fn deploy (not DevTools)envId hardcoded as string in all cloud.cloudPay.* callsWX_MCH_ID, WX_APPID, WX_MCH_KEY set as environment variablessubAppId removed from unifiedOrder unless sub-merchant modereturnCode + resultCode checkWhen a user reports "order cancelled but money not returned":
Query the order document: Check refundTransactionId — if empty string,
refund never executed.
tcb db query --envId <env-id> -c orders --where '{"_id":"<doc-id>"}'
Check order status: If status is user_cancelled but
refundTransactionId is empty, the old bug is confirmed.
Fix: Add a force_refund action in the payment cloud function that
bypasses status checks, then trigger it from the Mini Program via
wx.cloud.callFunction({ name: 'payment', data: { action: 'force_refund', orderId } }).
Cleanup: After backfilling, remove force_refund and any related UI
buttons — they are temporary fixes, not intended for users.
references/gotchas.md — Full pitfall catalog with debugging commands,
error code reference, and the exact chain of bugs encountered during the
real build.