filter('available_payment_methods', function ($methods) { if ($this->getConfig('enabled', true)) { $methods['EPay'] = [ 'name' => $this->getConfig('display_name', '易支付'), 'icon' => $this->getConfig('icon', '💳'), 'plugin_code' => $this->getPluginCode(), 'type' => 'plugin' ]; } return $methods; }); } public function form(): array { return [ 'url' => [ 'label' => '支付网关地址', 'type' => 'string', 'required' => true, 'description' => '请填写完整的支付网关地址,包括协议(http或https)' ], 'pid' => [ 'label' => '商户ID', 'type' => 'string', 'description' => '请填写商户ID', 'required' => true ], 'key' => [ 'label' => '通信密钥', 'type' => 'string', 'required' => true, 'description' => '请填写通信密钥' ], 'type' => [ 'label' => '支付类型', 'type' => 'string', 'description' => '支付类型,如: alipay, wxpay, qqpay 等,可自定义' ], ]; } public function pay($order): array { $params = [ 'money' => $order['total_amount'] / 100, 'name' => $order['trade_no'], 'notify_url' => $order['notify_url'], 'return_url' => $order['return_url'], 'out_trade_no' => $order['trade_no'], 'pid' => $this->getConfig('pid') ]; if ($paymentType = $this->getConfig('type')) { $params['type'] = $paymentType; } $params['sign'] = $this->signature($params); $params['sign_type'] = 'MD5'; return [ 'type' => 1, 'data' => rtrim($this->getConfig('url'), '/') . '/submit.php?' . http_build_query($params) ]; } public function notify($params): array|bool { foreach ($params as $value) { if (!is_scalar($value) && $value !== null) { return false; } } if (!is_string($params['sign'] ?? null) || ($params['sign_type'] ?? 'MD5') !== 'MD5' || !hash_equals($this->signature($params), $params['sign']) || ($params['trade_status'] ?? null) !== 'TRADE_SUCCESS' || (string) ($params['pid'] ?? '') !== (string) $this->getConfig('pid') || ($this->getConfig('type') && ($params['type'] ?? null) !== $this->getConfig('type')) || empty($params['out_trade_no']) || empty($params['trade_no'])) { return false; } $order = $this->findOrder((string) $params['out_trade_no']); $amount = $this->amountInCents((string) ($params['money'] ?? '')); if (!$order || (int) $order->payment_id !== (int) $this->getConfig('id') || $amount === null || $amount !== (int) $order->total_amount + (int) $order->handling_amount || !in_array((int) $order->status, [Order::STATUS_PENDING, Order::STATUS_PROCESSING, Order::STATUS_COMPLETED], true) || ($order->callback_no && $order->callback_no !== (string) $params['trade_no'])) { return false; } return [ 'trade_no' => $params['out_trade_no'], 'callback_no' => $params['trade_no'] ]; } protected function findOrder(string $tradeNo): ?Order { return Order::where('trade_no', $tradeNo)->first(); } private function signature(array $params): string { unset($params['sign'], $params['sign_type']); $params = array_filter($params, static fn ($value) => $value !== '' && $value !== null); ksort($params, SORT_STRING); $parts = []; foreach ($params as $key => $value) { $parts[] = $key . '=' . $value; } return md5(implode('&', $parts) . $this->getConfig('key')); } private function amountInCents(string $amount): ?int { if (!preg_match('/\A([0-9]{1,10})(?:\.([0-9]{1,2}))?\z/', $amount, $matches)) { return null; } return (int) $matches[1] * 100 + (int) str_pad($matches[2] ?? '', 2, '0'); } }