微信服务商V3版支付

一、前言

首先去微信支付服务商平台查看相关接口和官方提供的sdk。网址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay6_0.shtml,了解清楚支付需要的参数和需要申请的微信服务商相关信息。

二、开始demo

  1. 我使用的是php语言,用的thinkphp框架,所以使用composer直接拉去SDK就可以。composer require wechatpay/wechatpay
  2. 按照sdk的约定去堆积代码块,做好参数验证就可以了。
本类库是以 `OpenAPI` 对应的接入点 `URL.pathname``/`做切分,映射成`segments`<sup>[RFC3986](#note-rfc3986)</sup>,编码书写方式有如下约定:

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)</sup>,例如 `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.一个简单的聚合支付例子。

<?php

/*定义文件命名空间*/
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 log
                    Db::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,在使用的时候稍加修改就可以完美的实现微信服务商支付的功能了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 登录微信支付服务商平台,进入“API商户平台”界面,在左侧菜单栏中选择“开发配置”,点击“APIv3密钥”页面中的“下载证书”按钮,下载证书至本地。 2. 在你的应用中引入微信支付官方提供的SDK文件,包括wxpay.js和wxpay.php两个文件。将这两个文件放置在正确的路径下,确保能够被应用调用。 3. 在你的代码中引入wxpay.php文件,使用以下代码初始化微信支付服务商: ```php require_once "wxpay.php"; $config = array( 'mch_id' => '你的服务商商户号', 'appid' => '你的服务商公众号appid', 'key' => '你的服务商支付密钥', 'cert_path' => '你下载的证书文件路径/apiclient_cert.pem', 'key_path' => '你下载的证书文件路径/apiclient_key.pem' ); $wxpay = new Wxpay($config); ``` 4. 确定用户需要支付的金额和商品信息等相关信息,构建支付请求参数,例如: ```php $params = array( 'body' => '商品描述', 'out_trade_no' => '商户订单号', 'total_fee' => 100, // 单位:分 'notify_url' => '支付回调通知地址', 'trade_type' => 'JSAPI', 'openid' => '用户openid', 'sub_mch_id' => '子商户号' ); ``` 其中,'trade_type' => 'JSAPI'表示使用JSAPI支付方式,'openid' => '用户openid'表示用户在公众号中的openid,'sub_mch_id' => '子商户号'表示子商户号。 5. 调用统一下单接口,生成预支付订单。例如: ```php $result = $wxpay->unifiedOrder($params); ``` 6. 根据返回结果,生成JSAPI支付所需的参数,例如: ```php $order_params = array( 'appId' => $wxpay->appId, 'timeStamp' => time(), 'nonceStr' => $wxpay->getNonceStr(), 'package' => 'prepay_id=' . $result['prepay_id'], 'signType' => 'RSA', ); $order_params['paySign'] = $wxpay->getPaySign($order_params); // 生成签名 ``` 其中,'appId'、'timeStamp'、'nonceStr'、'package'、'signType'都是JSAPI支付所需的参数,'paySign'为签名值。 7. 将生成的JSAPI支付所需的参数返回给前端,用于调起微信支付界面。 8. 在支付完成后,微信会向你的'notify_url'地址发送支付结果通知。在该地址中,需要对支付结果进行处理,例如: ```php $xml = file_get_contents('php://input'); $result = $wxpay->notify($xml); // 处理支付结果 if ($result) { // 支付成功 } else { // 支付失败 } ``` 以上为微信服务商支付V3引入官方SDK的教程。需要注意的是,在使用微信支付服务商支付时,需要先在微信支付服务商平台中完成相关配置,包括商户号、支付密钥、证书等。同时,还需要在调用支付接口前获取用户的openid等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值