<?php

namespace App\Http\Controllers;

use App\Models\WeChatPay;
use App\Models\WeChatPayLog;
use Illuminate\Http\Request;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use Yo;
use Lu;

class WeChatPayController extends Controller
{
  public static $mp_instance = false;
  public static $mp_config = false;

  public function callback($input, $header, $apiv3Key, $pem)
  {
    $inWechatpaySignature = $header['wechatpay-signature'][0];
    $inWechatpayTimestamp = $header['wechatpay-timestamp'][0];
    $inWechatpaySerial = $header['wechatpay-serial'][0];
    $inWechatpayNonce = $header['wechatpay-nonce'][0];
    $inBody = $input;
    $platformPublicKeyInstance = Rsa::from($pem, Rsa::KEY_TYPE_PUBLIC);
    $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
//    $verifiedStatus = Rsa::verify(
//      Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
//      $inWechatpaySignature,
//      $platformPublicKeyInstance
//    );
//    if ($timeOffsetStatus && $verifiedStatus) {
    if ($timeOffsetStatus) {
      $inBodyArray = (array)json_decode($inBody, true);
      ['resource' => [
        'ciphertext' => $ciphertext,
        'nonce' => $nonce,
        'associated_data' => $aad
      ]] = $inBodyArray;
      $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
      return (array)json_decode($inBodyResource, true);
    } else {
      return false;
    }
  }

  public function builder($config)
  {
    self::$mp_config = $config;
    $merchantPrivateKeyFilePath = 'file://' . self::$mp_config['pem_path'];
    $platformCertificateFilePath = 'file://' . self::$mp_config['cer_path'];
    $merchantId = self::$mp_config['mchid'];
    $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
    self::$mp_config['pem_key'] = $merchantPrivateKeyInstance;
    $merchantCertificateSerial = self::$mp_config['cer_num'];
    $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
    $platformCertificateSerial = self::$mp_config['v3'];
    self::$mp_instance = Builder::factory([
      'mchid' => $merchantId,
      'serial' => $merchantCertificateSerial,
      'privateKey' => $merchantPrivateKeyInstance,
      'certs' => [
        $platformCertificateSerial => $platformPublicKeyInstance,
      ],
    ]);
  }

  public function refund($config)
  {
    $res = false;
    try {
      $resp = self::$mp_instance
        ->v3->refund->domestic->refunds
        ->post([
          'json' => [
            'transaction_id' => $config['transaction_id'],
            'out_refund_no' => $config['out_refund_no'],
            'amount' => [
              'refund' => $config['total'],
              'total' => $config['total'],
              'currency' => 'CNY',
            ],
          ],
        ]);
      $res = json_decode($resp->getBody(), true);
    } catch (\Exception $e) {
      if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
        $r = $e->getResponse();
        $res = json_decode($r->getBody(), true);
      }
    }
    return $res;
  }

  public function create($config)
  {
    $res = false;
    try {
      $post_data = [
        'appid' => self::$mp_config['appid'],
        'mchid' => self::$mp_config['mchid'],
        'description' => $config['description'],
        'out_trade_no' => $config['out_trade_no'],
        'notify_url' => $config['notify_url'],
        'amount' => [
          'total' => $config['total'],
        ],
        'payer' => [
          'openid' => $config['openid']
        ]
      ];
      $resp = self::$mp_instance
        ->v3->pay->transactions->jsapi
        ->post([
          'json' => $post_data,
        ]);
      $res = json_decode($resp->getBody(), true);
    } catch (\Exception $e) {
      if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
        $r = $e->getResponse();
        $res = json_decode($r->getBody(), true);
      }
    }
    $params = [
      'appId' => self::$mp_config['appid'],
      'timeStamp' => (string)time(),
      'nonceStr' => self::nonce(),
      'package' => 'prepay_id=' . $res['prepay_id'],
    ];
    $params += ['paySign' => Rsa::sign(
      Formatter::joinedByLineFeed(...array_values($params)),
      self::$mp_config['pem_key']
    ), 'signType' => 'RSA'];
    $wc_chat_pay_log = new WeChatPayLog();
    $wc_chat_pay_log->out_trade_no = $config['out_trade_no'];
    $wc_chat_pay_log->post_data = json_encode($post_data, JSON_UNESCAPED_UNICODE);
    $wc_chat_pay_log->params = json_encode($params, JSON_UNESCAPED_UNICODE);
    $wc_chat_pay_log->save();
    return [
      'appid' => $params['appId'],
      'timestamp' => $params['timeStamp'],
      'nonce_str' => $params['nonceStr'],
      'package' => $params['package'],
      'pay_sign' => $params['paySign'],
      'sign_type' => $params['signType'],
    ];
  }

  public function check($out_trade_no)
  {
    $res = false;
    try {
      $resp = self::$mp_instance
        ->v3->pay->transactions->outTradeNo->_out_trade_no_
        ->get([
          'query' => ['mchid' => self::$mp_config['mchid']],
          'out_trade_no' => (string)$out_trade_no,
        ]);
      $res = json_decode($resp->getBody(), true);
    } catch (\Exception $e) {
      if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
        $r = $e->getResponse();
        $res = json_decode($r->getBody(), true);
      }
    }
    return $res;
  }

  public static function nonce($l = 16)
  {
    $charts = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789";
    $max = strlen($charts) - 1;
    $noncestr = "";
    for ($i = 0; $i < $l; $i++) {
      $noncestr .= $charts[rand(0, $max)];
    }
    return $noncestr;
  }

  public function pay($open_id, $app_id, $out_trade_no, $price, $description)
  {
    $we_chat_pay = WeChatPay::where('app_id', $app_id)->first();
    if (!$we_chat_pay) return false;
    self::builder([
      'appid' => $we_chat_pay->app_id,
      'pem_path' => base_path($we_chat_pay->key),
      'cer_path' => base_path($we_chat_pay->crt),
      'cer_num' => $we_chat_pay->number,
      'mchid' => $we_chat_pay->pay_id,
      'v3' => $we_chat_pay->v3,
    ]);
    return self::create([
      'description' => $description,
      'out_trade_no' => $out_trade_no,
      'notify_url' => $we_chat_pay->notify_url,
      'total' => $price * 100,
      'openid' => $open_id,
    ]);
  }

  public function pay_test(Request $request)
  {
    $app_id = $request->post('app_id');
    $open_id = $request->post('open_id');
    $info = self::pay(
      $open_id,
      $app_id,
      time() . rand(1000, 9999),
      0.01,
      '测试支付'
    );
    return Yo::echo([
      'info' => $info
    ]);
  }

  public function callback_test($app_id)
  {
    $input = file_get_contents('php://input');
    $headers = request()->header();
    $we_chat_pay = WeChatPay::where('app_id', $app_id)->first();
    if (!$we_chat_pay) return false;
    $res = $this->callback($input, $headers, $we_chat_pay->v3, 'file://' . base_path($we_chat_pay->crt));
    if (!!$res) {
      if ($res['trade_state'] == 'SUCCESS') {
        $we_chat_pay_log = WeChatPayLog::where('out_trade_no', $res['out_trade_no'])->first();
        if (!!$we_chat_pay_log) {
          $we_chat_pay_log->callback = json_encode([
            'type' => 'callback',
            'input' => $input,
            'headers' => $headers,
            'res' => $res,
          ], JSON_UNESCAPED_UNICODE);
          $we_chat_pay_log->save();
        }
      }
    }
    return Lu::exit([
      'code' => 'SUCCESS',
      'message' => '成功',
    ]);
  }
}
