微信小程序支付 V3


流程

准备 APPID,商户ID,证书号,回调地址,请求JSApi地址,V3密匙

设置设置商品信息,金额

将参数进行 签名算法,向微信小程序发送支付请求,获取prepay_id。同时将获取的数据再次进行相应规则的签名

小程序获取到参数后,发起微信支付

支付结果调用回调接口,返回支付结果

1. 接入前准备

支付开发指引-小程序支付文档

'mini' => [
    'url'        => 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi',   // 请求地址
    'app_id'     => 'wxee5904f2b3b6b762',   // APP ID
    'mch_id'     => '1629935040',  // 商户ID
    'serial_no'  => '346AF1FA3B3D820A6F2B999D7E62FDE838C65B6E',  // 证书号
    'notify_url' => 'https://xjsxy.leadsvip.cn/api/pay/wechartCallback', // 回调地址
    'api_v3_Key' => '48bojx563qkptu1aw2siglcz9erhfyvm',   // V3 密匙(在微信商户平台设置)
]

2. 请求支付接口

public function _createOrder($params) {

    try {
        DB::beginTransaction();

        $order = new Order;

        $order->user_id = $params['user_id'];
        $order->order_phone = $params['order_phone'];
        $order->order_name = $params['order_name'];
        $order->product_id = $params['product_id'];
        $order->product_type = $params['product_type'];
        $order->price = $params['price'];
        $order->score = $params['score'];
        $order->order_no = $params['order_no'];
        $order->state = $params['state'];
        $order->save();

        Log::channel("miniwechart")->info('订单创建成功');
        $result = $this->_miniWeixinPay($params);
        if(!$result) {
            DB::rollBack();
            throw new \Exception('请求错误');
        }
        DB::commit();
    } catch (\Exception $e) {
        DB::rollBack();
        return '';
    }
    return $result;
}


public function _miniWeixinPay($params) {

    Log::channel("miniwechart")->info('调用微信支付');

    $url     = config('pay.mini.url');
    $urlarr  = parse_url($url);
    $appid   = config('pay.mini.app_id');
    $mchid   = config('pay.mini.mch_id');
    $xlid    = config('pay.mini.serial_no');
    $randstr = Util::getRanStr(16);
    $time    = time();

    $data                    = array();
    $data['appid']           = $appid;
    $data['mchid']           = $mchid;
    $data['description']     = $params['product_name'];   // 商品描述
    $data['out_trade_no']    = $params['order_no'];   // 订单号
    $data['notify_url']      = config('pay.mini.notify_url');   // 回调接口
    $data['amount']['total'] = 1;  // 金额
    $data['payer']['openid'] = $params['open_id'];   // 用户openID
    $data = json_encode($data);

    Log::channel("miniwechart")->info('生成参数_'.$data);

    $key    = $this->getSign($data, $urlarr['path'], $randstr, $time); // 签名
    $token  = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"', $mchid, $xlid, $randstr, $time, $key);   // 生成 Token

    $header = array(
        'Accept: application/json',
        'Content-Type: application/json; charset=UTF-8',
        'User-Agent: */*',
        'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
    );
    $result = Util::curlPostHttps($url, $data, $header);   // POST 请求
    
    Log::channel("miniwechart")->info('请求结果_'.$result);

    $disposePrepayId = json_decode($result, true);
    $prepay_id = $disposePrepayId['prepay_id'];

    $paySign = $this->paySignBody($appid, $time, $randstr, $prepay_id);   // 构造对prepay_id再次签名的签名数据主体

    $res = json_decode($data, true);
    $getPayData = $this->requestPayData($randstr, $prepay_id, $paySign, $time, $res['out_trade_no']);   // 返回小程序数据
    return $getPayData;
}


// CURL POST 请求
public static function curlPostHttps($url, $data = [],$header=null, $timeout = 3){
    $ch = curl_init();
    if($header){
        curl_setopt($ch, CURLOPT_HTTPHEADER,$header);
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置超时
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}


// 生产随机字符串
public static function getRanStr($length) {
    $str     = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $len     = strlen($str)-1;
    $randstr = '';
    for ($i=0; $i<$length; $i++) {
        $num = mt_rand(0,$len);
        $randstr .= $str[$num];
    }
    return $randstr;
}


// 生成签名数据主体
// $url 去除域名后的url
// $randstr 随机数
// $data 请求报文主体
public function getSign($data, $url, $randstr, $time) {
    $str = "POST"."\n".$url."\n".$time."\n".$randstr."\n".$data."\n";
    $key = file_get_contents(storage_path('app/public/apiclient_key.pem')); // 在商户平台下载的秘钥
    $str = $this->getSha256WithRSA($str,$key);
    return $str;
}


// 生成签名值
public function getSha256WithRSA($content, $private_key) {
    $private_key = openssl_pkey_get_private($private_key);
    openssl_sign($content, $signature, $private_key, "SHA256");
    openssl_free_key($private_key);
    $sign = base64_encode($signature);
    return $sign;
}


// 构造对prepay_id再次签名的签名数据主体
public function paySignBody($appid, $time, $randstr, $prepay_id) {

    $str = $appid."\n".$time."\n".$randstr."\n"."prepay_id=$prepay_id"."\n";
    $key = file_get_contents(storage_path('app/public/apiclient_key.pem')); // 在商户平台下载的秘钥
    $str = $this->getSha256WithRSA($str,$key);
    return $str;
}


// 返回小程序数据
public function requestPayData($nonce_str, $prepay_id, $paySign, $time, $out_trade_no) {
    return [
        'nonce_str' => $nonce_str,
        'package' => 'prepay_id='.$prepay_id,
        'paySign' => $paySign,
        'timeStamp' => $time,
//            'signType' => 'RSA',
        'signType' => 'MD5',
        'out_trade_no' => $out_trade_no,
        'app_id' => config('pay.mini.app_id'),
        'mch_id' => config('pay.mini.mch_id'),
    ];
}

3. 回调接口

public function Callback() {

    $xml = file_get_contents('php://input');   // 获取数据
    if (empty($xml)) {
        Log::channel("miniwechart")->info('weixin-notify:返回数据为空'. $xml);
        exit;
    }

    Log::channel("miniwechart")->info('weixin-notify:打印数据'. $xml);

    $orderData = $this->wechartDecrypt($xml);   // 解密数据

    $orderServer = new OrderService();
    $result = $orderServer->_verifyOrder($orderData);   // 验证订单数据

    // 回调成功
    if ($result) {
        $this->replyNotify();
    }
}


// 验证订单
public function _verifyOrder($data) {

    if ($data['appid'] != config('pay.mini.app_id')) {
        Log::channel("miniwechart")->info('APPID错误');
        return false;
    }

    $tradeNo = $data['out_trade_no'] ?? '';
    $orderInfo = Order::query()->where('order_no', $tradeNo)->first();
    if(!$orderInfo) {
        Log::channel("miniwechart")->info('回调未查询出订单');
        return false;
    }

    if ($data['trade_state'] == 'SUCCESS') {
        Log::channel("miniwechart")->info('支付成功, 开始处理回调数据');
        if ($this->_afterNotifyFirst(config('pay.pay_type_wexin'), $orderInfo['id'], $data['transaction_id'])) {
            return true;
        }
    }
    return false;
}


 // 回调保存数据
public function _afterNotifyFirst($type, $orderId, $transaction_id) {
    Log::channel("miniwechart")->info('开始处理回调数据');
    $orderModel = order::query()->find($orderId);

    $orderModel->pay_type       = $type;
    $orderModel->state          = config('pay.pay_state_paid');
    $orderModel->transaction_id = $transaction_id;

    $orderModel->save();
    return true;
}


// 解密数据
// 需要php扩展 sodium
public function wechartDecrypt($str) {
    $str = htmlspecialchars_decode($str,ENT_COMPAT);
    $post = json_decode($str,true);
    $key = config("pay.mini.api_v3_Key");
    $text = base64_decode($post['resource']['ciphertext']);

    $str = sodium_crypto_aead_aes256gcm_decrypt($text, $post['resource']['associated_data'], $post['resource']['nonce'], $key);
    return json_decode($str,true);
}


// 回复微信notify方法
public function replyNotify() {
    $xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    echo $xml;
}

4. 支付成功后微信回调发送的数据

{
  "id": "2dd87d80-2478-5ab0-852c-6d20084f8175",
  "create_time": "2022-07-26T17:07:28+08:00",
  "resource_type": "encrypt-resource",
  "event_type": "TRANSACTION.SUCCESS",
  "summary": "支付成功",
  "resource": {
    "original_type": "transaction",
    "algorithm": "AEAD_AES_256_GCM",
    "ciphertext": "此处显示密文,需要经过解密显示",
    "associated_data": "transaction",
    "nonce": "8oAAd7fCPvpO"
  }
}
posted @ 2022-09-27 11:08  linsonga  阅读(988)  评论(0编辑  收藏  举报