写个简单微信支付V3客户端封装函数,顺遍吐槽一下微信支付的SDK接口

吐槽一下微信支付的服务端SDK 接口 (SDK - SDK&开发工具 | 微信支付商户文档中心):代码写的确实好很OO、很优雅,额,但也不见的得写好,那哪里是接口,就是个显摆自己加密技术封装写的多好多好.

以PHP的来说 (wechatpay/wechatpay - Packagist ):

1、一个PublicKey还要用户用工具自己去下载.(浏览器访问https我们得手动下载证书?)

2、这SDK哪里是接口,没有整合一个接口,就只是做个加解密的封装罢了,封装完还是给出的是PSR的消息...

3、敏感内容要加解密还得写代码处理,就不能自动处理一下,或暴露个方法自动识别一下么.

哎,在他之上这再整个PHP封装函数,希望新手不那么迷茫:对开发人员来讲,已知什么就直接用什么就行了:
已知我们就只有这些,所以就不要再折磨我们做别的了:


商户号
商户API私钥文件路径 (/certs/apiclient_key.pem)
商户API证书文件路径 (/certs/apiclient_cert.pem)
商户API证书V3 key
 

这些都在 https://pay.weixin.qq.com/index.php/core/cert/api_cert 可以获取到,或设置

那我们是不是就这样就够了:
   

 public static function NewClient(
        string $merchantId,
        string $merchantPrivateKeyFilePath,
        string $merchantCertFilePath,
        string $apiv3Key
    )

 接着开始用,怎么用:

比如我们添加一个分账的账号,这里用到了加密/解密:

$client = $this->NewClient($merchantId, $merchantPrivateKeyFilePath, $merchantCertFilePath, $apiv3Key);

$resp = $client->chain('/v3/profitsharing/receivers/add')
    ->post([
        'json' => [
            'appid' => $appId,
            'type' => 'PERSONAL_OPENID', // 个人OpenID| PERSONAL_OPENID; 商户号| MERCHANT_ID; 个人在子商户应用下的OpenID|PERSONAL_SUB_OPENID
            'account' => 'oITsy5K-EfWhvRJavlm35CRap4Ac',
            //要加密
            'name' => $client->enc('谢平康'), //【分账接收方全称】,这个是要加秘的
            'relation_type' => 'PARTNER',//SERVICE_PROVIDER:服务商 STORE:门店 STAFF:员工 STORE_OWNER:店主 PARTNER:合作伙伴 HEADQUARTER:总部
            // BRAND:品牌方 DISTRIBUTOR:分销商 USER:用户 SUPPLIER:供应商 CUSTOM:自定义
            'custom_relation' => null, //自定义的分账关系
            'description' => '平台技术支持',
        ]])->getBody();
//返回的结果
echo $resp, PHP_EOL;
//结果识别处理
$data = json_decode($resp);
//返回的结果解密一下名称
echo($client->dec($data->name)), PHP_EOL;

什么公钥证书下载的都我们没关系,
可以简单做个cache存一下证书,根据证书的有效期定期更新证书就行了

直接上代码,函数写好一个方法就够用了:

上代码:


    /**
     * 创建一个新的客户端实例。
     *
     * @param string $merchantId 商户号
     * @param string $merchantPrivateKeyFilePath 商户API私钥文件路径
     * @param string $merchantCertFilePath 商户API证书文件路径
     * @param string $apiv3Key 商户API证书V3 key
     * @param string $platformCertificateFilePath 平台的公钥文件保存路径,可不传默认当前目录下 ( __DIR__ . '/webchat_pay_public-key.pem')
     * @return object 返回客户端实例或带有加密功能的客户端实例
     */
    public static function NewClient(
        string $merchantId,
        string $merchantPrivateKeyFilePath,
        string $merchantCertFilePath,
        string $apiv3Key,
        string $platformCertificateFilePath = null
    )
    {
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

        // 「商户API证书」的「证书序列号」
        //可以用 openssl x509 -noout -serial -in apiclient_cert.pem 查看到,这个直接用代码获取就行了
        $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertFilePath);

        //没传公钥文件就给他一下默认当前目录下
        if (!$platformCertificateFilePath) {
            $platformCertificateFilePath = 'file://' . __DIR__ . '/webchat_pay_public-key.pem';
        }
        //判断证书是否过期
        if (file_exists($platformCertificateFilePath)) {
            $certificate = openssl_x509_read(file_get_contents($platformCertificateFilePath));

            if (!$certificate) {
                //证书不对,删除文件
                unlink($platformCertificateFilePath);
            } else {
                // 解析证书以获取有效期
                $parsedCertificate = openssl_x509_parse($certificate);
                $validFrom = $parsedCertificate['validFrom_time_t'];
                $validTo = $parsedCertificate['validTo_time_t'];
                $currentTime = time();
                // 检查证书不在有效期内再重新下载
                if ($validFrom > $currentTime || $validTo < $currentTime) {
                    unlink($platformCertificateFilePath);
                }
            }
        }
        //判断文件是否存在或过期,要重新下载
        if (!file_exists($platformCertificateFilePath)) {
            //下载证书
            static $certs = ['any' => null];
            $instance = Builder::factory([
                'mchid' => $merchantId,
                'serial' => $merchantCertificateSerial,
                'certs' => &$certs,
                'privateKey' => $merchantPrivateKeyInstance,]);
            $stack = $instance->getDriver()->select('v3')->getConfig('handler');
            $stack->after('verifier', function (callable $handler) use ($apiv3Key, &$certs) {
                return function ($request, array $options) use ($apiv3Key, $handler, &$certs) {
                    return $handler($request, $options)->then(function (ResponseInterface $response) use ($apiv3Key, $request, &$certs) {
                        $body = (string)$response->getBody();
                        $json = \json_decode($body);
                        $data = \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : [];
                        \array_map(static function ($row) use ($apiv3Key, &$certs) {
                            $cert = $row->encrypt_certificate;
                            $certs[$row->serial_no] = AesGcm::decrypt($cert->ciphertext, $apiv3Key, $cert->nonce, $cert->associated_data);
                        }, $data);

                        return $response;
                    });
                };
            }, 'injector');
            $response = $instance->chain('v3/certificates')->get();
            $body = (string)$response->getBody();
            $json = \json_decode($body);
            $data = \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : [];
            foreach ($data as $row) {
                $serialNo = $row->serial_no;
                \file_put_contents($platformCertificateFilePath, $certs[$serialNo]);
            }
        }

        // 从本地文件中加载「微信支付平台证书」(可使用证书下载工具得到),用来验证微信支付应答的签名
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

        // 构造一个 APIv3 客户端实例
        $config = [
            'mchid' => $merchantId,
            'serial' => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
            'headers' => [
                'Wechatpay-Serial' => $platformCertificateSerial,
            ]
        ];
        $instance = Builder::factory($config);
        // 做一个匿名类返回,也可以用简单的 array
        return new class($instance, $platformPublicKeyInstance, $merchantPrivateKeyInstance) {
            public $instance;
            private $platformPublicKeyInstance;
            private $merchantPrivateKeyInstance;

            public function __construct($instance, $platformPublicKeyInstance, $merchantPrivateKeyInstance)
            {
                $this->instance = $instance;
                $this->platformPublicKeyInstance = $platformPublicKeyInstance;
                $this->merchantPrivateKeyInstance = $merchantPrivateKeyInstance;
            }

            public function enc(string $msg): string
            {
                return Rsa::encrypt($msg, $this->platformPublicKeyInstance);
            }

            public function chain(string $msg)
            {
                return $this->instance->chain($msg);
            }

            public function dec(string $msg): string
            {
                return Rsa::decrypt($msg, $this->merchantPrivateKeyInstance);
            }
        };
    }

最后还得记录引入下:


use Psr\Http\Message\ResponseInterface;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值