<?php

namespace app\common\library\payment;

use app\common\model\order\Order as OrderModel;
use app\common\model\order\OrderRefunds as OrderRefundsModel;
use think\facade\Db;
use think\facade\Request;
use app\common\library\file\File;
use app\common\exception\BaseException;
use app\common\model\Payment;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
use WeChatPay\Formatter;
use WeChatPay\Crypto\AesGcm;

/**
 * @package app\common\library\payment
 * @class Wechat
 * @author xzncit 2024/3/16
 */
class Wechat {

    /**
     * @param $order
     * @param $type 类型(pay: 支付 refund: 退款)
     * @return array
     * @throws BaseException
     */
    public function initialization($order,$type="pay"){
        $config = $this->config();
        if($type == "pay"){
            $config["appid"] = $order["appid"];
            $order["order"]["mch_id"] = $config["mch_id"];
            // $data = array_merge(["mchid"=>$config["mch_id"],"appid"=>$config["appid"]],$order["order"]);
        }else{
            $order["segment"] = "/v3/refund/domestic/refunds";
        }

        // 商户号
        $merchantId = $config["mch_id"];
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $config["ssl_key"];
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 「商户API证书」的「证书序列号」
        $merchantCertificateSerial = $config["mch_serial"];
        // 从本地文件中加载「微信支付平台证书」(可使用证书下载工具得到），用来验证微信支付应答的签名
        $platformCertificateFilePath = $config["ssl_cer"];
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid'      => $merchantId,
            'serial'     => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);

        $resp = $instance->chain($order["segment"])->post(['json' => $order["order"]]);
        if($resp->getStatusCode() != 200) {
            // https://pay.weixin.qq.com/docs/merchant/development/interface-rules/http-response-code.html
            throw new \Exception("请求出错,状态码: " . $resp->getStatusCode(),0);
        }

        $string = $resp->getBody();
        $result = json_decode($string,true);
        return [$result,$config];
    }

    /**
     * 发起支付
     * @param $order
     * @return array|void
     * @throws \Exception
     */
    public function create($order){
        list($result,$config) = $this->initialization($order);
        if($order["terminal"] == 1){
            return [
                "isPay"  => 2,
                "type"   => "wechat",
                "options" => $this->sign($config,$result["prepay_id"]),
                "config"  => \app\common\library\wechat\Wechat::create("wechat")->script->getJsSign(Request::param("url",Request::domain(),"trim"))
            ];
        }else if($order["terminal"] == 2){
            return [
                "isPay"  => 2,
                "type"   => "wechat",
                "params"=>$this->sign($config,$result["prepay_id"])
            ];
        }else if($order["terminal"] == 9){
            return [
                "isPay"  => 2,
                "type"   => "wechat",
                "url"=>$result["h5_url"]
            ];
        }else if(in_array($order["terminal"],[10,11])){
            return [
                "isPay"  => 2,
                "type"   => "wechat",
                "params" => $this->appSign($config,$result["prepay_id"])
            ];
        }
    }

    public function refund($order){
        list($result,$config) = $this->initialization($order);
        return $result;
    }

    /**
     * 前端JS方法调起公众号支付
     * @param $config
     * @param $prepay_id
     * @return array
     */
    public function sign($config,$prepay_id){
        $merchantPrivateKeyFilePath = $config["ssl_key"];
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);

        $params = [
            'appId'     => $config["appid"],
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr'  => Formatter::nonce(),
            'package'   => 'prepay_id=' . $prepay_id,
        ];

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

        return $params;
    }

    public static function appSign($config,$prepayId){
        $merchantPrivateKeyFilePath = $config["ssl_key"];
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);

        $params = [
            'appid'         => $config["appid"],
            'partnerid'     => $config["mch_id"],
            'prepayid'      => (string)$prepayId,
            'package'       => 'Sign=WXPay',
            'noncestr'      => Formatter::nonce(),
            'timestamp'     => (string)Formatter::timestamp(),
        ];

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

        // echo json_encode($params);
        return $params;
    }

    public function config(){
        $wechat = Payment::where("code","wechat")->find();
        if(empty($wechat["config"])){
            throw new BaseException("请配置微信支付",0);
        }

        $array = json_decode($wechat["config"],true);
        if(empty($array["mchid"])){
            throw new BaseException("您未配置微信商户号",0);
        }else if(empty($array["mchkey"])){
            throw new BaseException("您未配置微信商户密钥",0);
        }else if(empty($array["private_key"]) || empty($array["public_key"])){
            throw new BaseException("请配置微信支付证书");
        }

        return [
            "appid"         => "",
            "mch_id"        => trim($array["mchid"]),
            "mch_key"       => trim($array["mchkey"]),
            "mch_serial"    => trim($array["mchserial"]),
            "ssl_cer"       => 'file://' . File::getPath().'runtime/payment/wechat/apiclient_cert.pem',
            "ssl_key"       => 'file://' . File::getPath().'runtime/payment/wechat/apiclient_key.pem'
        ];
    }

    public function refundNotify(){
        try{
            $config                 = $this->config();
            $inWechatpaySignature   = Request::header("Wechatpay-Signature");
            $inWechatpayTimestamp   = Request::header("Wechatpay-Timestamp");
            $inWechatpaySerial      = Request::header("Wechatpay-Serial");
            $inWechatpayNonce       = Request::header("Wechatpay-Nonce");
            $inBody                 = file_get_contents('php://input');// 请根据实际情况获取，例如: file_get_contents('php://input');
            $apiv3Key               = $config["mch_key"];

            // 根据通知的平台证书序列号，查询本地平台证书文件，
            // 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
            $platformPublicKeyInstance = Rsa::from($config["ssl_cer"], Rsa::KEY_TYPE_PUBLIC);

            // 检查通知时间偏移量，允许5分钟之内的偏移
            $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
            $verifiedStatus = Rsa::verify(
                // 构造验签名串
                Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
                $inWechatpaySignature,
                $platformPublicKeyInstance
            );

            if (!($timeOffsetStatus && $verifiedStatus)) {
                throw new \Exception("解密失败",0);
            }

            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);

            // 使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext'      => $ciphertext,
                'nonce'           => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;

            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);

            // 把解密后的文本转换为PHP Array数组
            $data = (array)json_decode($inBodyResource, true);

            if(!isset($data["refund_status"]) || $data["refund_status"] != 'SUCCESS'){
                throw new BaseException("退款失败,状态码：-998",0);
            }

            // 检查订单号是否存在
            if(!isset($data["out_refund_no"])){
                return $this->fail();
            }

            $refund = OrderRefundsModel::where("order_no",$data["out_refund_no"])->findOrEmpty()->toArray();
            if(empty($refund)){
                throw new BaseException("退款订单不存在",0);
            }

            if(in_array($refund["status"],[2,3,4])){
                return $this->success();
            }

            Db::startTrans();
            OrderRefundsModel::where("order_no",$data["out_refund_no"])->save([
                "status"        => 2,
                "audit_status"  => 2,
                "update_time"   => time()
            ]);

            OrderModel::where(["id"=>$refund['order_id']])->save([
                "status"=>6,
                "refund_status"=>4
            ]);
            Db::commit();
            return $this->success();
        }catch (\Exception $ex){
            Db::rollback();
            return $this->fail();
        }
    }

    /**
     * 支付回调通知
     * @return false|string
     */
    public function notify() {
        try{
            $config                 = $this->config();
            $inWechatpaySignature   = Request::header("Wechatpay-Signature");
            $inWechatpayTimestamp   = Request::header("Wechatpay-Timestamp");
            $inWechatpaySerial      = Request::header("Wechatpay-Serial");
            $inWechatpayNonce       = Request::header("Wechatpay-Nonce");
            $inBody                 = file_get_contents('php://input');// 请根据实际情况获取，例如: file_get_contents('php://input');
            $apiv3Key               = $config["mch_key"];

            // 根据通知的平台证书序列号，查询本地平台证书文件，
            // 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
            $platformPublicKeyInstance = Rsa::from($config["ssl_cer"], Rsa::KEY_TYPE_PUBLIC);

            // 检查通知时间偏移量，允许5分钟之内的偏移
            $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
            $verifiedStatus = Rsa::verify(
            // 构造验签名串
                Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
                $inWechatpaySignature,
                $platformPublicKeyInstance
            );

            if (!($timeOffsetStatus && $verifiedStatus)) {
                throw new \Exception("解密失败",0);
            }

            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);

            // 使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext'      => $ciphertext,
                'nonce'           => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;

            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);

            // 把解密后的文本转换为PHP Array数组
            $data = (array)json_decode($inBodyResource, true);

            if(!isset($data["trade_state"]) || $data["trade_state"] != 'SUCCESS'){
                throw new BaseException("支付失败,状态码：-998",0);
            }

            // 检查订单号是否存在
            if(!isset($data["out_trade_no"])){
                return $this->fail();
            }

            Db::startTrans();
            $data["trade_no"] = $data["transaction_id"]??"";
            Order::pay($data);
            Db::commit();
            return $this->success();
        }catch (\Exception $ex){
            Db::rollback();
            return $this->fail();
        }
    }

    public function success(){
        return json_encode(["code"=>"SUCCESS","message"=>"成功"]);
    }

    public function fail(){
        return json_encode(["code"=>"FAIL","message"=>"失败"]);
    }

}