<?php

namespace App\Models;

use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;

/** * 实测 可用 相关配置弄好
 * date:   2022/09/19 18:10
 * desc:
 */
class PayV3
{
    private $v3_key;
    private $merchantId;
    private $merchantCertificateSerial;
    private $merchantPrivateKeyFilePath;
    private $platformCertificateFilePath;
    private $platformPublicKeyInstance;
    private $instance;

    const AUTH_TAG_LENGTH_BYTE = 16;

    public function __construct()
    {
        //证书生成命令(生成的证书在sdk目录 或 自己指定)
        //php ./bin/CertificateDownloader.php -k jiaxin**************uzhou20 -m 1614781491 -f /www/wwwroot/项目目录/server/public/cert/apiclient_key.pem -s 629BD177D7******************8CE43244 -o ./

        // 商户号
        $this->merchantId = env('WX_XCX_MCH_ID');
        // v3api秘钥
        $this->v3_key = env('WX_XCX_MCH_KEY_V3');
        // 「商户API证书」的「证书序列号」 商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号
        $this->merchantCertificateSerial = '75BFCD14831525581A301D0CD190330606153F0E';
        // 商户API私钥
        //$this->merchantPrivateKeyFilePath = $pay_config['apiclient_key']; //"file://"
        $this->merchantPrivateKeyFilePath = "file://" . public_path() . '/cert/apiclient_key.pem'; //
        ;
        // 微信支付平台证书 首次通过命令行生成  https://github.com/wechatpay-apiv3/wechatpay-php/tree/main/bin
        //$this->platformCertificateFilePath = $pay_config['apiclient_cert'];
        $this->platformCertificateFilePath = "file://" . public_path() . "/cert//wechatpay_38D2255A4177BE40747CE1ECAF6ED0812E7D00C8.pem";
        // 构建客户端实例
        $this->instance = $this->buildInstance();


        //var_dump($this->merchantPrivateKeyFilePath,$this->platformCertificateFilePath);exit;
    }

    /**
     * 商家付款到零钱
     * @param string $batch_sn  商户系统内部的商家批次单号
     * @param string $out_detail_no  账明细单的唯一标识
     * @param float $money      转账金额,单位:元
     * @param string $openid    用户openid
     * @param string $remark    转账备注
     * @param string $user_name 用户姓名,转账金额大于2000元,必填
     * @return array
     * @throws \Exception
     */
    public function transfer(string $batch_sn, string $out_detail_no, float $money, string $openid, string $remark, string $user_name = ''): array
    {
        $trans_detail = [
            'out_detail_no'   => $out_detail_no,
            'transfer_amount' => intval($money * 100),
            'transfer_remark' => $remark,
            'openid'          => $openid,
        ];
        if ($money >= 2000) {
            if (empty($user_name)) {
                throw new \Exception('提现金额大于2000元,真实姓名不能为空');
            }
            $trans_detail['user_name'] = $this->encrypt($user_name);
        }
        $response = $this->instance->chain('v3/transfer/batches')->post([
            'json' => [
                'appid'                => env('WX_XCX_APPID'),
                'out_batch_no'         => $batch_sn,
                'batch_name'           => '提现转账',
                'batch_remark'         => '提现转账',
                'total_amount'         => intval($money * 100),
                'total_num'            => 1,
                'transfer_detail_list' => [
                    $trans_detail
                ]
            ]
        ]);
        return json_decode($response->getBody(), true);
    }

    /**
     * 转账明细查询
     * @param string $out_batch_no  商户系统内部的商家批次单号
     * @param string $out_detail_no 账明细单的唯一标识
     * @return array
     */
    public function transferQuery(string $out_batch_no, string $out_detail_no = ''): array
    {
        $api = 'v3/transfer/batches/out-batch-no/' . $out_batch_no;
        $query = [];
        if ($out_detail_no) {
            $api .= '/details/out-detail-no/' . $out_detail_no;
        } else {
            $query = [
                'query' => [
                    'need_query_detail' => true,
                    'detail_status'     => 'ALL'
                ]
            ];
        }
        $response = $this->instance->chain($api)->get($query);
        return json_decode($response->getBody(), true);
    }

    // 自动更新平台证书
    public function autoUpdatePlatformCertificateFile()
    {
        $result = $this->v3Certificates()['data'];
        $data   = end($result);
        $effective_time = strtotime($data['effective_time']);
        if ($effective_time - time() > 0 && $effective_time - time() <= 86400) {
            $encrypt_certificate = $data['encrypt_certificate'];
            $cert_content = $this->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext']);
            file_exists($this->platformCertificateFilePath) && file_put_contents($this->platformCertificateFilePath, $cert_content);
        }
    }

    // 获取平台证书
    private function v3Certificates()
    {
        $response = $this->instance->chain('v3/certificates')->get();
        return json_decode($response->getBody(), true);
    }

    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     * 用于解密v3Certificates接口数据获取证书
     * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
     * @param string    $associatedData     AES GCM additional authentication data
     * @param string    $nonceStr           AES GCM nonce
     * @param string    $ciphertext         AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    private function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->v3_key);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->v3_key);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->v3_key, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
        }
        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }


    // 加密
    private function encrypt(string $msg): string
    {
        return Rsa::encrypt($msg, $this->platformPublicKeyInstance);
    }

    private function buildInstance(): \WeChatPay\BuilderChainable
    {
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 从本地文件中加载 微信支付平台证书
        $this->platformPublicKeyInstance   = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        return Builder::factory([
            'mchid'      => $this->merchantId,
            'serial'     => $this->merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $platformCertificateSerial => $this->platformPublicKeyInstance,
            ],
        ]);
    }
}