<?php


namespace backend\modules\payment\logic;


use api\logic\Helper;
use backend\modules\shop\models\ars\PaymentLog;
use backend\modules\shop\models\ars\RefundLog;
use backend\modules\shop\models\ars\WxPayConfig;
use EasyWeChat\Factory;
use yii\db\Exception;
use yii\helpers\Json;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
use Yii;

class WxPaymentManager
{
    /*支付类型*/
    const PAY_TYPE_WEB = 1;
    const PAY_TYPE_MINI_PROGRAM = 2;
    /*支付状态*/
    const STATUS_PAYMENT_WAITING = 0;
    const STATUS_PAYMENT_SUCCESS = 1;
    /*退款状态*/
    const STATUS_REFUND_WAIT = 0;  //退款待审核
    const STATUS_REFUND_CONFIRM = 1; //退款待确认
    const STATUS_REFUND_SUCCESS = 2; //退款成功
    const STATUS_REFUND_PORTION = 3; //部分退款
    /*发起支付方式*/
    const TRADE_TYPE_JS_API = 'JSAPI';

    public $appId;
    public $mchId;
    public $key;
    public $certPath;
    public $keyPath;
    public $notifyUrl;
    public $tradeType;
    public $payType;
    public $app;
    public $order;

    /**
     * @param $orderId
     * @param $orderAmount
     * @param $paymentAmount
     * @param $notifyUrl
     * @param $payType
     * @return mixed
     * @throws BadRequestHttpException
     * @throws Exception
     * 微信统一下单
     */
    public function wxPayment($orderId, $orderAmount, $paymentAmount, $notifyUrl, $payType)
    {
        if (empty($orderId) || empty($orderAmount) ||empty($paymentAmount) || empty($notifyUrl) || empty($payType)) {
            throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
        }
        $this->tradeType = self::TRADE_TYPE_JS_API;
        $this->payType = $payType;
        $this->notifyUrl = $notifyUrl;

        $unifyParams = $this->applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl);
        $result = $this->unify($unifyParams);
        if ($result['return_code'] == 'FAIL') {
            throw new BadRequestHttpException($result['return_msg']);
        }
        if ($result['return_code'] == 'SUCCESS' && $result['return_msg'] == 'OK' && $result['result_code'] == 'FAIL') {
            throw new BadRequestHttpException($result['err_code_des']);
        }
        return Json::decode($this->getBridgeConfig($result['prepay_id']), true);
    }

    /**
     * @param $prepayId
     * @return array|string
     */
    private function getBridgeConfig($prepayId)
    {
        $this->initObject();
        return $this->app->jssdk->bridgeConfig($prepayId);
    }

    /**
     * @param  string $orderId
     * @param  int $orderAmount
     * @param  int $paymentAmount
     * @param  string $notifyUrl
     * @return array
     * @throws BadRequestHttpException
     * @throws Exception
     * 生成支付参数
     */
    private function applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl)
    {
        $this->savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl);

        $params = [
            'body' => '订单支付',
            'out_trade_no' => $orderId,
            'total_fee' => $paymentAmount,
            'openid' => Yii::$app->user->identity->wx_openid,
        ];
        return $params;
    }

    /**
     * @param  string $orderId
     * @param  int $orderAmount
     * @param  int $paymentAmount
     * @param  string $notifyUrl
     * @throws BadRequestHttpException
     * @throws Exception
     * 保存支付信息
     */
    private function savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl)
    {
        $paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
        if (!$paymentLog) {
            $paymentLog = new PaymentLog();
        } elseif (!empty($paymentLog->wx_payment_id)) {
            throw new BadRequestHttpException('此订单号也有支付信息,不能重复');
        }
        $paymentLog->order_id = $orderId;
        $paymentLog->order_amount = $orderAmount;
        $paymentLog->payment_amount = $paymentAmount;
        $paymentLog->notify_url = $this->notifyUrl;
        $paymentLog->type = $this->payType;
        $paymentLog->status = self::STATUS_PAYMENT_WAITING;
        if (!$paymentLog->save()) {
            throw new Exception(Helper::errorMessageStr($paymentLog->errors));
        }
    }

    protected function getApp()
    {
        $this->initObject();
        $config = [
            'app_id' => $this->appId,
            'mch_id' => $this->mchId,
            'key' => $this->key,
            'cert_path' => $this->certPath,
            'key_path' => $this->keyPath,
            'notify_url' => $this->notifyUrl,
            'trade_type' => $this->tradeType,
            'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
        ];
        $this->app = Factory::payment($config);
        //判断当前是否为沙箱模式:
        $this->app->inSandbox();
    }

    /**
     * @var WxPayConfig $wxPayConfig
     */
    private function initObject()
    {
        $path = Yii::getAlias('@backend');
        $wxPayConfig = WxPayConfig::find()->one();
        switch ($this->payType) {
            case self::PAY_TYPE_WEB:
                //        $wxConfig = WxConfig::find()->one();
//                $this->appId = trim($wxConfig->appid);
                break;
            case self::PAY_TYPE_MINI_PROGRAM:
                //        $miniProgramConfig = MiniProgramConfig::find()->one();
//                $this->appId = trim($miniProgramConfig->appid);
                break;
        }
        $this->mchId = trim($wxPayConfig->mch_id);
        $this->key = trim($wxPayConfig->key);
        $this->certPath = trim($path . $wxPayConfig->cert_path);
        $this->keyPath = trim($path . $wxPayConfig->key_path);
        $this->notifyUrl = Yii::$app->request->hostInfo . '/wx-payment/notify';

        $this->mchId = '1395812402';
        $this->key = '51CF5EE3B2E35B9843E17E6099325A65';
    }

    /**
     * @param $unifyParams
     * @return mixed
     * 统一下单
     */
    private function unify($unifyParams)
    {
        $unifyParams['trade_type'] = $this->tradeType;
        $this->getApp();
        return $this->app->order->unify($unifyParams);
    }

    /**
     * @param $data
     * @return bool
     * 支付成功回调验证签名和支付金额
     */
    public function checkSign($data)
    {
        $this->initObject();
        $notifySign = $data['sign'];
        unset($data['sign']);
        $sign = $this->_sign($data);
        if ($notifySign == $sign) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param $arr
     * @return string
     * 微信签名方法
     */
    private function _sign($arr)
    {
        $arr = array_filter($arr);
        ksort($arr);
        $arr['key'] = $this->key;
        $queryString = http_build_query($arr);
        $queryString = urldecode($queryString);
        return strtoupper(md5($queryString));
    }


/***************************************  Refund ************************************************/
    /**
     * @param string $orderId
     * @param int $refundAmount
     * @param string $refundAccount
     * @param int $reason
     * @return RefundLog
     * @throws BadRequestHttpException
     * @throws Exception
     * @throws NotFoundHttpException
     * 申请退款
     */
    public function applyRefund($orderId, $refundAmount, $refundAccount, $reason)
    {
        if (empty($orderId) || empty($refundAmount) || empty($refundAccount) || empty($reason)) {
            throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
        }
        $paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
        if (empty($paymentLog)) {
            throw new NotFoundHttpException('订单支付信息未找到');
        }
        if (RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT])) {
            throw new BadRequestHttpException('此订单存在等待审核的退款申请');
        }
        $refundedAmount = RefundLog::find()
                ->where(['order_id' => $orderId, 'status' => self::STATUS_PAYMENT_SUCCESS])
                ->sum('refund_amount') ?? 0;

        $refundLog = new RefundLog();
        $refundLog->order_id = $orderId;
        $refundLog->wx_refund_id = Helper::timeRandomNum(3, 'R');
        $refundLog->reason = $reason;
        $refundLog->order_amount = $paymentLog->payment_amount;
        $refundLog->refund_amount = $refundAmount;
        $refundLog->refunded_amount = $refundedAmount;
        $refundLog->type = $paymentLog->type;
        $refundLog->status = self::STATUS_REFUND_WAIT;
        $refundLog->refund_account = $refundAccount;
        $refundLog->applyed_at = time();
        if (!$refundLog->save()) {
            throw new Exception(Helper::errorMessageStr($refundLog->errors));
        }
        return $refundLog;
    }

    /**
     * @param string $orderId
     * @param int $refundAmount
     * @param int $operatorId
     * @return array|mixed
     * @throws BadRequestHttpException
     * @throws NotFoundHttpException
     */
    public function refund($orderId, $refundAmount, $operatorId)
    {
        $paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
        if (empty($paymentLog)) {
            throw new NotFoundHttpException('订单支付信息未找到');
        }
        $refundLog = RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT]);
        if (empty($refundLog)) {
            throw new NotFoundHttpException('订单退款信息未找到');
        }

        $this->executeRefund($paymentLog, $refundLog, $refundAmount);
        return $this->updateRefundInfo($paymentLog, $refundLog, $operatorId);
    }

    /**
     * @param PaymentLog $paymentLog
     * @param RefundLog $refundLog
     * @param $refundAmount
     * @throws BadRequestHttpException
     */
    private function executeRefund($paymentLog, $refundLog, $refundAmount)
    {
        /*参数分别为:微信订单号、商户退款单号、订单金额、退款金额、其他参数*/
        $this->getApp();
        $config = [
            'refund_desc' => '退款'
        ];
        $result = $this->app->refund->byTransactionId(
            $paymentLog->wx_payment_id,
            $refundLog->wx_refund_id,
            $paymentLog->payment_amount,
            $refundAmount,
            $config
        );
        Yii::info($result, 'refund_log');

        if ($result['return_code'] == 'FAIL' || $result['return_msg'] != 'OK' || $result['result_code'] == 'FAIL') {
            throw new BadRequestHttpException($result['return_msg']);
        }
        if ($result['err_code_des']) {
            throw new BadRequestHttpException($result['err_code_des']);
        }
    }

    /**
     * @param PaymentLog $paymentLog
     * @param RefundLog $refundLog
     * @param int $operatorId
     * @return array
     */
    private function updateRefundInfo($paymentLog, $refundLog, $operatorId)
    {
        $tra = Yii::$app->db->beginTransaction();
        try {
            $paymentLog->wx_refund_id = $refundLog->wx_refund_id;
            $paymentLog->refund_account = $refundLog->refund_account;
            $paymentLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION;
            if (!$paymentLog->save()) {
                throw new Exception(Helper::errorMessageStr($paymentLog->errors));
            }

            $refundLog->operator_id = $operatorId;
            $refundLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION;
            $refundLog->finished_at = time();
            if (!$refundLog->save()) {
                throw new Exception(Helper::errorMessageStr($refundLog->errors));
            }

            $tra->commit();
            return ['status' => true];
        } catch (Exception $e) {
            $tra->rollBack();
            return ['status' => false, 'info' => $e->getMessage()];
        }
    }

}