Thinkphp6接入微信支付V3版电商收付通简单示例

开发框架:thinkphp6

Api封装类:extend/library/WxPay.php

<?php
namespace library;

class WxPay
{
    //appId
    private $appId;
    //appSecret
    private $appSecret;
    //商户号
    private $mchId;
    //商户密钥
    private $mchKey;
    //本地路径
    private $path;
    //证书文件
    private $sslCert = 'apiclient_cert.pem';
    private $sslKey = 'apiclient_key.pem';
    private $platCert = 'plat_cert.json';
    //api地址
    private $rootUrl = 'https://api.mch.weixin.qq.com';
    //api证书序列号
    private $serialNo;
    //商户私钥
    private $privateKey;
    //v3密钥
    private $platKey;
    //平台证书序列号
    private $platSerialNo;
    //平台密钥
    private $platPublicKey;
    //加密方式
    private $signAlg = 'sha256WithRSAEncryption';
    private $schema = 'WECHATPAY2-SHA256-RSA2048';
    //主机头
    private $httpHeader;
    //请求类型
    private $method = 'GET';
    //api接口
    private $panel = '';
    //随机字符串
    public $nonce = '';
    //时间戳
    public $stamp = 0;
    //报文主体
    public $body = '';
    //签名
    public $signature = '';
    /**
     * 构造函数
     */
    public function __construct(array $config)
    {
        $this->appId = isset($config['appId']) ? $config['appId'] : '';
        $this->appSecret = isset($config['appSecret']) ? $config['appSecret'] : '';
        $this->mchId = isset($config['mchId']) ? $config['mchId'] : '';
        $this->mchKey = isset($config['mchKey']) ? $config['mchKey'] : '';
        $this->path = str_replace('\\', '/', ROOT_PATH);
        $this->sslCert = $this->path . (isset($config['sslCert']) ? $config['sslCert'] : $this->sslCert);
        $this->sslKey = $this->path . (isset($config['sslKey']) ? $config['sslKey'] : $this->sslKey);
        $this->platCert = $this->path . (isset($config['platCert']) ? $config['platCert'] : $this->platCert);
        $folder = dirname($this->platCert);
        if (!is_dir($folder)) {
            mkdir($folder, 0755, true);
        }
        $this->serialNo = isset($config['serialNo']) ? $config['serialNo'] : '';
        $this->platKey = isset($config['platKey']) ? $config['platKey'] : '';
        $this->getPrivateKey();
        $this->getCertificates();
    }
    /**
     * 预支付
     */
    public function prepay(array $data)
    {
        $data['appid'] = $this->appId;
        $data['mchid'] = $this->mchId;
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/pay/transactions/jsapi';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * JSAPI支付
     */
    public function invoke($preid)
    {
        $this->body = 'prepay_id=' . $preid;
        $this->buildSignature();
        $data = array(
            'timeStamp' => $this->stamp,
            'nonceStr' => $this->nonce,
            'package' => $this->body,
            'signType' => 'RSA',
            'paySign' => $this->signature,
        );
        return $data;
    }
    /**
     * 原生支付
     */
    public function native(array $data)
    {
        $data['sp_appid'] = $this->appId;
        $data['sp_mchid'] = $this->mchId;
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/pay/partner/transactions/native';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 合单
     */
    public function combine(array $data)
    {
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/combine-transactions/native';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 退款
     */
    public function refund(array $data)
    {
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/refund/domestic/refunds';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 余额查询
     */
    public function balance($sub_mchid)
    {
        $this->method = 'GET';
        $this->panel = '/v3/ecommerce/fund/balance/' . $sub_mchid;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->get($url), true);
        return $result;
    }
    /**
     * 交易账单下载
     */
    public function bill($sub_mchid, $date = '')
    {
        if (!$date) {
            $date = date('Y-m-d', strtotime('-1 day'));
        }
        $this->method = 'GET';
        $this->panel = '/v3/bill/tradebill?bill_date=' . $date . '&sub_mchid=' . $sub_mchid . '&bill_type=ALL';
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->get($url), true);
        if (isset($result['download_url'])) {
            $this->panel = str_replace($this->rootUrl, '', $result['download_url']);
            $this->buildHeader();
            $result = $this->get($result['download_url']);
        }
        return $result;
    }
    /**
     * 上传图片
     */
    public function upload($file)
    {
        if (!$file && is_file($file)) {
            return false;
        }
        if (!in_array(pathinfo($file, PATHINFO_EXTENSION), array('jpg', 'png', 'bmp'))) {
            return false;
        }
        $file = str_replace('\\', '/', $file);
        $info = new \finfo(FILEINFO_MIME_TYPE);
        $mime = $info->file($file);
        $name = basename($file);
        $stream = file_get_contents($file);
        $meta = json_encode(array(
            'filename' => $name,
            'sha256' => hash_file('sha256', $file),
        ));
        $boundary = uniqid();
        $this->method = 'POST';
        $this->panel = '/v3/merchant/media/upload';
        $this->body = $meta;
        $this->httpHeader = array(
            'Accept: application/json',
            'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'],
            'Authorization: ' . $this->buildAuthorization(),
            'Content-Type: multipart/form-data;boundary=' . $boundary,
        );
        $url = $this->rootUrl . $this->panel;
        $sep = '--' . $boundary . "\r\n";
        $out = $sep;
        $out .= 'Content-Disposition: form-data; name="meta";' . "\r\n";
        $out .= 'Content-Type: application/json' . "\r\n";
        $out .= "\r\n";
        $out .= $meta . "\r\n";
        $out .= $sep;
        $out .= 'Content-Disposition: form-data; name="file"; filename="' . $name . '";' . "\r\n";
        $out .= 'Content-Type: ' . $mime . "\r\n";
        $out .= "\r\n";
        $out .= $stream . "\r\n";
        $out .= '--' . $boundary . '--' . "\r\n";
        $result = json_decode($this->post($url, $out), true);
        return $result;
    }
    /**
     * 二级商户进件
     */
    public function applyment(array $data)
    {
        if (!isset($data['id_card_info']['id_card_name'])) {
            return false;
        }
        if (!isset($data['id_card_info']['id_card_number'])) {
            return false;
        }
        if (!isset($data['account_info']['account_name'])) {
            return false;
        }
        if (!isset($data['account_info']['account_number'])) {
            return false;
        }
        if (!isset($data['contact_info']['contact_name'])) {
            return false;
        }
        if (!isset($data['contact_info']['contact_id_card_number'])) {
            return false;
        }
        if (!isset($data['contact_info']['mobile_phone'])) {
            return false;
        }
        if (!isset($data['contact_info']['contact_email'])) {
            return false;
        }
        $data['appid'] = $this->appId;
        $data['id_card_info']['id_card_name'] = $this->encrypt($data['id_card_info']['id_card_name']);
        $data['id_card_info']['id_card_number'] = $this->encrypt($data['id_card_info']['id_card_number']);
        $data['account_info']['account_name'] = $this->encrypt($data['account_info']['account_name']);
        $data['account_info']['account_number'] = $this->encrypt($data['account_info']['account_number']);
        $data['contact_info']['contact_name'] = $this->encrypt($data['contact_info']['contact_name']);
        $data['contact_info']['contact_id_card_number'] = $this->encrypt($data['contact_info']['contact_id_card_number']);
        $data['contact_info']['mobile_phone'] = $this->encrypt($data['contact_info']['mobile_phone']);
        $data['contact_info']['contact_email'] = $this->encrypt($data['contact_info']['contact_email']);
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/ecommerce/applyments/';
        $this->body = $data;
        $this->buildHeader(true);
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 二级商户审核状态查询
     */
    public function auditState($no, $type = 0)
    {
        if (!$no) {
            return false;
        }
        $this->method = 'GET';
        $this->panel = '/v3/ecommerce/applyments/' . ($type ? 'out-request-no/' : '') . $no;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->get($url), true);
        return $result;
    }
    /**
     * 添加分账接收方
     */
    public function addReceivers(array $data)
    {
        if (!isset($data['name'])) {
            return false;
        }
        $data['appid'] = $this->appId;
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/ecommerce/profitsharing/receivers/add';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 分账
     */
    public function divide(array $data)
    {
        if (!$data && is_array($data)) {
            return false;
        }
        $data['appid'] = $this->appId;
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $this->method = 'POST';
        $this->panel = '/v3/ecommerce/profitsharing/orders';
        $this->body = $data;
        $this->buildHeader();
        $url = $this->rootUrl . $this->panel;
        $result = json_decode($this->post($url, $data), true);
        return $result;
    }
    /**
     * 敏感字段加密
     */
    public function encrypt($str)
    {
        $encrypted = '';
        if (openssl_public_encrypt($str, $encrypted, $this->platPublicKey, OPENSSL_PKCS1_OAEP_PADDING)) {
            $sign = base64_encode($encrypted);
        } else {
            throw new Exception('encrypt failed');
        }
        return $sign;
    }
    /**
     * 报文解密
     */
    public function decrypt($text, $assoc, $nonce)
    {
        $check = extension_loaded('sodium');
        if (false === $check) {
            return false;
        }
        $check = sodium_crypto_aead_aes256gcm_is_available();
        if (false === $check) {
            return false;
        }
        return sodium_crypto_aead_aes256gcm_decrypt(base64_decode($text), $assoc, $nonce, $this->platKey);
    }
    /**
     * 生成Header
     */
    private function buildHeader($usePlatSerial = false)
    {
        $this->httpHeader = array(
            'Accept: application/json',
            'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'],
            'Content-Type: application/json',
        );
        if ($usePlatSerial) {
            $this->httpHeader[] = 'Wechatpay-Serial: ' . $this->platSerialNo;
        }
        $this->httpHeader[] = 'Authorization: ' . $this->buildAuthorization();
        if ($this->body) {
            $this->httpHeader[] = 'Content-Length: ' . strlen($this->body);
        }
    }
    /**
     * 生成Authorization
     */
    private function buildAuthorization()
    {
        $this->signer();
        $str = 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"';
        $str = sprintf($str, $this->mchId, $this->nonce, $this->stamp, $this->serialNo, $this->signature);
        return $this->schema . ' ' . $str;
    }
    /**
     * 签名生成器
     */
    private function signer()
    {
        $this->stamp = time();
        $this->nonce = $this->buildNonceStr();
        $message = $this->method . "\n" . $this->panel . "\n" . $this->stamp . "\n" . $this->nonce . "\n" . $this->body . "\n";
        openssl_sign($message, $signature, $this->privateKey, $this->signAlg);
        $this->signature = base64_encode($signature);
    }
    /**
     * 生成JSSDK签名
     */
    private function buildSignature()
    {
        $this->stamp = time();
        $this->nonce = $this->buildNonceStr();
        $message = $this->appId . "\n" . $this->stamp . "\n" . $this->nonce . "\n" . $this->body . "\n";
        openssl_sign($message, $signature, $this->privateKey, $this->signAlg);
        $this->signature = base64_encode($signature);
    }
    /**
     * 获取商户私钥
     */
    private function getPrivateKey()
    {
        if (is_file($this->sslKey)) {
            $this->privateKey = openssl_pkey_get_private(file_get_contents($this->sslKey));
        }
    }
    /**
     * 获取平台证书
     */
    private function getCertificates()
    {
        if (is_file($this->platCert)) {
            $json = file_get_contents($this->platCert);
            $data = json_decode($json, true);
            if (isset($data['serial_no'])) {
                $this->platSerialNo = $data['serial_no'];
            }
            if (isset($data['cipher'])) {
                $this->platPublicKey = $data['cipher'];
            }
        }
        if (!($this->platSerialNo && $this->platPublicKey)) {
            $this->method = 'GET';
            $this->panel = '/v3/certificates';
            $this->buildHeader();
            $url = $this->rootUrl . $this->panel;
            $result = json_decode($this->get($url), true);
            if ($result && isset($result['data']) && $result['data']) {
                $data = $result['data'][0];
                $serial_no = $data['serial_no'];
                $cert = $data['encrypt_certificate'];
                $cipher = $this->decrypt($cert['ciphertext'], $cert['associated_data'], $cert['nonce']);
                $this->platSerialNo = $serial_no;
                $this->platPublicKey = $cipher;
                $json = json_encode(array(
                    'serial_no' => $serial_no,
                    'cipher' => $cipher,
                ));
                file_put_contents($this->platCert, $json);
            }
        }
    }
    /*
     * 生成随机字符串
     */
    private function buildNonceStr($len = 32)
    {
        $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $str = '';
        for ($i = 0; $i < $len; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    /*
     * get请求
     */
    private function get($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->httpHeader);
        $out = curl_exec($ch);
        curl_close($ch);
        return $out;
    }
    /*
     * post请求
     */
    private function post($url, $data = '')
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->httpHeader);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        curl_close($ch);
        return $result;
    }
    /**
     * 获取客户端ip
     */
    public function getClientIp()
    {
        if (getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
        } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        } elseif (getenv('HTTP_X_FORWARDED')) {
            $ip = getenv('HTTP_X_FORWARDED');
        } elseif (getenv('HTTP_FORWARDED_FOR')) {
            $ip = getenv('HTTP_FORWARDED_FOR');
        } elseif (getenv('HTTP_FORWARDED')) {
            $ip = getenv('HTTP_FORWARDED');
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return $ip;
    }
}

交易流程:

Step1:上传二级商户营业执照、身份证正反面照片(upload)

Step2:二级商户进件(applyment)

Step3:查询二级商户进件状态,审核通过后邀请二级商户签约(auditState)

Step4:添加分账接收方(addReceivers)

Step5:用户下单付款,指定分账信息(native、combine)

Step6:交易成功发起分账(divide)

使用示例:Sample.php

<?php
namespace app\[module]\controller;

use library\WxPay;
use think\App;

class Sample extends Base
{
    protected $app;
    private $wxpay;
    /**
     * 构造方法
     */
    public function __construct(App $app)
    {
        parent::__construct($app);
        $this->wxpay = new WxPay(array(
            'appId' => '<appId>',
            'appSecret' => '<appSecret>',
            'mchId' => '<mchId>',
            'mchKey' => '<mchKey>',
            'serialNo' => '<serialNo>',
            'platKey' => '<platKey>',
        ));
    }
    /**
     * 上传图片示例
     */
    public function uploadImage()
    {
        $file = ROOT_PATH . 'sample.png';
        $result = $this->wxpay->upload($file);
        var_dump($result);
    }
    /**
     * 二级商户进件示例
     */
    public function applyment()
    {
        $data = array(
            'out_request_no' => '<内部编号>',
            'organization_type' => '<主体类型>',
            'business_license_info' => array(
                'business_license_copy' => '<上传营业执照得到的MediaID>',
                'business_license_number' => '<营业执照号>',
                'merchant_name' => '<商户名称>',
                'legal_person' => '<法人姓名>',
            ),
            'id_doc_type' => 'IDENTIFICATION_TYPE_MAINLAND_IDCARD',
            'id_card_info' => array(
                'id_card_copy' => '<上传身份证正面得到的MediaID>',
                'id_card_national' => '<上传身份证背面得到的MediaID>',
                'id_card_name' => '<身份证登记姓名>',
                'id_card_number' => '<身份证号>',
                'id_card_valid_time' => '<身份证有效期>',
            ),
            'need_account_info' => true,
            'account_info' => array(
                'bank_account_type' => '<银行账号类型>',
                'account_bank' => '<开户行>',
                'account_name' => '<银行账号开户名>',
                'bank_address_code' => '<开户行地区编号>',
                'bank_name' => '<银行全称>',
                'account_number' => '<银行账号>',
            ),
            'contact_info' => array(
                'contact_type' => '64[|65]',
                'contact_name' => '<超级管理员姓名>',
                'contact_id_card_number' => '<超级管理员身份证号>',
                'mobile_phone' => '<超级管理员手机号>',
                'contact_email' => '<超级管理员电子信箱>',
            ),
            'sales_scene_info' => array(
                'store_name' => '<经营网店名称>',
                'store_url' => '<经营网店URL地址>',
            ),
            'merchant_shortname' => '<商户简称>',
        );
        $result = $this->wxpay->applyment($data);
        var_dump($result);
    }
    /**
     * 二级商户审核状态查询示例
     */
    public function state()
    {
        $request_no = '202101010000';
        $result = $this->wxpay->auditState($request_no);
        var_dump($result);
    }
    /**
     * 添加分账方示例
     */
    public function addReceivers()
    {
        $data = array(
            'type' => 'MERCHANT_ID',
            'account' => '<二级商户号>',
            'name' => '<二级商户全称>',
            'relation_type' => 'SUPPLIER',
        );
        $result = $wxpay->addReceivers($data);
        var_dump($result);
    }
    /**
     * 原生支付示例
     */
    public function native()
    {
        $data = array(
            'sub_mchid' => '<二级商户的商户号>',
            'description' => '<商品描述>',
            'out_trade_no' => '<内部订单号>',
            'notify_url' => '<通知URL>',
            'settle_info' => array(
                'profit_sharing' => true,
            ),
            'amount' => array(
                'total' => '<订单总金额>',
                'currency' => 'CNY',
            ),
            'attach' => '<可选,附加数据>',
        );
        $result = $wxpay->native($data);
        var_dump($result);
    }
    /**
     * 合单支付示例
     */
    public function combine()
    {
        $data = array(
            'combine_appid' => '<合单发起方的appid>',
            'combine_mchid' => '<合单发起方的商户号>',
            'combine_out_trade_no' => '<合单支付总订单号>',
            'scene_info' => array(
                'payer_client_ip' => $this->wxpay->getClientIp(),
            ),
            'sub_orders' => array(
                array(
                    'mchid' => '<子单发起方的商户号>',
                    'attach' => '<附加数据>',
                    'amount' => array(
                        'total_amount' => 1,
                        'currency' => 'CNY',
                    ),
                    'out_trade_no' => '<子单商户订单号>',
                    'sub_mchid' => '<二级商户号>',
                    'description' => '<商品描述>',
                    'settle_info' => array(
                        'profit_sharing' => true,
                    ),
                ),
            ),
            'notify_url' => 'https://www.xxx.com/notify.php',
        );
        $result = $wxpay->combine($data);
        var_dump($result);
    }
    /**
     * 退款示例
     */
    public function refund()
    {
        $data = array(
            'sub_mchid' => '<二级商户号>',
            'transaction_id' => '<微信支付订单号>',
            'out_trade_no' => '<内部订单号>',
            'out_refund_no' => '<内部退款单号>',
            'amount' => array(
                'refund' => '<交易总金额>',
                'total' => '<退款金额>',
                'currency' => 'CNY',
            ),
        );
        $result = $wxpay->refund($data);
        var_dump($result);
    }
    /**
     * 分账示例
     */
    public function divide()
    {
        $data = array(
            'sub_mchid' => '<二级商户号>',
            'transaction_id' => '<微信支付订单号>',
            'out_order_no' => '<内部订单号>',
            'receivers' => array(
                array(
                    'type' => '<分账接收方类型>',
                    'account' => '<分账接收方账号>',
                    'amount' => '<分账金额>',
                    'description' => '<分账描述>',
                ),
            ),
            'finish' => true,
        );
        $result = $wxpay->divide($data);
        var_dump($result);
    }
    /**
     * 余额查询
     */
    public function balance()
    {
        $result = $wxpay->balance('<二级商户号>');
        var_dump($result);
    }
    /**
     * 下载账单
     */
    public function bill()
    {
        $date = date('Y-m-d');
        $result = $wxpay->bill('<二级商户号>', $date);
        var_dump($result);
    }
}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xmode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值