Files
anxinyan/server-api/app/support/ShouqianbaConfigService.php
2026-06-04 16:12:59 +08:00

180 lines
6.1 KiB
PHP

<?php
namespace app\support;
use support\think\Db;
class ShouqianbaConfigService
{
private const GROUP = 'payment';
private const SHOUQIANBA_NOTIFY_PATH = '/api/open/shouqianba/payment/notify';
public const MINI_PROGRAM_PLUGIN_PROVIDER = 'wx7903bb295ac26ac7';
public function getConfig(): array
{
$rows = Db::name('system_configs')
->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;
}
}