Bootstrap

使用wechatpay-guzzle-middleware实现jspai支付(服务商和普通商户)


前言

这个是之前用的,最开始用的curl那个,没找到官方文档,正好看到wechatpay-guzzle-middleware这个sdk有样例,就用的这个。这个也很方便,挺好用的。


一、wechatpay-guzzle-middleware下载

我用的是composer,简单。

composer require wechatpay/wechatpay-guzzle-middleware

二、接入jaspi支付

环境证书配置可以看上一篇:使用wechatpay-php实现JSAPI支付(服务商和普通商户)
这里不再赘述。

1.环境配置

我是把参数写到类初始化里面了。
代码如下(示例):

protected $config;
protected $mch_private_key;
protected $wechatpayCertificate;
protected $schema = 'WECHATPAY2-SHA256-RSA2048';

public function __construct()
{
    $this->config = $this->getConfigGroup('1'); // 我是把很多参数写到配置里面了
    $this->mch_private_key = PemUtil::loadPrivateKey(request()->domain() . "/cert/apiclient_key.pem"); //商户私钥文件路径
    $this->wechatpayCertificate = PemUtil::loadCertificate(request()->domain() . '/cert/wechatpay_70CA41F8C0475EDFE07378C9895FB30FD6xxxxxx.pem'); // 微信支付平台证书文件路径
}

2.请求jsapi接口

代码如下(示例):

    /**
     * @param $order_no 订单号
     * @param $name 商品名称
     * @param $pay_price 支付金额
     * @param $open_id openid
     * @param $notify_url 回调地址
     * @param array $attach 附加参数,这里面可以写使用优惠券之类的业务
     * @return array|void
     * 支付接口
     */
    public function pay($order_no, $name, $pay_price, $open_id, $notify_url, $attach = [])
    {
        try {
            $param = [
                "mchid" => $this->config['mchid'],  // 商户号
                "out_trade_no" => $order_no,
                "appid" => $this->config['appid'], // 微信小程序appid
                "description" => "购买" . $name,
                "notify_url" => $notify_url,
                "amount" => [
                    "total" => intval($pay_price * 100),  // 金额,单位分
                    "currency" => "CNY",
                ],
                "payer" => [
                    "openid" => $open_id,
                ],
                "attach" => json_encode($attach, JSON_UNESCAPED_UNICODE),
            ];
            $authorization = $this->getAuthorization("POST", json_encode($param, JSON_UNESCAPED_UNICODE)); // 这个跟上篇写法基本一样,下面我会再更新下

            // 商户相关配置
            $merchantId = $this->config['mchid']; // 商户号
            $merchantSerialNumber = $this->config['serial_no']; // 商户API证书序列号
            $merchantPrivateKey = $this->mch_private_key; // 商户私钥文件路径

            // 微信支付平台配置
            $wechatpayCertificate = $this->wechatpayCertificate; // 微信支付平台证书文件路径

            // 构造一个WechatPayMiddleware
            $wechatpayMiddleware = WechatPayMiddleware::builder()
                ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
                ->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array
                ->build();

            // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
            $stack = HandlerStack::create();
            $stack->push($wechatpayMiddleware, 'wechatpay');

            // 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
            $client = new Client(['handler' => $stack]);
            $resp = $client->request(
                'POST',
                'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', //请求URL
                [
                    // JSON请求体
                    'json' => $param,
                    'headers' => [
                        'Content-type' => 'application/json;charset=utf-8',
                        'Accept' => 'application/json',
                        'User-Agent' => '*/*',
                        'Authorization' => $this->schema . " " . $authorization // 注意这里面有个空格
                    ],
                ]
            );
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
            // 这里是封装签名,给前端调用支付时使用
                $array = json_decode($resp->getBody()->getContents(), true);

                $merchantPrivateKeyInstance = Rsa::from($this->mch_private_key);

                $params = [
                    'appId' => $this->config['appid'],
                    'timeStamp' => strval(Formatter::timestamp()),
                    'nonceStr' => Formatter::nonce(),
                    'package' => 'prepay_id=' . $array['prepay_id'],
                ];

                $params += ['paySign' => Rsa::sign(
                    Formatter::joinedByLineFeed(...array_values($params)),
                    $merchantPrivateKeyInstance
                ), 'signType' => 'RSA'];
                $params['out_trade_no'] = $order_no;

                return $this->setSuccess('', $params);
            } else if ($statusCode == 204) { //处理成功,无返回Body
                return $this->setSuccess('');
            }
        } catch (RequestException|Exception|GuzzleException $e) {
            // 进行错误处理
            return $this->setError($e->getMessage());
        }
    }

3.获取Authorization

代码如下(示例):

/**
 * @param $http_method
 * @param $body
 * @return string
 */
public function getAuthorization($http_method = "POST", $body = []): string
{
    // Authorization: <schema> <token>
    $url = "/v3/pay/transactions/jsapi"; //https://api.mch.weixin.qq.com
    $serial_no = $this->config['serial_no']; // 商户证书序列号
    $mchid = $this->config['mchid'];  // 商户号
    $timestamp = time();
    $nonce = $this->nonce_str();
    $url_parts = parse_url($url);

    $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
    $message = $http_method . "\n" .
        $canonical_url . "\n" .
        $timestamp . "\n" .
        $nonce . "\n" .
        $body . "\n";

    openssl_sign($message, $raw_sign, $this->mch_private_key, 'sha256WithRSAEncryption');
    $sign = base64_encode($raw_sign);

    return sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
        $mchid, $nonce, $timestamp, $serial_no, $sign);
}
    /**
     * @return string
     * 随机32位字符串
     * 上一篇忘记写这个方法了,补上
     */
    private function nonce_str(): string
    {
        $result = '';
        $str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
        for ($i = 0; $i < 32; $i++) {
            $result .= $str[rand(0, 48)];
        }
        return $result;
    }

4.支付回调

代码如下(示例):

    public function orderNotify(): bool
    {
        $notifyData = file_get_contents('php://input');
        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($notifyData, true);
        // 使用PHP7的数据解构语法,从Array中解构并赋值变量
        ['resource' => [
            'ciphertext' => $ciphertext,
            'nonce' => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;
        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, 'a296d789ca60d352dde0367a956883ce', $nonce, $aad);
        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);
        file_put_contents("Notify.txt", var_export($inBodyResourceArray, 1));
        if ($inBodyResourceArray['trade_state'] == "SUCCESS") {
            Db::startTrans();
            try {
                //编写支付成功的业务逻辑
                $order_no = $inBodyResourceArray['out_trade_no'];
                $attach = json_decode($inBodyResourceArray['attach'], true); // 如果传入信息的话,可以接收使用

                Db::commit();
            } catch (Exception $e) {
                Log::write($e->getMessage(), 'error');
                Db::rollback();
                return true;
            }
        } else {
            // 回调异常
            file_put_contents("NotifyError.txt", var_export($inBodyResourceArray, 1));
        }
        return true;
    }

总结

这个sdk跟wechatpay-php比起来差不太多,都挺清晰的。
按照上面步骤跑起来没啥问题。有问题可以留言,我印象中只有签名遇到坑了。

;