- 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
10-09 - 微信合单支付demo - PHP
微信合单支付PHP版demo 1、获取API v3证书 /** * 获取证书 * @return mixed */ public function getCert() { $url = 'https://api.mch.weixin.qq.com/v3/certificates'; $timestamp = time(); $nonce = $this->nonce_str(); $body = ''; $sign = $this->sign($url, 'GET', $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id, $this->serial_no); $header = [ 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign, 'Accept:application/json', 'User-Agent:' . $this->mch_id, ]; $result = $this->curl($url, '', $header, 'GET'); $result = json_decode($result, true); return $result['data']['0']['serial_no']; } 2、支付请求 /** * @return bool|mixed */ public function pay() { $url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/app'; $requestData = [ 'combine_appid' => $this->app_id, 'combine_mchid' => $this->mch_id, 'combine_out_trade_no' => 'app_pay_' . time(), 'scene_info' => [ 'device_id' => 'pay_device_id', 'payer_client_ip' => '127.0.0.1', ], 'time_start' => date('c', time()), 'time_expire' => date('c', time() + 7200), 'notify_url' => 'http://www.ttglad.com/notify.php', 'sub_orders' => [ [ 'mchid' => $this->mch_id, 'attach' => 'notify with attach', 'amount' => [ 'total_amount' => 100, 'currency' => 'CNY', ], 'out_trade_no' => 'sub_order_' . time(), 'sub_mchid' => $this->sub_mch_id, // 二级商户号 需要进件系统生成 'profit_sharing' => true, // 分账 'description' => '描述', ], ], ]; $header = $this->getCurlHeader($url, json_encode($requestData), 'POST'); $result = $this->curl($url, json_encode($requestData), $header, 'POST'); return json_decode($result, true); } 3、支付查询 /** * @return mixed */ public function payQuery() { $url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/'; $url = $url . ''; // 支付单号 $method = 'GET'; $data = ''; $header = $this->getCurlHeader($url, $data, $method); $result = $this->curl($url, $data, $header, $method); return json_decode($result, true); } 4、退款 /** * @return mixed */ public function refund() { $requestData = [ 'sp_appid' => $this->app_id, 'sub_mchid' => $this->sub_mch_id, 'transaction_id' => '',// 支付三方流水 'out_refund_no' => '',// 退款单号 'notify_url' => 'http://www.ttglad.com/notify_refund.php', 'amount' => [ 'refund' => 100, 'total' => 100, 'currency' => 'CNY', ] ]; $header = $this->getCurlHeader($this->refundUrl, json_encode($requestData), 'POST'); $result = $this->curl($this->refundUrl, json_encode($requestData), $header, 'POST'); return json_decode($result, true); } 5、退款查询 /** * @return mixed */ public function refundQuery() { $url = 'https://api.mch.weixin.qq.com/v3/ecommerce/refunds/id/' . '' . '?sub_mchid=' . $this->sub_mch_id; $method = 'GET'; $data = ''; $header = $this->getCurlHeader($url, $data, $method); $result = $this->curl($url, $data, $header, $method); return json_decode($result, true); } 6、支付通知 public function notify() { $header = $this->getHeaders(); $body = $GLOBALS['HTTP_RAW_POST_DATA']; if (empty($header) || empty($body)) { throw new Exception('通知参数为空', 2001); } $timestamp = $header['WECHATPAY-TIMESTAMP']; $nonce = $header['WECHATPAY-NONCE']; $signature = $header['WECHATPAY-SIGNATURE']; $serialNo = $header['WECHATPAY-SERIAL']; if (empty($timestamp) || empty($nonce) || empty($signature) || empty($serialNo)) { throw new Exception('通知头参数为空', 2002); } $cert = $this->getCertBySerialNo($serialNo); $message = "$timestamp\n$nonce\n$body\n"; //校验签名 if (!$this->verify($message, $signature, $cert['plainCertificate'])) { throw new Exception('验签失败', 2005); } $decodeBody = json_decode($body, true); if (empty($decodeBody) || !isset($decodeBody['resource'])) { throw new Exception('通知参数内容为空', 2003); } $decodeBodyResource = $decodeBody['resource']; $decodeData = $this->decryptToString($decodeBodyResource['associated_data'], $decodeBodyResource['nonce'], $decodeBodyResource['ciphertext'], ''); $decodeData = json_decode($decodeData, true); if (empty($decodeData)) { throw new Exception('通知参数解密发生错误', 2004); } // todo 业务逻辑 } 7、其他方法 /** * 初始化参数 */ public function __construct() { parent::__construct(); // 微信支付 商户号 $this->mch_id = ''; // 二级商户号,需要走进件系统生成 $this->sub_mch_id = ''; // 微信支付 商户号绑定的appid $this->app_id = ''; // 商户私钥 $this->private_key = ''; // 商户证书序列号 // 如何查看证书序列号:https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-hao $this->serial_no = ''; // apiv3秘钥:https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/api-v3-mi-yao $this->mch_key = ''; } /** * @param $serialNo * @return mixed */ private function getCertBySerialNo($serialNo) { $url = 'https://api.mch.weixin.qq.com/v3/certificates'; $timestamp = time(); $nonce = $this->nonce_str(); $body = ''; $sign = $this->sign($url, 'GET', $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id, $this->serial_no); $header = [ 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign, 'Accept:application/json', 'User-Agent:' . $this->mch_id, ]; $result = $this->curl($url, '', $header, 'GET'); $cert = json_decode($result, true); $return = []; if (!empty($cert['data'])) { foreach ($cert['data'] as $item) { if ($serialNo == $item['serial_no']) { $return = $item; break; } } } return $return; } /** * @param $url * @param $body * @param $method * @return array */ protected function getCurlHeader($url, $body, $method) { $timestamp = time(); $nonce = $this->nonce_str(); $sign = $this->sign($url, $method, $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id, $this->serial_no); return [ 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign, 'Accept:application/json', 'User-Agent:' . $this->mch_id, 'Content-Type:application/json', 'Wechatpay-Serial:' . $this->getCert(), ]; } /** * @return string */ protected function nonce_str() { static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); $randomString = ''; for ($i = 0; $i < 32; $i++) { $randomString .= $characters[rand(0, $charactersLength - 1)]; } return $randomString; } /** * @param $key * @return bool|resource */ protected function getPrivateKey($key) { return openssl_get_privatekey($key); } /** * @param $key * @return resource */ protected function getPublicKey($key) { return openssl_get_publickey($key); } /** * @param $url * @param $http_method * @param $timestamp * @param $nonce * @param $body * @param $mch_private_key * @param $merchant_id * @param $serial_no * @return string */ protected function sign($url, $http_method, $timestamp, $nonce, $body, $mch_private_key, $merchant_id, $serial_no) { $url_parts = parse_url($url); $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "")); $message = $http_method . "\n" . $canonical_url . "\n" . $timestamp . "\n" . $nonce . "\n" . $body . "\n"; openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); $schema = 'WECHATPAY2-SHA256-RSA2048 '; $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign); return $token; } /** * @param $message * @param $signature * @param $merchantPublicKey * @return bool|int */ private function verify($message, $signature, $merchantPublicKey) { if (empty($merchantPublicKey)) { return false; } if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) { throw new \RuntimeException("当前PHP环境不支持SHA256withRSA"); } $signature = base64_decode($signature); return openssl_verify($message, $signature, $this->getPublicKey($merchantPublicKey), 'sha256WithRSAEncryption'); } /** * @param $url * @param array $data * @param $header * @param string $method * @param int $time_out * @return mixed */ private function curl($url, $data = [], $header, $method = 'POST', $time_out = 3) { $curl = curl_init(); // 设置curl允许执行的最长秒数 curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_TIMEOUT, $time_out); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); if ($method == 'POST') { curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } // 执行操作 $result = curl_exec($curl); curl_close($curl); return $result; } /** * @param $associatedData * @param $nonceStr * @param $ciphertext * @param $aesKey * @return bool|string */ private function decryptToString($associatedData, $nonceStr, $ciphertext, $aesKey = '') { if (empty($aesKey)) { $aesKey = $this->mch_key; } $ciphertext = \base64_decode($ciphertext); if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) { return false; } // ext-sodium (default installed on >= PHP 7.2) if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) { return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey); } // ext-libsodium (need install libsodium-php 1.x via pecl) if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) { return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey); } // openssl (PHP >= 7.1 support AEAD) if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) { $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE); $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE); return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData); } throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } /** * @return array */ private function getHeaders() { $headers = array(); foreach ($_SERVER as $key => $value) { if ('HTTP_' == substr($key, 0, 5)) { $headers[str_replace('_', '-', substr($key, 5))] = $value; } if (isset($_SERVER['PHP_AUTH_DIGEST'])) { $header['AUTHORIZATION'] = $_SERVER['PHP_AUTH_DIGEST']; } elseif (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { $header['AUTHORIZATION'] = base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); } if (isset($_SERVER['CONTENT_LENGTH'])) { $header['CONTENT-LENGTH'] = $_SERVER['CONTENT_LENGTH']; } if (isset($_SERVER['CONTENT_TYPE'])) { $header['CONTENT-TYPE'] = $_SERVER['CONTENT_TYPE']; } } return $headers; } 8、注意事项 支付通知需要根据通知header里面的证书序号获取证书 合单支付同一个子单不允许在不同的父单支付 9、最近写了个支付的package,仅供参考:https://github.com/ttglad/payment 参考文档: 合单支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/combine.shtml接口规则:https://wechatpay-api.gitbook.io/wechatpay-api-v3/
2021-04-16 - 微信支付V3版本小程序支付 php签名,验签,数据解密代码分享
数据解密需要用到sodium扩展 大部分php版本需要安装 证书序列号可以在这里查看https://myssl.com/cert_decode.html 我用的php7.4版本 直接上代码: <?php //微信原生支付 class Wxpay { private static $appid = 'xxxxxxxxxxxxxx'; //appid private static $mchid = 'xxxxxxxxxxxxxx'; //商户号 private static $mch_apiv3key = 'xxxxxxxxxxxxxx'; //商户平台apiv3密钥(商户平台手动生成) private static $serial_no = 'xxxxxxxxxxxxxx';//商户API证书序列号serial_no private static $wx_serial_no = 'xxxxxxxxxxxxxx';//平台证书序列号 /* * 支付(小程序支付) * @param type $sn 订单编号 * @param type $money 金额 * @param type $openid 用户小程序openid * @return type */ public static function getPayParam($sn, $money, $openid) { $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'; $notify_url = url('/api/weixin/notify'); $data = []; $data['appid'] = self::$appid; $data['mchid'] = self::$mchid; //商户号 $data['description'] = 'xxx'; //描述? $data['out_trade_no'] = $sn; //商户系统内部订单号 $data['time_expire'] = date('Y-m-d') . 'T' . date('H:i:s', (time() + 1800)) . '+08:00'; //订单失效时间2018-06-08T10:34:56+08:00 $data['notify_url'] = $notify_url; //异步通知接口地址 $data['amount'] = ['total' => $money * 100, 'currency' => 'CNY']; //金额 $data['payer'] = ['openid' => $openid]; //用户 $re = self::wxCurl($url, $data, 'POST'); if (!isset($re['prepay_id'])) { exit('参数获取失败'); } $result = []; $result['appId'] = self::$appid; $result['timeStamp'] = (string)time(); $result['nonceStr'] = uniqid(); $result['package'] = 'prepay_id=' . $re['prepay_id']; $result['signType'] = 'RSA'; $result['paySign'] = self::getPaySign($result); return $result; } /** * 查询订单 * @param type $sn */ public static function select($sn, $return = false) { $mchid = self::$mchid; //商户号 $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $sn . '?mchid=' . $mchid; $re = self::wxCurl($url, [], 'GET'); if ($return) { return $re; } if (isset($re['trade_state']) && $re['trade_state'] == 'SUCCESS') { return true; } return false; } /** * 关闭订单 * @param type $sn */ public static function close($sn) { $mchid = self::$mchid; //商户号 $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $sn . '/close'; $re = self::wxCurl($url, ['mchid' => $mchid], 'POST'); return true; } /** * 退款 * @param type $sn */ public static function refund($order_sn, $refund_sn, $total, $refund, $msg = '退款') { $url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds'; $data = []; $data['notify_url'] = url('ag/weixin/notify_refund'); $data['out_trade_no'] = $order_sn; //订单号 $data['out_refund_no'] = $refund_sn; //退款单号 $data['reason'] = $msg; $data['amount'] = ['refund' => $refund * 100, 'total' => $total * 100, 'currency' => 'CNY']; $re = self::wxCurl($url, $data, 'POST'); return $re; } //请求 public static function wxCurl($url, $data = [], $method = 'GET') { $Authorization = self::getReSign($url, $data, $method); $header = [ 'Content-Type: application/json', 'Accept: application/json', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63', 'Authorization: ' . $Authorization ]; $redata = $data ? json_encode($data) : ''; //CURL $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FAILONERROR, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //有post参数-设置 if ($method=='POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $redata); } curl_setopt($ch, CURLOPT_HTTPHEADER, $header); $result = curl_exec($ch); curl_close($ch); return $result ? json_decode($result, true) : []; } //后端请求签名 public static function getReSign($url, $data, $method = 'GET') { $url_parts = parse_url($url); $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "")); $http_method = $method; $timestamp = time(); $nonce = uniqid(); $body = $data ? json_encode($data) : ''; $mchid = self::$mchid; //商户id $serial_no = self::$serial_no; //证书编号 $private_key = self::getPrivateKey(BASE_PATH . 'cert/apiclient_key.pem'); //商户私钥 $message = $http_method . "\n" . $canonical_url . "\n" . $timestamp . "\n" . $nonce . "\n" . $body . "\n"; openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $mchid, $nonce, $timestamp, $serial_no, $sign); return 'WECHATPAY2-SHA256-RSA2048 ' . $token; } //前端小程序签名 public static function getPaySign($result) { $private_key = self::getPrivateKey(BASE_PATH . 'cert/apiclient_key.pem'); //商户私钥 $message = $result['appId'] . "\n" . $result['timeStamp'] . "\n" . $result['nonceStr'] . "\n" . $result['package'] . "\n"; openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); return $sign; } //验证签名 public static function checkSign() { $header = $_SERVER;//请求头 $serial_no = $header['wechatpay-serial'] ?? ''; //微信平台序列号 $timeStamp = $header['wechatpay-timestamp'] ?? ''; $nonce = $header['wechatpay-nonce'] ?? ''; $body = file_get_contents('php://input');//获取原始请求体 $wx_sign = $header['wechatpay-signature'] ?? ''; $wx_serial_no = self::$wx_serial_no; //保存的平台序列号(从下载的平台证书里面获取) if (!$serial_no || $wx_serial_no != $serial_no) {//证书过期 return false; } $message = $timeStamp . "\n" . $nonce . "\n" . $body . "\n"; $wx_sign = base64_decode($wx_sign); $public_key = self::getPublicKey(BASE_PATH . 'cert/wx_public_cert.pem'); //平台公钥 $res = openssl_verify($message, $wx_sign, $public_key, OPENSSL_ALGO_SHA256); if ($res == 1) { return true; } return false; } //获取私钥 public static function getPrivateKey($filepath) { return openssl_get_privatekey(file_get_contents($filepath)); } //获取公钥 public static function getPublicKey($filepath) { return openssl_pkey_get_public(file_get_contents($filepath)); } //加密数据(基本用不上) public static function getEncrypt($str) { //$str是待加密字符串 $public_key_path = BASE_PATH . 'cert/wx_public_cert.pem'; //'平台证书路径'; $public_key = file_get_contents($public_key_path); $encrypted = ''; if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) { //base64编码 $sign = base64_encode($encrypted); } else { throw new Exception('encrypt failed'); } return $sign; } //解密数据(下载证书用的) public static function decryptToString($ciphertext, $associatedData, $nonceStr) { $aesKey =self::$mch_apiv3key; //商户apiv3密钥解密 $str = base64_decode($ciphertext); if (strlen($str) <= 16) { return ''; } // ext-sodium (default installed on >= PHP 7.2) 如果没有该函数需要安装sodium扩展 return sodium_crypto_aead_aes256gcm_decrypt($str, $associatedData, $nonceStr, $aesKey); } //下载平台证书 public static function downCert() { $url = 'https://api.mch.weixin.qq.com/v3/certificates'; $re = self::wxCurl($url, [], 'GET'); if (!isset($re['data'])) { exit('获取证书失败'); } $ciphertext = $re['data'][0]['encrypt_certificate']['ciphertext']; $associatedData = $re['data'][0]['encrypt_certificate']['associated_data']; $nonceStr = $re['data'][0]['encrypt_certificate']['nonce']; $data = self::decryptToString($ciphertext, $associatedData, $nonceStr); if (!$data) { exit('获取证书解密失败'); } //保存平台证书 (https://myssl.com/cert_decode.html)获取证书序列号 file_put_contents(BASE_PATH . '/cert/wx_public_cert.pem', $data); return $data; } }
2021-10-11 - PHP实战v3验签详讲及注意事项
序言 Hi~朋友们好久不见,这期我们聊聊怎么使用PHP做V3验签吧,相信技术支持的老铁们遇到过很多使用PHP请求报“验签失败”来咨询的吧! v3接口规则怎么验签文档已经讲得很清楚了,自己去脑补下吧 ********************************************************* 签名验证文档地址https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml********************************************************* 准备 商户调用平台证书接口并解密,获取商户平台证书,接口文档https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay5_1.shtml ---设置商户平台证书路径和证书序列号,PHP代码如下: <?php //$path为平台证书路径,$serial_no为平台证书序列号 $path="./wxp_cert.pem"; $serial_no="22BA85499B66D007562D2EB68F2DC8CABA4DEDAD"; ?> PHP验签案例 商户先从应答中获取应答时间戳,应答随机串,应答主体,微信支付的平台证书序列号接口文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_14.shtml ---相关PHP代码如下: <?php //$Wechatpay_Serial为微信支付的平台证书序列号,$Wechatpay_Timestamp为应答时间戳,$Wechatpay_Nonce为应答随机串,$response_Body为应答主体 $Wechatpay_Serial="22BA85499B66D007562D2EB68F2DC8CABA4DEDAD"; $Wechatpay_Timestamp="1620962622"; $Wechatpay_Nonce="4284dcd92f013aa25c7161d42dc21e99"; $response_Body=$ret_message; ?> 检查平台证书序列号,微信支付的平台证书序列号位于HTTP头Wechatpay-Serial。验证签名前,请商户先检查序列号是否跟商户当前所持有的微信支付平台证书的序列号一致。如果不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将无法成功验证签名。---相关PHP代码如下: <?php if ($serial_no!=$Wechatpay_Serial) { throw new \RuntimeException("证书序列号不匹配"); } ?> 按照以下规则构造应答的验签名串,签名串共有三行,行尾以\n结束,包括最后一行。\n为换行符(ASCII编码值为0x0A)。若应答报文主体为空(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。[图片] ---相关PHP代码如下: <?php $str为最终构造的签名串 $str=$Wechatpay_Timestamp."\n".$Wechatpay_Nonce."\n".$response_Body."\n"; ?> ---构造的应答验签串真实数据为: 1620962622 4284dcd92f013aa25c7161d42dc21e99 {"code":"INVALID_REQUEST","message":"与现有记录冲突,且appid与已存在记录不同"} 获取应答签名,微信支付的应答签名通过HTTP头Wechatpay_Signature传递,对Wechatpay_Signature的字段值使用Base64进行解码,得到应答签名---对签名base64解码,相关PHP代码如下: <?php //$signature为应答签名 $Wechatpay_Signature="lKi0KkD/A2dJDbigyE9jCk/MP9p+qgFdrJPSCNj2FOJbAdodgMgj2IHr8FgLbQHws9Xy04oYdvvdYsRSpY4RbzXGPdoaEagonaXWTR8Tlg54uIcJ8bajWrqBaOedZw5wDK/j6wl5/zWHNRod506ZmTKhWpZ0VnOS6yMq/bWGPF4B9MtOCZzSVyqFfPm9vdUqlljnMlT2+KGnrXgqAKUfUVG2PugugAlU7eAyvz5bAnU+18Gh25qZ+yr93fs4+qhBHoDdvecHNg6Xhv+V6e8w7keJDIvSLydE+xbYGWw3lHJGBqXwLNKuN3ftG5eZP7HenlCh3+YTiEnDeas3VMo/0g=="; $signature=base64_decode($Wechatpay_Signature); ?> 验证签名,很多编程语言的签名验证函数支持对验签名串和签名进行签名验证。强烈建议商户调用该类函数,使用微信支付平台公钥对验签名串和签名进行SHA256 with RSA签名验证。---PHP开启OpenSSL拓展,使用openssl_sign()函数验签代码如下: <?php //$ret为验签结果 $mch_cert_key=openssl_get_publickey(file_get_contents($path)); $ret=openssl_verify($str,$signature,$mch_cert_key,'sha256WithRSAEncryption'); if (!$ret) { throw new \RuntimeException("Verified Fail"); } ?> 附件:完整PHP代码 <?php //$path为平台证书路径,$serial_no为平台证书序列号 $path="./wxp_cert.pem"; $serial_no="22BA85499B66D007562D2EB68F2DC8CABA4DEDAD"; //$Wechatpay_Serial为微信支付的平台证书序列号,$Wechatpay_Timestamp为应答时间戳,$Wechatpay_Nonce为应答随机串,$response_Body为应答主体 $Wechatpay_Serial="22BA85499B66D007562D2EB68F2DC8CABA4DEDAD"; $Wechatpay_Timestamp="1620962622"; $Wechatpay_Nonce="4284dcd92f013aa25c7161d42dc21e99"; $response_Body=$ret_message; if ($serial_no!=$Wechatpay_Serial) { throw new \RuntimeException("证书序列号不匹配"); } $str为最终构造的签名串 $str=$Wechatpay_Timestamp."\n".$Wechatpay_Nonce."\n".$response_Body."\n"; //$signature为应答签名 $Wechatpay_Signature="lKi0KkD/A2dJDbigyE9jCk/MP9p+qgFdrJPSCNj2FOJbAdodgMgj2IHr8FgLbQHws9Xy04oYdvvdYsRSpY4RbzXGPdoaEagonaXWTR8Tlg54uIcJ8bajWrqBaOedZw5wDK/j6wl5/zWHNRod506ZmTKhWpZ0VnOS6yMq/bWGPF4B9MtOCZzSVyqFfPm9vdUqlljnMlT2+KGnrXgqAKUfUVG2PugugAlU7eAyvz5bAnU+18Gh25qZ+yr93fs4+qhBHoDdvecHNg6Xhv+V6e8w7keJDIvSLydE+xbYGWw3lHJGBqXwLNKuN3ftG5eZP7HenlCh3+YTiEnDeas3VMo/0g=="; $signature=base64_decode($Wechatpay_Signature); //$ret为验签结果 $mch_cert_key=openssl_get_publickey(file_get_contents($path)); $ret=openssl_verify($str,$signature,$mch_cert_key,'sha256WithRSAEncryption'); if (!$ret) { throw new \RuntimeException("Verified Fail"); } ?>
2021-05-25