WechatPayment.php 4.81 KB
<?php
/**
 * Created by PhpStorm.
 * User: lishuai
 * Date: 2022/2/15
 * Time: 4:23 PM
 */

namespace App\Payment;

use App\Models\Order;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class WechatPayment implements PaymentInterface
{
    /** 支付接口基础地址 */
    const MCH_BASE_URL = 'https://api.mch.weixin.qq.com';

    /** APP支付*/
    const APP_URL = '/v3/pay/transactions/app';

    /** H5支付*/
    const H5_URL = '/v3/pay/transactions/h5';

    /** App的应用id */
    public $appid;

    /** 商户身份ID */
    public $mchid;

    /** 商品描述*/
    public $description;

    /** 商户订单号*/
    public $out_trade_no;

    /** 支付回调*/
    public $notify_url;

    /** 订单金额*/
    public $amount;

    /** 证书序列号*/
    public $serial_no;

    /** 商户API私钥*/
    // todo 待添加 file:///path/to/merchant/apiclient_key.pem'
    public $mch_private_key = '';


    public function prepare(Order $order)
    {
        // todo
        return [
            'appid' => env('WECHAT_APPID'),
            'partnerid' => env('WECHAT_PAY_MCH_ID'),
            'prepayid' => 'wx261153585405162d4d02642eabe7000000',
            'package' => 'Sign=WXPay',
            'noncestr' => '5K8264ILTKCH16CQ2502SI8ZNMTM67VS',
            'timestamp' => '1647249156',
            'sign' => 'oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg=='
        ];

        $body = [
            'appid' => env('WECHAT_APPID'),
            'mchid' => env('WECHAT_PAY_MCH_ID'),
            'description' => $order->description,
            'out_trade_no' => $order->order_sn,
            'notify_url' => env('WECHAT_PAY_NOTIFY'),
            'amount' => [
                'total' => $order->pay_amount * 100,
                'currency' => 'CNY'
            ],
        ];

        $timestamp = time();//时间戳

        $nonce = self::nonce(32);//随机串

        $sign = $this->sign('POST', self::APP_URL, $timestamp, $nonce, json_encode($body));

        //设置HTTP头
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            env('WECHAT_PAY_MCH_ID'), $nonce, $timestamp, env('WECHAT_PAY_SERIAL_NO'), $sign);
        $headers = [
            'Accept' => 'application/json, text/plain, application/x-gzip, application/pdf, image/png, image/*;q=0.5',
            'User-Agent' => '*/*',
            'Content-Type' => 'application/json; charset=utf-8',
            'Authorization' => $token,
        ];

        $client = new Client([
            'base_uri' => self::MCH_BASE_URL,
            'timeout' => 0,
            'allow_redirects' => false,
            'headers' => $headers
        ]);

        try {
            $response = $client->request('POST', self::APP_URL, ['json' => $body]);
            dd($prepayid = $response->getBody()->getContents());
        } catch (GuzzleException $exception) {
            dd($exception->getMessage());
        }

        return [
            'appid' => env('WECHAT_APPID'),
            'partnerid' => env('WECHAT_PAY_MCH_ID'),
            'prepayid' => $prepayid,
            'package' => 'Sign=WXPay',
            'noncestr' => $nonce,
            'timestamp' => $timestamp,
            'sign' => $sign
        ];
    }


    public function notify()
    {

    }




    /**
     * @param string $http_method "GET"/"POST"
     * @param string $url "/v3/certificates"
     * @param string $timestamp
     * @param string $nonce
     * @param string $body "json_encode($body)"
     * @return array
     */
    public function sign($http_method, $url, $timestamp, $nonce, $body = '')
    {
        $mch_private_key = openssl_get_privatekey(file_get_contents($this->mch_private_key));//私钥

        //构造签名串
        $message = implode("\n", [$http_method, $url, $timestamp, $nonce, $body]);

        //计算签名值
        openssl_sign($message, $signature, $mch_private_key, OPENSSL_ALGO_SHA256);

        return base64_encode($signature);
    }

    /**
     * 生成32位随机字符串
     * @param int $size - Nonce string length, default is 32.
     * @return string - base62 random string.
     * @throws
     */
    public static function nonce(int $size = 32): string
    {
        if ($size < 1) {
            throw new \InvalidArgumentException('Size must be a positive integer.');
        }

        return implode('', array_map(static function (string $c): string {
            return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[ord($c) % 62];
        }, str_split(random_bytes($size))));
    }

}