IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 微信服务商V3版支付 -> 正文阅读

[开发测试]微信服务商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,在使用的时候稍加修改就可以完美的实现微信服务商支付的功能了。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章           查看所有文章
加:2022-03-22 20:54:32  更:2022-03-22 20:55:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 0:15:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码