优化提现流程,新增用户确认模式以支持待用户确认的转账,更新相关API和数据库结构以确保数据一致性。同时,调整小程序界面以展示待确认收款信息,提升用户体验。

This commit is contained in:
乘风
2026-02-06 19:45:24 +08:00
parent 2e65d68e1e
commit 8e67eb5d62
19 changed files with 649 additions and 95 deletions

View File

@@ -158,6 +158,100 @@ export async function createTransfer(params: CreateTransferParams): Promise<Crea
}
}
// ========== 用户确认模式:发起转账(需用户在小程序内确认收款)==========
// 文档: https://pay.weixin.qq.com/doc/v3/merchant/4012716434
export interface CreateTransferUserConfirmParams {
openid: string
amountFen: number
outBillNo: string
transferRemark?: string
}
export interface CreateTransferUserConfirmResult {
success: boolean
state?: string
packageInfo?: string
transferBillNo?: string
createTime?: string
errorCode?: string
errorMessage?: string
}
/** 获取转账结果通知地址 */
function getTransferNotifyUrl(): string {
const base = process.env.NEXT_PUBLIC_BASE_URL || process.env.VERCEL_URL || 'http://localhost:3000'
const host = base.startsWith('http') ? base : `https://${base}`
return `${host}/api/payment/wechat/transfer/notify`
}
/**
* 用户确认模式 - 发起转账
* 返回 WAIT_USER_CONFIRM 时需将 package_info 下发给小程序,用户调 wx.requestMerchantTransfer 确认收款
*/
export async function createTransferUserConfirm(
params: CreateTransferUserConfirmParams
): Promise<CreateTransferUserConfirmResult> {
const cfg = getConfig()
if (!cfg.mchId || !cfg.appId || !cfg.certSerialNo) {
return { success: false, errorCode: 'CONFIG_ERROR', errorMessage: '微信转账配置不完整' }
}
const urlPath = '/v3/fund-app/mch-transfer/transfer-bills'
const body = {
appid: cfg.appId,
out_bill_no: params.outBillNo,
transfer_scene_id: '1005',
openid: params.openid,
transfer_amount: params.amountFen,
transfer_remark: params.transferRemark || '提现',
notify_url: getTransferNotifyUrl(),
user_recv_perception: '提现',
transfer_scene_report_infos: [
{ info_type: '岗位类型', info_content: '兼职人员' },
{ info_type: '报酬说明', info_content: '当日兼职费' },
],
}
const bodyStr = JSON.stringify(body)
const timestamp = Math.floor(Date.now() / 1000).toString()
const nonce = generateNonce()
const signature = buildSignature('POST', urlPath, timestamp, nonce, bodyStr)
const authorization = buildAuthorization(timestamp, nonce, signature)
const res = await fetch(`${BASE_URL}${urlPath}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: authorization,
'User-Agent': 'Soul-Withdraw/1.0',
},
body: bodyStr,
})
const data = (await res.json()) as Record<string, unknown>
if (res.ok && res.status >= 200 && res.status < 300) {
const state = (data.state as string) || ''
return {
success: true,
state,
packageInfo: data.package_info as string | undefined,
transferBillNo: data.transfer_bill_no as string | undefined,
createTime: data.create_time as string | undefined,
}
}
return {
success: false,
errorCode: (data.code as string) || 'UNKNOWN',
errorMessage: (data.message as string) || (data.error as string) as string || '请求失败',
}
}
/** 供小程序调起确认收款时使用:获取商户号与 AppID */
export function getTransferMchAndAppId(): { mchId: string; appId: string } {
const cfg = getConfig()
return { mchId: cfg.mchId, appId: cfg.appId }
}
/**
* 解密回调 resourceAEAD_AES_256_GCM
*/