forked from gxwebsoft/mp-10550
feat(withdraw): 实现微信商家转账收款确认功能
- 配置文件中更新测试环境API基础URL - 添加ShopDealerWithdrawCreateResult类型定义以支持微信转账返回的package_info - 修改addShopDealerWithdraw函数以处理微信转账流程的特殊返回值 - 实现extractPackageInfo、canRequestMerchantTransferConfirm和requestMerchantTransferConfirm辅助函数 - 在微信钱包提现流程中集成商户转账确认页面调用 - 添加对微信小程序环境的检测和错误处理 - 更新快速金额选项,增加1元选项 - 修改微信提现提示文字,说明需要确认收款的流程
This commit is contained in:
@@ -15,7 +15,7 @@ export const ENV_CONFIG = {
|
|||||||
},
|
},
|
||||||
// 测试环境
|
// 测试环境
|
||||||
test: {
|
test: {
|
||||||
API_BASE_URL: 'https://mp-api.s209.websoft.top/api',
|
API_BASE_URL: 'https://mp-api.websoft.top/api',
|
||||||
APP_NAME: '测试环境',
|
APP_NAME: '测试环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ import request from '@/utils/request';
|
|||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from './model';
|
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from './model';
|
||||||
|
|
||||||
|
// WeChat transfer v3: backend may return `package_info` for MiniProgram to open the
|
||||||
|
// "confirm receipt" page via `wx.requestMerchantTransfer`.
|
||||||
|
export type ShopDealerWithdrawCreateResult =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
package_info?: string;
|
||||||
|
packageInfo?: string;
|
||||||
|
[k: string]: any;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询分销商提现明细表
|
* 分页查询分销商提现明细表
|
||||||
*/
|
*/
|
||||||
@@ -33,13 +45,14 @@ export async function listShopDealerWithdraw(params?: ShopDealerWithdrawParam) {
|
|||||||
/**
|
/**
|
||||||
* 添加分销商提现明细表
|
* 添加分销商提现明细表
|
||||||
*/
|
*/
|
||||||
export async function addShopDealerWithdraw(data: ShopDealerWithdraw) {
|
export async function addShopDealerWithdraw(data: ShopDealerWithdraw): Promise<ShopDealerWithdrawCreateResult> {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await request.post<ApiResult<any>>(
|
||||||
'/shop/shop-dealer-withdraw',
|
'/shop/shop-dealer-withdraw',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.message;
|
// Some backends return `message`, while WeChat transfer flow returns `data.package_info`.
|
||||||
|
return res.data ?? res.message;
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,62 @@ interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
|
|||||||
accountDisplay?: string
|
accountDisplay?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractPackageInfo = (result: unknown): string | null => {
|
||||||
|
if (!result || typeof result !== 'object') return null
|
||||||
|
const r = result as any
|
||||||
|
return (
|
||||||
|
r.package_info ??
|
||||||
|
r.packageInfo ??
|
||||||
|
r.package ??
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canRequestMerchantTransferConfirm = (): boolean => {
|
||||||
|
try {
|
||||||
|
if (typeof (Taro as any).getEnv === 'function' && (Taro as any).ENV_TYPE) {
|
||||||
|
const env = (Taro as any).getEnv()
|
||||||
|
if (env !== (Taro as any).ENV_TYPE.WEAPP) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const api =
|
||||||
|
(globalThis as any).wx?.requestMerchantTransfer ||
|
||||||
|
(Taro as any).requestMerchantTransfer
|
||||||
|
|
||||||
|
return typeof api === 'function'
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestMerchantTransferConfirm = (packageInfo: string): Promise<any> => {
|
||||||
|
if (!canRequestMerchantTransferConfirm()) {
|
||||||
|
return Promise.reject(new Error('请在微信小程序内完成收款确认'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend may wrap/format base64 with newlines; WeChat API requires a clean string.
|
||||||
|
const cleanPackageInfo = String(packageInfo).replace(/\s+/g, '')
|
||||||
|
|
||||||
|
const api =
|
||||||
|
(globalThis as any).wx?.requestMerchantTransfer ||
|
||||||
|
(Taro as any).requestMerchantTransfer
|
||||||
|
|
||||||
|
if (typeof api !== 'function') {
|
||||||
|
return Promise.reject(new Error('当前环境不支持商家转账收款确认(缺少 requestMerchantTransfer)'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
api({
|
||||||
|
// WeChat API uses `package`, backend returns `package_info`.
|
||||||
|
package: cleanPackageInfo,
|
||||||
|
mchId: '1737910695',
|
||||||
|
appId: 'wxad831ba00ad6a026',
|
||||||
|
success: (res: any) => resolve(res),
|
||||||
|
fail: (err: any) => reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Some backends may return money fields as number; keep internal usage always as string.
|
// Some backends may return money fields as number; keep internal usage always as string.
|
||||||
const normalizeMoneyString = (money: unknown) => {
|
const normalizeMoneyString = (money: unknown) => {
|
||||||
if (money === null || money === undefined || money === '') return '0.00'
|
if (money === null || money === undefined || money === '') return '0.00'
|
||||||
@@ -237,12 +293,41 @@ const DealerWithdraw: React.FC = () => {
|
|||||||
withdrawData.bankName = values.bankName || '银行卡'
|
withdrawData.bankName = values.bankName || '银行卡'
|
||||||
}
|
}
|
||||||
|
|
||||||
await addShopDealerWithdraw(withdrawData)
|
// WeChat wallet: backend should return `package_info`, frontend opens the "confirm receipt" page
|
||||||
|
// for user to click "确认收款".
|
||||||
|
if (values.accountType === 'wechat') {
|
||||||
|
if (!canRequestMerchantTransferConfirm()) {
|
||||||
|
throw new Error('当前环境不支持微信收款确认,请在微信小程序内操作')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResult = await addShopDealerWithdraw(withdrawData)
|
||||||
|
const packageInfo = extractPackageInfo(createResult)
|
||||||
|
if (!packageInfo) {
|
||||||
|
throw new Error('后台未返回 package_info,无法调起微信收款确认页')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestMerchantTransferConfirm(packageInfo)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '已调起收款确认页',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = String(e?.errMsg || e?.message || '')
|
||||||
|
if (/cancel/i.test(msg)) {
|
||||||
|
Taro.showToast({title: '已取消收款确认', icon: 'none'})
|
||||||
|
} else {
|
||||||
|
// Keep the original WeChat error for troubleshooting (e.g. "商户号错误").
|
||||||
|
throw new Error(msg || '调起收款确认页失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await addShopDealerWithdraw(withdrawData)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '提现申请已提交',
|
title: '提现申请已提交',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
formRef.current?.resetFields()
|
formRef.current?.resetFields()
|
||||||
@@ -265,7 +350,7 @@ const DealerWithdraw: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const quickAmounts = ['100', '300', '500', '1000']
|
const quickAmounts = ['1','100', '300', '500', '1000']
|
||||||
|
|
||||||
const setQuickAmount = (amount: string) => {
|
const setQuickAmount = (amount: string) => {
|
||||||
formRef.current?.setFieldsValue({amount})
|
formRef.current?.setFieldsValue({amount})
|
||||||
@@ -411,7 +496,7 @@ const DealerWithdraw: React.FC = () => {
|
|||||||
{selectedAccount === 'wechat' && (
|
{selectedAccount === 'wechat' && (
|
||||||
<View className="px-4 py-2">
|
<View className="px-4 py-2">
|
||||||
<Text className="text-sm text-gray-500">
|
<Text className="text-sm text-gray-500">
|
||||||
微信钱包提现将直接转入您的微信零钱
|
提交后将拉起微信收款确认页,需要您点击“确认收款”后才会完成转账
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user