<?php
// +----------------------------------------------------------------------
// | onezan-admin 快速开发的管理平台
// +----------------------------------------------------------------------
// | 官方网址：https://www.onezan.cn
// +----------------------------------------------------------------------
// | onezan团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: 543885307@qq.com
// +----------------------------------------------------------------------
namespace app\service\domain\pay\gateway;

use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory as AlipayFactory;
use app\model\UserChannelAccount;
use app\service\domain\channel\CoreChannelCatalogService;
use app\service\domain\pay\CorePayConfigService;
use think\Request;
use Yansongda\Pay\Pay;


/**
 * Alipay Gateway 支付宝支付网关类
 * @package app\service\domain\pay\gateway
 */
class AlipayGateway extends BaseGateway
{
    private CorePayConfigService $payConfig;

    /**
     * 构造函数
     * 初始化支付宝支付网关
     * @param CorePayConfigService $payConfig 支付配置服务
     */
    public function __construct(CorePayConfigService $payConfig)
    {
        $this->payConfig = $payConfig;
    }


    /**
     * 获取支付网关提供者
     * @return string 支付网关提供者
     */
    public function provider(): string
    {
        return 'alipay';
    }


    /**
     * 获取支付网关名称
     * @return string 支付网关名称
     */
    public function name(): string
    {
        return '支付宝支付';
    }


    /**
     * 检查支付支持指定终端
     * @param string $terminal 终端
     * @return bool 是否支持
     */
    public function supportsTerminal(string $terminal): bool
    {
        return in_array($terminal, ['pc', 'h5', 'app', 'aliapp'], true);
    }


    /**
     * 检查支付是否可分配
     * @return bool 是否可分配
     */

    public function isAllocatable(): bool
    {
        return true;
    }


    /**
     * 获取支付网关成功响应
     * @return array 支付网关成功响应
     */
    public function successResponse(): array
    {
        return [
            'body' => 'success',
            'headers' => [],
        ];
    }


    /**
     * 创建支付订单
     * @param array $payment 支付订单
     * @param array $order 订单信息
     * @param string $terminal 终端
     * @param array $context 上下文信息
     * @return array 支付订单
     */
    public function create(array $payment, array $order, string $terminal, array $context): array
    {
        $subject = $this->buildSubject($payment, $order);

        if ($this->isTestMode()) {
            return $this->testModeCreateResponse($payment, $terminal);
        }

        $config = $this->buildGatewayConfig($terminal);
        $this->bootAlipay($config);
        $client = AlipayFactory::util()->generic();
        $outTradeNo = (string)($payment['payment_no'] ?? '');
        $amount = $this->normalizeAmount($payment['amount'] ?? 0);

        return $this->dispatchByTerminal($client, $outTradeNo, $amount, $subject, $terminal, $config, $context);
    }


    /**
     * 处理支付通知
     * @param Request $request 请求对象
     * @param string $terminal 终端
     * @return array 支付通知响应
     */
    public function notify(Request $request, string $terminal): array
    {
        $config = $this->buildGatewayConfig($terminal);
        $this->bootAlipay($config);

        $data = Pay::alipay()->callback($request->param());
        $tradeStatus = strtoupper((string)$data->get('trade_status', ''));

        $code = in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true) ? 0 : 1;
        return [
            'code' => $code,
            'payment_no' => (string)$data->get('out_trade_no', ''),
            'trade_no' => (string)$data->get('trade_no', ''),
            'amount' => (string)$data->get('total_amount', '0'),
            'raw' => $data->all(),
        ];
    }


    /**
     * 处理支付退款
     * @param array $refund 退款订单
     * @param array $payment 支付订单
     * @return array 退款订单
     */
    public function refund(array $refund, array $payment): array
    {
        if ($this->isTestMode()) {
            return $this->testModeRefundResponse($refund, $payment);
        }

        $terminal = $this->normalizeTerminal((string)($payment['terminal'] ?? ''));
        $config = $this->buildGatewayConfig($terminal);
        $this->bootAlipay($config);
        $client = AlipayFactory::util()->generic();

        $response = $client->execute('alipay.trade.refund', [], [
            'out_trade_no' => (string)($payment['payment_no'] ?? ''),
            'refund_amount' => $this->normalizeAmount($refund['amount'] ?? 0),
            'refund_reason' => (string)($refund['reason'] ?? ''),
            'out_request_no' => (string)($refund['refund_no'] ?? ''),
        ]);

        $data = $this->parseResponseBody((string)($response->httpBody ?? ''));
        if (($data['code'] ?? '') !== '10000') {
            throw new \RuntimeException((string)($data['sub_msg'] ?? $data['msg'] ?? ''));
        }

        $fundChange = strtoupper((string)($data['fund_change'] ?? 'N'));
        $isSuccess = $fundChange === 'Y';

        return [
            'code' => 0,
            'msg' => $isSuccess ? '' : '',
            'status' => $isSuccess ? 3 : 2,
            'remark' => $isSuccess ? '' : '',
            'extra' => ['refund_response' => $data],
        ];
    }


    /**
     * 处理支付退款通知
     * @param Request $request 请求对象
     * @param string $terminal 终端
     * @return array 退款通知响应
     */
    public function refundNotify(Request $request, string $terminal): array
    {
        return ['code' => 0, 'refund_no' => '', 'status' => 'SUCCESS', 'raw' => []];
    }


     /**
     * 构建支付网关配置
     * @param string $terminal 终端
     * @return array 支付网关配置
     */

    public function buildGatewayConfig(string $terminal): array
    {
        $channel = $this->terminalChannel($terminal);
        $config = $this->payConfig->getProviderConfig($channel, 'alipay');

        if ($this->isTestMode()) {
            return array_merge($config, [
                'app_id' => (string)($config['app_id'] ?? '') ?: '2021000000000001',
                'private_key' => (string)($config['private_key'] ?? '') ?: 'test_private_key',
                'alipay_public_key' => (string)($config['alipay_public_key'] ?? '') ?: 'test_public_key',
                '_test_mode' => true,
            ]);
        }

        foreach (['app_id', 'private_key', 'alipay_public_key'] as $field) {
            if (empty($config[$field])) {
                throw new \RuntimeException('' . $field);
            }
        }

        return $config;
    }

     /**
     * 处理支付订单
     * @param $client 支付客户端
     * @param string $outTradeNo 订单号
     * @param string $amount 金额
     * @param string $subject 订单标题
     * @param string $terminal 终端
     * @param array $config 支付网关配置
     * @param array $context 上下文信息
     * @return array 支付订单
     */
    private function dispatchByTerminal($client, string $outTradeNo, string $amount, string $subject, string $terminal, array $config, array $context): array
    {
        if ($terminal === 'pc') {
            $response = $client->execute('alipay.trade.precreate', [], [
                'out_trade_no' => $outTradeNo,
                'total_amount' => $amount,
                'subject' => $subject,
            ]);
            $data = $this->parseResponseBody((string)($response->httpBody ?? ''));
            if (($data['code'] ?? '') !== '10000') {
                throw new \RuntimeException((string)($data['sub_msg'] ?? $data['msg'] ?? ''));
            }
            return [
                'action' => 'qrcode',
                'message' => '',
                'code_url' => (string)($data['qr_code'] ?? ''),
                'trade_no' => (string)($data['trade_no'] ?? ''),
                'params' => $data,
            ];
        }

        if ($terminal === 'h5') {
            $sdkResponse = $client->sdkExecute('alipay.trade.wap.pay', [], [
                'out_trade_no' => $outTradeNo,
                'total_amount' => $amount,
                'subject' => $subject,
                'product_code' => 'QUICK_WAP_WAY',
                'quit_url' => (string)($config['return_url'] ?? ''),
            ]);
            return [
                'action' => 'redirect',
                'message' => '',
                'order_string' => (string)($sdkResponse->body ?? ''),
                'redirect_url' => $this->buildAlipayGatewayUrl($config, (string)($sdkResponse->body ?? '')),
            ];
        }

        if ($terminal === 'app') {
            $sdkResponse = $client->sdkExecute('alipay.trade.app.pay', [], [
                'out_trade_no' => $outTradeNo,
                'total_amount' => $amount,
                'subject' => $subject,
            ]);
            return [
                'action' => 'app',
                'message' => '',
                'order_string' => (string)($sdkResponse->body ?? ''),
            ];
        }

        if ($terminal === 'aliapp') {
            $userId = (int)($context['user_id'] ?? 0);
            $buyerId = $this->resolveAlipayBuyerId($userId);
            $response = $client->execute('alipay.trade.create', [], [
                'out_trade_no' => $outTradeNo,
                'total_amount' => $amount,
                'subject' => $subject,
                'buyer_id' => $buyerId,
                'product_code' => 'JSAPI_PAY',
            ]);
            $data = $this->parseResponseBody((string)($response->httpBody ?? ''));
            if (($data['code'] ?? '') !== '10000') {
                throw new \RuntimeException((string)($data['sub_msg'] ?? $data['msg'] ?? ''));
            }
            return [
                'action' => 'mini',
                'message' => '',
                'trade_no' => (string)($data['trade_no'] ?? ''),
                'out_trade_no' => (string)($data['out_trade_no'] ?? ''),
                'params' => $data,
            ];
        }

        throw new \RuntimeException('');
    }

     /**
     * 初始化支付宝支付
     * @param array $config 支付网关配置
     */

    private function bootAlipay(array $config): void
    {
        $options = new Config();
        $options->protocol = 'https';
        $options->gatewayHost = trim((string)($config['gateway'] ?? 'openapi.alipay.com')) ?: 'openapi.alipay.com';
        $options->signType = 'RSA2';
        $options->appId = (string)($config['app_id'] ?? '');
        $options->merchantPrivateKey = (string)($config['private_key'] ?? '');
        $options->alipayPublicKey = (string)($config['alipay_public_key'] ?? '');
        $options->notifyUrl = (string)($config['notify_url'] ?? '');
        AlipayFactory::setOptions($options);
    }

     /**
     * 解析支付宝支付响应体
     * @param string $body 响应体
     * @return array 解析后的响应体
     */

    private function parseResponseBody(string $body): array
    {
        $decoded = json_decode($body, true);
        if (!is_array($decoded)) {
            return [];
        }
        $result = $decoded;
        foreach (['alipay_trade_precreate_response', 'alipay_trade_wap_pay_response',
                     'alipay_trade_create_response', 'alipay_trade_refund_response',
                     'alipay_trade_app_pay_response'] as $key) {
            if (isset($decoded[$key])) {
                $result = $decoded[$key];
                break;
            }
        }
        return is_array($result) ? $result : $decoded;
    }

     /**
     * 构建支付宝支付网关URL
     * @param array $config 支付网关配置
     * @param string $orderString 订单字符串
     * @return string 支付网关URL
     */

    private function buildAlipayGatewayUrl(array $config, string $orderString): string
    {
        $gateway = trim((string)($config['gateway'] ?? 'openapi.alipay.com')) ?: 'openapi.alipay.com';
        return 'https://' . $gateway . '/gateway.do?' . $orderString;
    }

     /**
     * 解析支付宝支付买家ID
     * @param int $userId 用户ID
     * @return string 买家ID
     */

    private function resolveAlipayBuyerId(int $userId): string
    {
        $account = UserChannelAccount::where([
            'user_id' => $userId,
            'channel' => 'aliapp',
        ])->find();

        if (!$account || empty($account->provider_uid)) {
            throw new \RuntimeException('');
        }

        return (string)$account->provider_uid;
    }

     /**
     * 格式化金额
     * @param float $amount 金额
     * @return string 格式化后的金额
     */

    private function normalizeAmount($amount): string
    {
        return number_format((float)$amount, 2, '.', '');
    }

     /**
     * 转换支付宝支付终端
     * @param string $terminal 终端
     * @return string 转换后的终端
     */

    private function terminalChannel(string $terminal): string
    {
        return $terminal === 'pc' ? 'h5' : $terminal;
    }
}