企业付款到零钱问题
baofeng15 opened this issue · 10 comments
用V2的企业付款到零钱功能,会出现偶尔重复付款多次的情况,可能是异步方式,多次请求了
口说无凭,你代码怎么写的?
按照例子写的,不知道是异步问题还是咋的,偶尔会出现重复付款问题
贴一下你的代码吧,show me the code,不然也没法看到具体是什么问题。注意隐藏掉敏感信息。
//企业付款到零钱
private function wechatpay_transfers($user_id, $money, $desc, $is_check_name = true) {
exit();
//ini_set("display_errors", "On");//打开错误提示
//ini_set("error_reporting", E_ALL);//显示所有错误
//ini_set("display_errors", "Off");
ini_set("error_reporting", E_ALL & ~E_USER_DEPRECATED);//显示所有错误,用户生成的弃用警告除外
$user = $this->db->select('id,real_name,openid')->from('user')->where(array('id' => $user_id))->get()->row_array();
if (!empty($user)) {
// 商户号,假定为1000100
$merchantId = config_item('pay_transfers_mchid');
// APIv2密钥(32字节) 假定为exposed_your_key_here_have_risks
,使用请替换为实际值
$apiv2Key = config_item('pay_transfers_key');
// 商户私钥,文件路径假定为 /path/to/merchant/apiclient_key.pem
$merchantPrivateKeyFilePath = FCPATH . 'zhengshu/apiclient_key.pem';
// 商户证书,文件路径假定为 /path/to/merchant/apiclient_cert.pem
$merchantCertificateFilePath = FCPATH . 'zhengshu/apiclient_cert.pem';
// 工厂方法构造一个实例
$instance = WeChatPay\Builder::factory([
'mchid' => $merchantId,
'serial' => 'nop',
'privateKey' => 'any',
'certs' => ['any' => null],
'secret' => $apiv2Key,
'merchant' => [
'cert' => $merchantCertificateFilePath,
'key' => $merchantPrivateKeyFilePath,
],
]);
$order_no = date('YmdHis') . str_pad(mt_rand(10, 999999), 6, '0', STR_PAD_BOTH);
//企业付款到零钱,https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
$res = $instance
->v2->mmpaymkttransfers->promotion->transfers
->postAsync([
'xml' => [
'mch_appid' => config_item('pay_transfers_app_id'),
'mchid' => config_item('pay_transfers_mchid'),// 注意这个商户号,key是`mchid`非`mch_id`
'partner_trade_no' => $order_no,
'openid' => $user['openid'],
'check_name' => $is_check_name ? 'FORCE_CHECK' : 'NO_CHECK',
're_user_name' => $user['real_name'],
'amount' => strval($money * 100),
'desc' => $desc
],
'security' => true, //请求需要双向证书
//'debug' => true //开启调试模式
])
->then(static function($response) {
//print_r($response);
return WeChatPay\Transformer::toArray((string)$response->getBody());
})
->otherwise(static function($e) {
//print_r($e);
// 更多`$e`异常类型判断是必须的,这里仅列出一种可能情况,请根据实际对接过程调整并增加
//if ($e instanceof \GuzzleHttp\Promise\RejectionException) {
// return WeChatPay\Transformer::toArray((string)$e->getReason()->getBody());
//}
//return [];
//echo $e->getBody();
//echo $e->getBody()->__toString();
//echo $e->getBody()->getContents();
return WeChatPay\Transformer::toArray((string)$e->getBody());
})
->wait();
//print_r($res);
}
return $res;
}
从代码上看,特殊接口(无返回值验签)可以仿测试用例代码
改进如下:
wechatpay-php/tests/OpenAPI/V2/Mmpaymkttransfers/Promotion/TransfersTest.php
Lines 135 to 140 in d384eba
$stack = $instance->getDriver()->select(\WeChatPay\ClientDecoratorInterface::XML_BASED)->getConfig('handler');
$stack = clone $stack;
$stack->remove('transform_response');
$res = @$instance
->v2->mmpaymkttransfers->promotion->transfers
->postAsync([
'xml' => [
'mch_appid' => config_item('pay_transfers_app_id'),
'mchid' => config_item('pay_transfers_mchid'),
'partner_trade_no' => $order_no,
'openid' => $user['openid'],
'check_name' => $is_check_name ? 'FORCE_CHECK' : 'NO_CHECK',
'amount' => strval((int)($money * 100)),
'desc' => $desc
] + ($is_check_name ? [
're_user_name' => $user['real_name'],
] : []),
'security' => true,
'handler' => $stack,
])
->then(static function($response) {
return \WeChatPay\Transformer::toArray((string)$response->getBody());
})
->wait();
如果怀疑是异步被重试(理论上即使被重试,partner_trade_no
未被重置也应是幂等的,没道理重复付款),可以调整代码逻辑为同步模式如:
重复付款多次 可能的原因是,当接口返回值 return_code=SUCESS
及 result_code=FAIL
时,你的代码逻辑有缺陷,未明确异常码err_code
,换单重入了(按照文档要求,是需要再判断err_code
当为下列枚举值时,需要 原商户订单号重试 即 partner_trade_no
重入再试)。
NOTENOUGH
SYSTEMERROR
NAME_MISMATCH
SIGN_ERROR
FREQ_LIMIT
MONEY_LIMIT
CA_ERROR
V2_ACCOUNT_SIMPLE_BAN
PARAM_IS_NOT_UTF8
SENDNUM_LIMIT
SEND_MONEY_LIMIT
RECEIVED_MONEY_LIMIT
有验返回值的,如下:
$result = $this->wechatpay_transfers($cash['user_id'], $cash['total'], '提现', $is_check_name);
if ($result['return_code'] == 'SUCCESS') {
if ($result['result_code'] == 'SUCCESS') {
$partner_trade_no = $result['partner_trade_no'];
//$payment_no = $result['payment_no'];
//$payment_time = $result['payment_time'];
$this->_cash('cashed', 'cash_agree', $cash['user_id'], 0, $cash['money'], '同意提现' . $cash['money'] . '元');
$this->db->where('id', $cash['id'])->update('cash', array('order_no' => $partner_trade_no, 'examine_status' => 1, 'examine_reason' => empty($post['reason']) ? '' : $post['reason'], 'examine_time' => date('Y-m-d H:i:s')));
$this->_admin_log('同意提现', json_encode($post));
} else {
$return_code = 2;
$return_message = 'err_code:' . $result['err_code'] . ',err_code_des:' . $result['err_code_des'];
}
} else {
$return_code = 1;
$return_message = 'return_code:' . $result['return_code'] . ',return_msg:' . $result['return_msg'];
}
建议把付款单号从 $this->wechatpay_transfers
内部提出到外部,以最终付款(业务)状态决定是否需要需要重试,另外排查是不是因12种异常状态被系统自动恢复付款,可按如下步骤:
- 登录 pay.weixin.qq.com 查询付款记录,看下相同金额的付款单号是不是相邻很近(或者有多笔单号一致)的付款记录;
- 拿付款单号,到这里,联系官方在线技术支持,排查下是不是相邻很近的 原单 被系统自动 恢复付款 了;
同步模式报一下错误是啥原因?
The promise was rejected
这个接口是特殊接口之一,返回值没有sign
字典,无法验签;而SDK默认是强验签,如果没有给特殊请求 handler
则会把所有返回值均打入 RejectedPromise
,建议按照 #78 (comment) 方式改造请求参数