where('config_group', self::GROUP) ->column('config_value', 'config_key'); $expireMinutes = trim((string)($rows['order_expire_minutes'] ?? '1440')); if ($expireMinutes === '' || !ctype_digit($expireMinutes)) { $expireMinutes = '1440'; } $workstationSn = trim((string)($rows['workstation_sn'] ?? '0')); if ($workstationSn === '') { $workstationSn = '0'; } $industryCode = trim((string)($rows['industry_code'] ?? '0')); if ($industryCode === '') { $industryCode = '0'; } $notifyUrl = trim((string)($rows['notify_url'] ?? '')); if ($notifyUrl === '') { $notifyUrl = $this->defaultNotifyUrl(); } return [ 'enabled' => (string)($rows['enabled'] ?? 'disabled') === 'enabled', 'api_domain' => rtrim(trim((string)($rows['api_domain'] ?? '')), '/'), 'appid' => trim((string)($rows['appid'] ?? '')), 'brand_code' => trim((string)($rows['brand_code'] ?? '')), 'store_sn' => trim((string)($rows['store_sn'] ?? '')), 'store_name' => trim((string)($rows['store_name'] ?? '')), 'workstation_sn' => $workstationSn, 'industry_code' => $industryCode, 'order_expire_minutes' => max(1, min(43200, (int)$expireMinutes)), 'merchant_private_key' => $this->normalizeKey((string)($rows['merchant_private_key'] ?? '')), 'shouqianba_public_key' => $this->normalizeKey((string)($rows['shouqianba_public_key'] ?? ''), 'PUBLIC KEY'), 'notify_url' => $notifyUrl, 'mini_program_plugin_version' => trim((string)($rows['mini_program_plugin_version'] ?? '')), ]; } public function assertReady(bool $requirePublicKey = false): array { $config = $this->getConfig(); if (!$config['enabled']) { throw new \RuntimeException('收钱吧支付未启用,请先在后台系统配置中启用。'); } $required = [ 'api_domain' => '收钱吧 API 域名', 'appid' => '收钱吧 AppID', 'brand_code' => '收钱吧品牌编号', 'store_sn' => '门店编号', 'workstation_sn' => '收银机编号', 'industry_code' => '行业代码', 'merchant_private_key' => '商户 RSA 私钥', 'notify_url' => '支付通知地址', ]; if ($requirePublicKey) { $required['shouqianba_public_key'] = '收钱吧 RSA 公钥'; } foreach ($required as $key => $label) { if (trim((string)($config[$key] ?? '')) === '') { throw new \RuntimeException(sprintf('收钱吧支付配置未完成,请先填写%s。', $label)); } } return $config; } public function h5OrderDetailUrl(int $orderId): string { $baseUrl = $this->h5PageBaseUrl(); if ($baseUrl === '') { return ''; } $fallbackQuery = http_build_query([ 'sqb_return_order_id' => $orderId, ], '', '&', PHP_QUERY_RFC3986); return $baseUrl . '/?' . $fallbackQuery . '#/pages/order/detail?id=' . $orderId; } public function miniProgramCallbackPath(int $orderId): string { return '/pages/order/detail?id=' . $orderId; } private function h5PageBaseUrl(): string { $value = (string)Db::name('system_configs') ->where('config_group', 'h5') ->where('config_key', 'page_base_url') ->value('config_value'); $baseUrl = trim($value); if ($baseUrl === '') { return ''; } $hashPos = strpos($baseUrl, '#'); if ($hashPos !== false) { $baseUrl = substr($baseUrl, 0, $hashPos); } if (!preg_match('/^https?:\/\//i', $baseUrl)) { $baseUrl = 'https://' . ltrim($baseUrl, '/'); } return rtrim($baseUrl, '/'); } private function defaultNotifyUrl(): string { $baseUrl = trim((string)($_ENV['PUBLIC_FILE_BASE_URL'] ?? '')); if ($baseUrl === '') { $baseUrl = trim((string)Db::name('system_configs') ->where('config_group', 'file_storage') ->where('config_key', 'public_base_url') ->value('config_value')); } if ($baseUrl === '') { return ''; } if (!preg_match('/^https?:\/\//i', $baseUrl)) { $baseUrl = 'https://' . ltrim($baseUrl, '/'); } return rtrim($baseUrl, '/') . self::SHOUQIANBA_NOTIFY_PATH; } private function normalizeKey(string $value, string $pemLabel = ''): string { $value = trim($value); if ($value === '') { return ''; } if (str_contains($value, '-----BEGIN')) { return $value; } if (is_file($value)) { $content = file_get_contents($value); return is_string($content) ? $this->normalizeKey($content, $pemLabel) : ''; } if ($pemLabel !== '' && $this->looksLikeBase64KeyBody($value)) { $body = preg_replace('/\s+/', '', $value) ?: ''; return sprintf( "-----BEGIN %s-----\n%s\n-----END %s-----", $pemLabel, rtrim(chunk_split($body, 64, "\n")), $pemLabel ); } return $value; } private function looksLikeBase64KeyBody(string $value): bool { $body = preg_replace('/\s+/', '', trim($value)); if (!is_string($body) || strlen($body) < 64) { return false; } return preg_match('/^[A-Za-z0-9+\/=]+$/', $body) === 1 && base64_decode($body, true) !== false; } }