'https://openapi.alipay.com/gateway.do?charset=utf-8', self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8', ]; /** * Alipay payload. * * @var array */ protected $payload; /** * Alipay gateway. * * @var string */ protected $gateway; /** * extends. * * @var array */ protected $extends; /** * Bootstrap. * * @author yansongda * * @throws \Exception */ public function __construct(Config $config) { $this->gateway = Support::create($config)->getBaseUri(); $this->payload = [ 'app_id' => $config->get('app_id'), 'method' => '', 'format' => 'JSON', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'version' => '1.0', 'return_url' => $config->get('return_url'), 'notify_url' => $config->get('notify_url'), 'timestamp' => date('Y-m-d H:i:s'), 'sign' => '', 'biz_content' => '', 'app_auth_token' => $config->get('app_auth_token'), ]; if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) { $this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key')); $this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert')); } } /** * Magic pay. * * @author yansongda * * @param string $method * @param array $params * * @throws GatewayException * @throws InvalidArgumentException * @throws InvalidConfigException * @throws InvalidGatewayException * @throws InvalidSignException * * @return Response|Collection */ public function __call($method, $params) { if (isset($this->extends[$method])) { return $this->makeExtend($method, ...$params); } return $this->pay($method, ...$params); } /** * Pay an order. * * @author yansongda * * @param string $gateway * @param array $params * * @throws InvalidGatewayException * * @return Response|Collection */ public function pay($gateway, $params = []) { Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params)); $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url']; $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url']; unset($params['return_url'], $params['notify_url']); $this->payload['biz_content'] = json_encode($params); $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway'; if (class_exists($gateway)) { return $this->makePay($gateway); } throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists"); } /** * Verify sign. * * @author yansongda * * @param array|null $data * * @throws InvalidSignException * @throws InvalidConfigException */ public function verify($data = null, bool $refund = false): Collection { if (is_null($data)) { $request = Request::createFromGlobals(); $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all(); } if (isset($data['fund_bill_list'])) { $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']); } Events::dispatch(new Events\RequestReceived('Alipay', '', $data)); if (Support::verifySign($data)) { return new Collection($data); } Events::dispatch(new Events\SignFailed('Alipay', '', $data)); throw new InvalidSignException('Alipay Sign Verify FAILED', $data); } /** * Query an order. * * @author yansongda * * @param string|array $order * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException */ public function find($order, string $type = 'wap'): Collection { $gateway = get_class($this).'\\'.Str::studly($type).'Gateway'; if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) { throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method"); } $config = call_user_func([new $gateway(), 'find'], $order); $this->payload['method'] = $config['method']; $this->payload['biz_content'] = $config['biz_content']; $this->payload['sign'] = Support::generateSign($this->payload); Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload)); return Support::requestApi($this->payload); } /** * Refund an order. * * @author yansongda * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException */ public function refund(array $order): Collection { $this->payload['method'] = 'alipay.trade.refund'; $this->payload['biz_content'] = json_encode($order); $this->payload['sign'] = Support::generateSign($this->payload); Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload)); return Support::requestApi($this->payload); } /** * Cancel an order. * * @author yansongda * * @param array|string $order * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException */ public function cancel($order): Collection { $this->payload['method'] = 'alipay.trade.cancel'; $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); $this->payload['sign'] = Support::generateSign($this->payload); Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload)); return Support::requestApi($this->payload); } /** * Close an order. * * @param string|array $order * * @author yansongda * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException */ public function close($order): Collection { $this->payload['method'] = 'alipay.trade.close'; $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); $this->payload['sign'] = Support::generateSign($this->payload); Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload)); return Support::requestApi($this->payload); } /** * Download bill. * * @author yansongda * * @param string|array $bill * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException */ public function download($bill): string { $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query'; $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]); $this->payload['sign'] = Support::generateSign($this->payload); Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload)); $result = Support::requestApi($this->payload); return ($result instanceof Collection) ? $result->get('bill_download_url') : ''; } /** * Reply success to alipay. * * @author yansongda */ public function success(): Response { Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway)); return new Response('success'); } /** * extend. * * @author yansongda * * @throws GatewayException * @throws InvalidConfigException * @throws InvalidSignException * @throws InvalidArgumentException */ public function extend(string $method, callable $function, bool $now = true): ?Collection { if (!$now && !method_exists($this, $method)) { $this->extends[$method] = $function; return null; } $customize = $function($this->payload); if (!is_array($customize) && !($customize instanceof Collection)) { throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); } Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize)); if (is_array($customize)) { $this->payload = $customize; $this->payload['sign'] = Support::generateSign($this->payload); return Support::requestApi($this->payload); } return $customize; } /** * Make pay gateway. * * @author yansongda * * @throws InvalidGatewayException * * @return Response|Collection */ protected function makePay(string $gateway) { $app = new $gateway(); if ($app instanceof GatewayInterface) { return $app->pay($this->gateway, array_filter($this->payload, function ($value) { return '' !== $value && !is_null($value); })); } throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface"); } /** * makeExtend. * * @author yansongda * * @throws GatewayException * @throws InvalidArgumentException * @throws InvalidConfigException * @throws InvalidSignException */ protected function makeExtend(string $method, array ...$params): Collection { $params = count($params) >= 1 ? $params[0] : $params; $function = $this->extends[$method]; $customize = $function($this->payload, $params); if (!is_array($customize) && !($customize instanceof Collection)) { throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); } Events::dispatch(new Events\MethodCalled( 'Alipay', 'extend - '.$method, $this->gateway, is_array($customize) ? $customize : $customize->toArray() )); if (is_array($customize)) { $this->payload = $customize; $this->payload['sign'] = Support::generateSign($this->payload); return Support::requestApi($this->payload); } return $customize; } }