client = $client ?: null; if ($options['mchType'] === 'provider') { $config = [ 'appid' => $options['provider']['appid'], 'appkey' => $options['provider']['appkey'], 'msgSrcId' => $options['provider']['msgSrcId'], 'msgSrc' => $options['provider']['msgSrc'], 'md5Secret' => $options['provider']['md5Secret'], 'mid' => $options['provider']['mid'], 'tid' => $options['provider']['tid'], ]; } else { $config = [ 'appid' => $options['normal']['appid'], 'appkey' => $options['normal']['appkey'], 'msgSrcId' => $options['normal']['msgSrcId'], 'msgSrc' => $options['normal']['msgSrc'], 'md5Secret' => $options['normal']['md5Secret'], 'mid' => $options['normal']['mid'], 'tid' => $options['normal']['tid'], ]; } $this->config = $config; return $this; } /* 统一下单API * @param string $outTradeNo 交易订单号 * @param string $totalFee 实际付款金额 * @param array $extra 附加的数据 (需要携带openid) * @return bool * @throws BaseException */ public function addReceiver(string $type, string $account, string $name, string $relation_type = "HEADQUARTER", string $custom_relation = ""): bool { return true; } /** * 统一下单API * @param string $outTradeNo 交易订单号 * @param string $totalFee 实际付款金额 * @param array $extra 附加的数据 (需要携带openid) * @return bool * @throws BaseException */ public function profitsharing(string $transaction_id, string $out_order_no, array $receivers): bool { return true; } public function profitsharingQuery(string $out_order_no, string $transaction_id): ?array{ return []; } /** * 统一下单API * @param string $outTradeNo 交易订单号 * @param string $totalFee 实际付款金额 * @param array $extra 附加的数据 (需要携带openid) * @return bool * @throws BaseException */ public function unify(string $outTradeNo, string $totalFee, array $extra = []): bool { try { $result = null; // 发起API调用 微信小程序端 if ($this->client === ClientEnum::MP_WEIXIN) { //$outTradeNo = date("YmdHis").mt_rand(1000,9999); $order_url = $this->domain . "/v1/netpay/wx/mini-pre-order"; //$order_url = $this->domain . "/v1/netpay/wx/unified-order"; $params = [ "requestTimestamp" => date("Y-m-d H:i:s"), "merOrderId" => $this->config['msgSrcId'].$outTradeNo, "mid" => $this->config['mid'], "tid" => $this->config['tid'], "subAppId" => $this->config['appid'], "subOpenId" => $extra['openid'], "tradeType" => "MINI", "notifyUrl" => $this->notifyUrl(), "orderDesc" => "商品支付", "totalAmount" => bcmul($totalFee, "100"), "divisionFlag" => false,//分账标记 //"platformAmount" => false,//平台商户分 账金额 // "subOrders" => [ // [ // "mid" => "111", // "totalAmount" => 1, // ] // ], ]; $authorization = $this->getAuthorization($params); $headers = ["Authorization:".$authorization]; $data = httpRequest($order_url, "POST", json_encode($params), $headers, false); if ($data['errCode'] != "SUCCESS") { Log::append('银联-unify', ['client' => $this->client, 'result' => $data]); return false; } $this->result = $data; return true; } } catch (\Throwable $e) { $this->throwError('银联API下单失败:' . $e->getMessage(), true, 'unify'); } return false; } /** * 交易查询 (主动查询订单支付状态) * @param string $outTradeNo 交易订单号 * @return array|null * @throws BaseException */ public function tradeQuery(string $outTradeNo): ?array { try { $tradeQuery_url = $this->domain . "/v1/netpay/wx/mini-pre-query"; $params = [ "requestTimestamp" => date("Y-m-d H:i:s"), "merOrderId" => $outTradeNo,//date("YmdHis").mt_rand(1000,9999), "mid" => $this->config['mid'], "tid" => $this->config['tid'], ]; $authorization = $this->getAuthorization($params); $headers = ["Authorization:".$authorization]; $data = httpRequest($tradeQuery_url, "POST", json_encode($params), $headers, false); // echo "
";
            // print_r($data);
            // exit();
            if ($data['errCode'] != "SUCCESS") {
                Log::append('huifu-tradeQuery', ['client' => $this->client, 'result' => $data]);
                return false;
            }
            return [
                // 支付状态: true成功 false失败
                'paySuccess' => isset($data['status']) && $data['status'] === 'TRADE_SUCCESS',
                // 第三方交易流水号
                'tradeNo' => $data['targetOrderId'] ?? ""
            ];
            return $data;
        } catch (\Throwable $e) {
            $this->throwError('银联API交易查询失败:' . $e->getMessage(), true, 'tradeQuery');
        }
        return false;
       
    }

    /**
     * 输出错误信息
     * @param string $errMessage 错误信息
     * @param bool $isLog 是否记录日志
     * @param string $action 当前的操作
     * @throws BaseException
     */
    private function throwError(string $errMessage, bool $isLog = false, string $action = '')
    {
        $this->error = $errMessage;
        $isLog && Log::append("银联-{$action}", ['errMessage' => $errMessage]);
        throwError($errMessage);
    }
    
    

    /**
     * 支付成功后的异步通知
     * @return bool
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    public function notify(): bool
    {
       // 接收表单数据
        $this->notifyParams = request()->filter([])->post();

        $notifyParams = $this->notifyParams;
        $sign = $notifyParams['sign'];
        unset($notifyParams['sign']);
        ksort($notifyParams);
        $str = http_build_query($notifyParams);
        $verifyNotify = false;
        if ($sign != md5($str)) {
            $verifyNotify = true;
        }
        
        // 判断交易单状态必须是支付成功
        $this->notifyResult = $verifyNotify && $this->notifyParams['status'] === 'TRADE_SUCCESS';
        // 记录日志
        Log::append('银联-notify', [
            'params' => $this->notifyParams,
            'verifyNotify' => $verifyNotify,
            'response' => $this->getNotifyResponse(),
            'result' => $this->notifyResult,
            'message' => '银联异步回调验证' . ($this->notifyResult ? '成功' : '失败')
        ]);
        return $this->notifyResult;
    }

    /**
     * 微信支付退款API
     * @param string $outTradeNo 第三方交易单号
     * @param string $refundAmount 退款金额
     * @param array $extra 附加数据 (需要携带订单付款总金额)
     * @return bool
     * @throws BaseException
     */
    public function refund(string $outTradeNo, string $refundAmount, array $extra = []): bool
    {

        try {
            $refund_url = $this->domain . "/v1/netpay/refund";
            $params = [
                "requestTimestamp" => date("Y-m-d H:i:s"),
                "merOrderId" => $outTradeNo,
                "mid" => $this->config['mid'],
                "tid" => $this->config['tid'],
                "targetOrderId" => $extra['trade_no'],
                "refundAmount" => bcmul($refundAmount, "100"),
                "refundOrderId" => $this->config['msgSrcId'].date("YmdHis").mt_rand(1000, 9999),
                "refundDesc" => "订单退款",
            ];
            $authorization = $this->getAuthorization($params);
            $headers = ["Authorization:".$authorization];
            
            $data = httpRequest($refund_url, "POST", json_encode($params), $headers, false);
            if ($data['errCode'] != "SUCCESS") {
                $this->throwError($data['errMsg']);
            }
            // 请求成功
            return true;
         } catch (\Throwable $e) {
            $this->throwError('银联API退款请求:' . $e->getMessage(), true, 'refund');
        }
        return false;
    }

    /**
     * 商家转账到零钱API
     * @param string $outTradeNo 交易订单号
     * @param string $totalFee 实际付款金额
     * @param array $extra 附加的数据 (需要携带openid、desc)
     * @return bool
     * @throws BaseException
     */
    public function transfers(string $outTradeNo, string $totalFee, array $extra = []): bool
    {
        if (!$this->getApp()->transfers($outTradeNo, $totalFee, $extra)) {
            $this->setError($this->getApp()->getError());
            return false;
        }
        return true;
    }

    /**
     * 获取异步回调的请求参数
     * @return array
     */
    public function getNotifyParams(): array
    {
        return [
            // 第三方交易流水号
            'tradeNo' => $this->notifyParams['targetOrderId']
        ];
    }

    /**
     * 返回异步通知结果的输出内容
     * @return string
     */
    public function getNotifyResponse(): string
    {
        return $this->notifyResult ? 'SUCCESS' : 'FAILED';
    }

    /**
     * 返回统一下单API的结果 (用于前端)
     * @return array
     * @throws BaseException
     */
    public function getUnifyResult(): array
    {
        if (empty($this->result)) {
            $this->throwError('当前没有unify结果', true, 'getUnifyResult');
        }
        $this->result['out_trade_no'] =  $this->result['merOrderId'];//str_replace($this->config['msgSrcId'], "", $this->result['merOrderId']);
        return $this->result;
    }
    /**
     * 生成授权码
     * [getAuthorization description]
     * @param  [type] $data [description]
     * @return [type]       [description]
     */
    private function getAuthorization($data){
        $appid = $this->config['appid'];
        $appkey = $this->config['appkey'];

        $timestamp = date("YmdHis",time());
        $nonce = md5(uniqid((string)microtime(true),true));
        $body = json_encode($data);
        //echo $body;
        $str = bin2hex(hash('sha256', $body, true));
        $signature = base64_encode(hash_hmac('sha256', "$appid$timestamp$nonce$str", $appkey, true));
        $authorization = 'OPEN-BODY-SIG AppId="'.$appid.'", Timestamp="'.$timestamp.'", Nonce="'.$nonce.'", Signature="'.$signature.'"';

        return $authorization;
    }
    /**
     * 异步回调地址
     * @return string
     */
    private function notifyUrl(): string
    {
        // 例如:https://www.xxxx.com/alipayNotice.php
        return base_url() . 'huifuNotice.php';
    }
}