求帮忙看一下代码对不对,移动端调用一直提示签名验证失败
wangya123456789 opened this issue · 8 comments
运行环境
- OS:Nginx 1.22.1
- PHP:7.4.33
- wechatpay-php:1.4.8
描述你的问题现象
App是使用Flutter开发的,使用fluwx微信支付插件,移动端一直提示支付验证签名失败,我还配置了拉起小程序的功能,没遇到问题,就是支付一直报错
<?php
namespace app\controller;
use app\BaseController;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
use WeChatPay\Builder;
use WeChatPay\Formatter;
class Index extends BaseController
{
public function index()
{
// 商户号
$merchantId = '16443******';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file://' . './WechatKey/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = '624BE588C745175E5557DA9D29AD*******';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = 'file://' . './WechatKey/cert.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
// 发送请求
$resp = $instance->chain('v3/certificates')->get(
['debug' => false] // 调试模式,https://docs.guzzlephp.org/en/stable/request-options.html#debug
);
//echo $resp->getBody(), PHP_EOL;
//第二步,生成prepay_id,以及sign签名
try {
$resp = $instance
->chain('v3/pay/transactions/app')
->post(['json' => [
'mchid' => '16443******',
'out_trade_no' => $this->FunctionName(),
'appid' => 'wx1d3659d570********',
'description' => '友情赞助',
'notify_url' => 'https://www.weixin.qq.com/wxpay/pay.php', //用来接收,用户是否支付成功的链接
'amount' => [
'total' => 1, //订单金额
'currency' => 'CNY'
],
]]);
//echo $resp->getStatusCode(), PHP_EOL;
//echo $resp->getBody(), PHP_EOL;
$obj = json_decode($resp->getBody());
$sign = json_decode($this->sign($obj->prepay_id));
$ret = ["prepay_id" => $obj->prepay_id, "sign" => $sign->paySign];
return json_encode($ret); //返回sign签名和prepay_id
} catch (\Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
}
//随机生成一个订单号
public function FunctionName()
{
// 定义字符集
$chars = '0123456789';
// 生成随机字符串
$randomString = '';
for ($i = 0; $i < 18; $i++) {
$index = rand(0, strlen($chars) - 1);
$randomString .= $chars[$index];
}
return $randomString . time(); // 输出随机字符串
}
//生成sign签名
public function sign(string $prepay_id)
{
$merchantPrivateKeyFilePath = 'file://' . './WechatKey/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
$params = [
'appId' => 'wx1d3659d5******',
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return json_encode($params);
}
}
$resp = $instance->chain('v3/certificates')
这个请求仅是README示例,在实际项目内,几乎用不到,建议拿掉...
$ret = ["prepay_id" => $obj->prepay_id, "sign" => $sign->paySign];
return json_encode($ret); //返回sign签名和prepay_id
这里的return
会把 sign
做二次json序列话,你前端Flutter拿到要做一次JSON.parse
成为对象再送给 wx.requestPayment
前端看不到你是怎么处理的,大致推断是你前端把 sign
当成字符串给 wx.requestPayment
的缘由。
另外注意看一下 #116 , 你前端用的API是拿锅,你要查拿锅儿开放平台的官方文档,有些地方入参要求是 timestamp
有些地方要求是 timeStamp
, 拿锅儿 S 字符大小写得按MP端的文档来,该大写大写,该小写小写...
另外注意看一下 #116 , 你前端用的API是拿锅,你要查拿锅儿开放平台的官方文档,有些地方入参要求是
timestamp
有些地方要求是timeStamp
, 拿锅儿S字符大小写得按MP端的文档来,该大写大写,该小写小写...
好的,我研究一下
$resp = $instance->chain('v3/certificates')
这个请求仅是README示例,在实际项目内,几乎用不到,建议拿掉...
$ret = ["prepay_id" => $obj->prepay_id, "sign" => $sign->paySign]; return json_encode($ret); //返回sign签名和prepay_id
这里的
return
会把sign
做二次json序列话,你前端Flutter拿到要做一次JSON.parse
成为对象再送给wx.requestPayment
前端看不到你是怎么处理的,大致推断是你前端把
sign
当成字符串给wx.requestPayment
的缘由。
还是报错 支付验证失败,下面是我的Flutter代码
Response response = await Dio().post('http://pay.yyybabc.com/index.php', data: {'money': money.value});
Map<String, dynamic> data =await json.decode(response.data);
await fluwx.pay(
which: Payment(
appId: "wx1d3659d57006b7d0",
partnerId: '1644348856',
prepayId: data['prepay_id'].toString(),
packageValue: 'Sign=WXPay',
nonceStr: noncestr().toString(),
timestamp: DateTime.now().second ,
sign: data['sign'].toString(),
));
print("支付结果:${_result}");
下面是请求发起到结束时的运行打印内容
D/MicroMsg.SDK.WXMsgImplComm(20143): check signature:308202eb30820254a00302010202044d36f7a4300d06092a864886f70d01010505003081b9310b300906035504061302383631123010060355040813094775616e67646f6e673111300f060355040713085368656e7a68656e31353033060355040a132c54656e63656e7420546563686e6f6c6f6779285368656e7a68656e2920436f6d70616e79204c696d69746564313a3038060355040b133154656e63656e74204775616e677a686f7520526573656172636820616e6420446576656c6f706d656e742043656e7465723110300e0603550403130754656e63656e74301e170d3131303131393134333933325a170d3431303131313134333933325a3081b9310b300906035504061302383631123010060355040813094775616e67646f6e673111300f060355040713085368656e7a68656e31353033060355040a132c54656e63656e7420546563686e6f6c6f6779285368656e7a68656e2920436f6d70616e79204c696d69746564313a3038060355040b133154656e63656e74204775616e677a686f7520526573656172636820616e6420446576656c6f706d656e742043656e7465723110300e0603550403130754656e63656e7430819f300d06092a864886f70d010101050003818d0030818902818100c05f34b231b083fb1323670bfbe7bdab40c0c0a6efc87ef2072a1ff0d60cc67c8edb0d0847f210bea6cbfaa241be70c86daf56be08b723c859e52428a064555d80db448cdcacc1aea2501eba06f8bad12a4fa49d85cacd7abeb68945a5cb5e061629b52e3254c373550ee4e40cb7c8ae6f7a8151ccd8df582d446f39ae0c5e930203010001300d06092a864886f70d0101050500038181009c8d9d7f2f908c42081b4c764c377109a8b2c70582422125ce545842d5f520aea69550b6bd8bfd94e987b75a3077eb04ad341f481aac266e89d3864456e69fba13df018acdc168b9a19dfd7ad9d9cc6f6ace57c746515f71234df3a053e33ba93ece5cd0fc15f3e389a3f365588a9fcb439e069d3629cd7732a13fff7b891499
D/MicroMsg.SDK.WXMsgImplComm(20143): pass
I/MicroMsg.SDK.WXApiImplV10(20143): sendReq, req type = 5
I/MicroMsg.SDK.WXApiImplV10(20143): getTokenFromWX token is wx1d3659d57006b7d0--557596528-25708289922379-193
I/MicroMsg.SDK.MMessageAct(20143): send, targetPkgName = com.tencent.mm, targetClassName = com.tencent.mm.plugin.base.stub.WXPayEntryActivity, launchMode = 2
I/MicroMsg.SDK.MMessageAct(20143): sendUsingPendingIntent
D/MicroMsg.SDK.MMessageAct(20143): send mm message, intent=Intent { flg=0x18000000 cmp=com.tencent.mm/.plugin.base.stub.WXPayEntryActivity (has extras) }
I/flutter (20143): 支付结果:无
I/MicroMsg.SDK.MMessageAct(20143): sendUsingPendingIntent onSendFinished resultCode: 0, resultData: null
W/SQLiteLog(20143): (28) double-quoted string literal: "
W/SQLiteLog(20143): (28) double-quoted string literal: "30E48AC1072C76BE12EF5DEBF5788F56"
D/DecorView[](20143): onWindowFocusChanged hasWindowFocus false
D/BrandVideoCacheManager(20143): onReceivedNewBrandCache start:2
D/BrandVideoCacheManager(20143): setting num:2
D/BrandVideoCacheManager(20143): save video cache:[]
D/BrandVideoCacheManager(20143): try delete: edf9a24f85e4da94183ec64b127e06b1 ,result false
E/flutter (21842): [ERROR:flutter/lib/ui/text/fontmgr_default_android.cc(426)] value Xiaomi
E/flutter (21842): [ERROR:flutter/lib/ui/text/fontmgr_default_android.cc(428)] fontXmlName /data/user/0/com.tencent.mm/code_cache/com.tencent.mm:appbrand0/flutter_custom_fonts2.xml
E/flutter (21842): [ERROR:flutter/lib/ui/text/fontmgr_default_android.cc(541)] use default font mgr
I/flutter (21842): [INFO:message_loop_android.cc(22)] prepare main_looper: 1
I/flutter (21842): [INFO:message_loop.cc(104)] start create platform thread message loop
I/flutter (21842): [INFO:message_loop_android.cc(49)] AcquireLooperForMainThread success
I/flutter (21842): [INFO:message_loop.cc(106)] create platform thread message loop end
I/flutter (21842): [INFO:message_loop.cc(108)] platform thread loop set to thread local message loop
D/BrandVideoCacheManager(20143): onReceivedNewBrandCache start:2
D/BrandVideoCacheManager(20143): setting num:2
D/BrandVideoCacheManager(20143): save video cache:[]
D/BrandVideoCacheManager(20143): try delete: edf9a24f85e4da94183ec64b127e06b1 ,result false
I/TeaLog (20143): s worked:true 60000
D/TrafficStats(20143): tagSocket(86) with statsTag=0xffffffff, statsUid=-1
I/TeaLog (20143): s 1 1
I/TeaLog (20143): s worked:true 60000
D/TrafficStats(20143): tagSocket(85) with statsTag=0xffffffff, statsUid=-1
I/TeaLog (20143): s worked:true 60000
I/com.yyybabc(20143): This is non sticky GC, maxfree is 8388608 minfree is 2097152
I/TeaLog (20143): s worked:true 60000
iOS sample@ https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
PayReq *request = [[[PayReq alloc] init] autorelease];
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
[WXApi sendReq:request];
backend:
public function sign(string $prepay_id, string $app_id = 'wx1d3659d57006b7d0', string $mch_id = '1644348856')
{
$merchantPrivateKeyFilePath = 'file://' . './WechatKey/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
$params = [
'appid' => $app_id,
'timestamp' => (string)Formatter::timestamp(),
'noncestr' => Formatter::nonce(),
'prepayid' => $prepay_id,
];
$params += ['sign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'partnerid' => $mch_id, 'package' => 'Sign=WXPay', ];
return $params;
}
front:
await fluwx.pay(
which: Payment(
appId: data['appid'].toString(),
partnerId: data['partnerid'].toString(),
prepayId: data['prepayid'].toString(),
packageValue: data['package'].toString(),
nonceStr: data['noncestr'].toString(),
timeStamp: data['timestamp'].toString(),
sign: data['sign'].toString(),
));
什么意思,没看明白,这不就是我的代码吗?
成功了, 'package' => 'prepay_id=' . $prepay_id,不能有prepay_id=,唉,你们文档也不说一下
//生成sign签名
public function sign(string $prepay_id)
{
$merchantPrivateKeyFilePath = 'file://' . './WechatKey/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
$params = [
'appId' => 'wx1d3659d5******',
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => **'prepay_id=' .** $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return json_encode($params);
}
app用于唤醒微信收银台的参数,建议均以服务端返回的为准,你调整后的代码能运行,是运气;建议你仔细看一下我给你贴的 backend front 关键代码。