微信服务商V3版支付
一、前言
首先去微信支付服务商平台查看相关接口和官方提供的sdk。网址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay6_0.shtml,了解清楚支付需要的参数和需要申请的微信服务商相关信息。
二、开始demo
- 我使用的是php语言,用的thinkphp框架,所以使用composer直接拉去SDK就可以。
composer require wechatpay/wechatpay - 按照sdk的约定去堆积代码块,做好参数验证就可以了。
本类库是以 `OpenAPI` 对应的接入点 `URL.pathname` 以`/`做切分,映射成`segments`<sup>[RFC3986](#note-rfc3986),编码书写方式有如下约定:1. 请求 `pathname` 切分后的每个`segment`,可直接以对象获取形式串接,例如 `v3/pay/transactions/native` 即串成 `v3->pay->transactions->native`;
2. 每个 `pathname` 所支持的 `HTTP METHOD`,即作为被串接对象的末尾执行方法,例如: `v3->pay->transactions->native->post(['json' => []])`;
3. 每个 `pathname` 所支持的 `HTTP METHOD`,同时支持`Async`语法糖,例如: `v3->pay->transactions->native->postAsync(['json' => []])`;
4. 每个 `segment` 有中线(dash)分隔符的,可以使用驼峰`camelCase`风格书写,例如: `merchant-service`可写成 `merchantService`,或如 `{'merchant-service'}`;
5. 每个 `segment` 中,若有`uri_template`动态参数<sup>[RFC6570](#note-rfc6570),例如 `business_code/{business_code}` 推荐以`business_code->{'{business_code}'}`形式书写,其格式语义与`pathname`基本一致,阅读起来比较自然;
6. SDK内置以 `v2` 特殊标识为 `APIv2` 的起始 `segmemt`,之后串接切分后的 `segments`,如源 `pay/micropay` 即串成 `v2->pay->micropay->post(['xml' => []])` 即以XML形式请求远端接口;
7. 在IDE集成环境下,也可以按照内置的`chain($segment)`接口规范,直接以`pathname`作为变量`$segment`,来获取`OpenAPI`接入点的`endpoints`串接对象,驱动末尾执行方法(填入对应参数),发起请求,例如 `chain('v3/pay/transactions/jsapi')->post(['json' => []])`;以下示例用法,以`异步(Async/PromiseA+)`或`同步(Sync)`结合此种编码模式展开。
3.一个简单的聚合支付例子。
/*定义文件命名空间*/
namespace app\v2pay\model;/*引入所需文件*/
use think\Model;
use think\Db;
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;/*定义文件类名称,并继承指定父类*/
class Wechat Extends Model{protected $instance = '';protected $result = ['code'=>3,'msg'=>"内部请求错误!",'total'=>0,'ret_data'=>[],'response_data'=>''];public function __construct(){// 商户号,假定为`1000100`$merchantId = '你的服务商户号';// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`$merchantPrivateKeyFilePath = '你的商户私钥路径';// 加载商户私钥$merchantPrivateKeyInstance = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);$merchantCertificateSerial = '商户证书序列号';// API证书不重置,商户证书序列号就是个常量// // 也可以使用openssl命令行获取证书序列号// // openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'// // 或者从以下代码也可以直接加载// // 商户证书,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`// $merchantCertificateFilePath = '/path/to/merchant/apiclient_cert.pem';// // 加载商户证书// $merchantCertificateInstance = PemUtil::loadCertificate($merchantCertificateFilePath);// // 解析商户证书序列号// $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateInstance);// 平台证书,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`$platformCertificateFilePath = '证书保存路径';// 加载平台证书$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);// 解析平台证书序列号$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);// 工厂方法构造一个实例$this->instance = Builder::factory(['mchid' => $merchantId,'serial' => $merchantCertificateSerial,'privateKey' => $merchantPrivateKeyInstance,'certs' => [$platformCertificateSerial => $platformCertificateInstance,],]);}public function pay($sub_mchid,$orderId,$amount,$expireTime,$goodsName,$notifyUrl,$payType,$appId,$openId,$attach){if ($orderId && $sub_mchid && $amount && $notifyUrl && $goodsName) {//从redis,查找缓存的订单支付信息$Redis = new \Redis();$Redis->connect(config('redis')['host'], config('redis')['port']);$Redis->auth(config('redis')['auth']);$Redis->select(1);$res = $Redis->hExists('wechat_' . $orderId, 'payCode');if ($res) {$list = $Redis->hGet('wechat_' . $orderId, 'payCode');$this->result['code'] = 0;$this->result['msg'] = "成功";$this->result['ret_data'] = json_decode($list, true);return $this->result;}if($payType== '1'){$pay_type = 'native';}elseif ($payType == '2'){$pay_type = 'app';}elseif ($payType == '3' || $payType == '4' ){$pay_type = 'jsapi';}else{$pay_type = 'h5';}try {//根据不通的支付方式加载对应参数$params = ['sp_appid' => $appId,'sp_mchid' => config('sp_mchid'),'out_trade_no' => $orderId,'sub_appid' => $appId,'sub_mchid' => $sub_mchid,'description' => $goodsName,'notify_url' => $notifyUrl,'amount' => ['total' => $amount * 100,'currency' => 'CNY'],'settle_info' => ['profit_sharing' => false]];if(!empty($attach)){$params['attach'] = $attach;}if($pay_type == 'jsapi'){$params['payer']['sub_openid'] = $openId;$params['scene_info']['payer_client_ip'] = self::get_realIp();}elseif ($pay_type == 'h5'){$params['scene_info']['payer_client_ip'] = self::get_realIp();$params['scene_info']['h5_info']['type'] = 'Wap';}//var_dump($params);die;$resp = $this->instance->v3->pay->partner->transactions->$pay_type->post(['json' => $params]);if ($resp->getStatusCode() == 200 && $resp->getReasonPhrase() == 'OK') {//wx_log('pay','post_param',json_encode($param)."\n\r",'paylog');$result = [];$payCode = [];$re = json_decode($resp->getBody(), true);if ($pay_type == 'jsapi' && isset($re['prepay_id'])) {$payCode['appId'] = $appId;$payCode['timeStamp'] = (string)time();$payCode['nonceStr'] = uniqid();$payCode['package'] = 'prepay_id=' . $re['prepay_id'];$payCode['signType'] = 'RSA';$payCode['paySign'] = self::getPaySign($payCode);} elseif ($pay_type == 'native' && isset($re['code_url'])) {$payCode = $re['code_url'];} elseif ($pay_type == 'h5' && isset($re['h5_url'])) {$payCode = $re['h5_url'];}elseif ($pay_type == 'app' && isset($re['prepay_id'])){$payCode['appId'] = $appId;$payCode['timeStamp'] = (string)time();$payCode['nonceStr'] = uniqid();$payCode['package'] = 'prepay_id=' . $re['prepay_id'];$payCode['signType'] = 'RSA';$payCode['paySign'] = self::getPaySign($payCode);}$result['orderId'] = $orderId;$result['uniqueOrderNo'] = '';$result['prePayTn'] = $payCode;$log = ['order_id' => $orderId,'post_data' => json_encode($params, JSON_UNESCAPED_UNICODE),'response_data' => json_encode($result, JSON_UNESCAPED_UNICODE),'create_time' => date('Y-m-d H:i:s'),];//write logDb::table('pay_log')->insert($log);//存入redis$Redis->hSet('wechat_'.$orderId,'payCode',json_encode($result,JSON_UNESCAPED_UNICODE));//设置过期时间if (!empty($expireTime)) {$ttl = strtotime($expireTime) - time() - 10;$Redis->expire('wechat_' . $orderId, $ttl);} else {$Redis->expire('wechat_' . $orderId, 7100);}$this->result['code'] = 0;$this->result['msg'] = '成功';$this->result['ret_data'] = $result;}/*echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL;echo $resp->getBody(), PHP_EOL;die;*/} catch (\Exception $e) {// 进行错误处理//echo $e->getMessage(), PHP_EOL;$this->result['msg'] = $e->getMessage();if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {$r = $e->getResponse();//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;$this->result['msg'] = json_decode($r->getBody()->getContents(), true)['message'];//var_dump($this->result['msg']);//echo $r->getBody()->getContents();}}} else {$this->result['msg'] = "参数有误!";}return $this->result;}//前端小程序签名public static function getPaySign($result){$merchantPrivateKeyFilePath = '你的商户私钥路径';// 加载商户私钥$private_key = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);$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 get_realIp(){//strcasecmp 比较两个字符,不区分大小写。返回0,>0,<0。if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {$ip = getenv('HTTP_CLIENT_IP');} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {$ip = getenv('HTTP_X_FORWARDED_FOR');} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {$ip = getenv('REMOTE_ADDR');} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {$ip = $_SERVER['REMOTE_ADDR'];}$res = preg_match ( '/[\d\.]{7,15}/', $ip, $matches ) ? $matches [0] : '';return $res;}}//查询订单public function queryOrder($orderId){if($orderId){$record = Db::table('pay_record')->where("order_id = $orderId ")->find();if(empty($record)){$this->result['msg'] = '未找到该订单';return $this->result;}$sub_mchid = $record['mch_app_id'];$res = $this->instance->v3->pay->partner->transactions->outTradeNo->{'{out_trade_no}'}->getAsync([// 查询参数结构'query' => ['sp_mchid'=>config('sp_mchid'), 'sub_mchid'=>"$sub_mchid"],// uri_template 字面量参数'out_trade_no' => $orderId,])->then(static function($response) {// 正常逻辑回调处理/*echo $response->getStatusCode(),PHP_EOL;echo $response->getReasonPhrase();echo $response->getBody()->getContents(), PHP_EOL;*/$data = [];if($response->getStatusCode() == 200 && $response->getReasonPhrase() == 'OK'){$data['code'] = 0;$data['msg'] = '成功';$data['list'] = json_decode($response->getBody()->getContents(),true);return $data;}})->otherwise(static function($e) {// 异常错误处理//echo $e->getMessage();$data = [];$data['msg'] = '接口异常,请稍后重试';if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {$r = $e->getResponse();//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;//$this->msg = json_decode($r->getBody()->getContents(),true)['message'];$data['msg'] = json_decode($r->getBody()->getContents(),true)['message'];}return $data;})->wait();//var_dump($res);die;if(!empty($res) && isset($res['list'])){$this->result['ret_data'] = $res;}else{$this->result['msg'] = $res['msg'];}}else {$this->result['msg'] = "orderId不能为空!";}return $this->result;}//退款
public function refund($orderId,$refundId,$refundAmount,$sub_mchid,$total,$notifyUrl){$res = $this->instance->chain('v3/refund/domestic/refunds')->postAsync(['json' => ['sub_mchid'=> strval($sub_mchid),'out_trade_no' => $orderId,'out_refund_no' => $refundId,'notify_url' => $notifyUrl,'amount' => ['refund' => $refundAmount*100,'total' => $total*100,'currency' => 'CNY',],],])->then(static function($response) {// 正常逻辑回调处理/*echo $response->getStatusCode(),PHP_EOL;echo $response->getReasonPhrase();echo $response->getBody()->getContents(), PHP_EOL;die;*/$data = [];if($response->getStatusCode() == 200 && $response->getReasonPhrase() == 'OK'){$data['code'] = 0;$data['msg'] = '成功';$data['list'] = json_decode($response->getBody()->getContents(),true);return $data;}})->otherwise(static function($e) {// 异常错误处理//echo $e->getMessage();die;$data = [];$data['msg'] = '接口异常,请稍后重试';if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {$r = $e->getResponse();//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;//$this->msg = json_decode($r->getBody()->getContents(),true)['message'];$data['msg'] = json_decode($r->getBody()->getContents(),true)['message'];return $data;}})->wait();if(!empty($res) && isset($res['list'])){$this->result['ret_data'] = $res;}else{$this->result['msg'] = $res['msg'];}return $this->result;}//支付回调 public function wechatBack(){/*判断请求类型(GET、POST、PUT、DELETE)*/switch (strtolower($this->request->method())){case "post":$statuscode = 500;$platformCertificateFilePath = '平台证书路径';// 加载平台证书$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);// 解析平台证书序列号$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);/*接收接口请求参数*/$response = file_get_contents('php://input');$data = json_decode(file_get_contents('php://input'),true);$signature = $this->request->header('Wechatpay-Signature');$timestamp = $this->request->header('Wechatpay-Timestamp');$nonce = $this->request->header('Wechatpay-Nonce');$serial = $this->request->header('Wechatpay-Serial');//平台证书序列号验证if ($serial == $platformCertificateSerial) {//签名验证if (Crypto\Rsa::verify(Formatter::response($timestamp, $nonce, $response), $signature, $platformCertificateInstance)) {//file_put_contents('./cert/wechatpaylog1.log', "Rsa:".'签名验证成功'."\n\r",FILE_APPEND);if(!empty($data) && $data['event_type'] == 'TRANSACTION.SUCCESS' && $data['resource_type'] == 'encrypt-resource'){$associated_data = $data['resource']['associated_data'];$nonce = $data['resource']['nonce'];$ciphertext = $data['resource']['ciphertext'];$apiv3Key = config('apiv3Key');//var_dump($apiv3Key);die;$list = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $associated_data);$list = json_decode($list,true);if(!empty($list)){//file_put_contents('./cert/wechatpaylog1.log', "解密成功list: ".json_encode($list,JSON_UNESCAPED_UNICODE)."\n\r",FILE_APPEND);//解密成功做业务逻辑代码if($list['trade_state'] == 'SUCCESS'){try {$record = Db::table('pay_record')->where("order_id = '$list[out_trade_no]' ")->find();if(empty($record)){$this->msg = '未找到该订单';break;}/*****这里写你的订单处理逻辑代码**///file_put_contents('./cert/wechatpaylog1.log', "msg:".'SUCCESS'."\n\r",FILE_APPEND);} catch (\Exception $e) {//file_put_contents('./cert/wechatpaylog1.log', "订单逻辑处理失败:".$e->getMessage()."\n\r",FILE_APPEND);$statuscode = 500;}$log = ['type' => 2,'order_id' => $list['out_trade_no'],'post_data' => json_encode($list,JSON_UNESCAPED_UNICODE),'response_data' => $this->msg,'create_time' => date('Y-m-d H:i:s')];Db::table('pay_log')->insert($log);}}}}}break;default:$statuscode = 500;$this->msg = "请求方式错误";break;}/*定义接口返回数据*/$this->success($this->msg,$this->ret_data,$this->code,$this->total,'',array('statuscode'=>$statuscode));}
以上demo包含了最基本的支付、支付查询、退款、支付回调model,在使用的时候稍加修改就可以完美的实现微信服务商支付的功能了。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
