items = Db::name('shopro_huifu_config')->column('value', 'name'); } catch (\Exception $e) { $this->items = []; } } public function all() { return $this->items; } public function get($name, $default = '') { return $this->items[$name] ?? $default; } public function getGatewayUrl() { $env = $this->get('env', 'test'); $url = $this->get('gateway_url'); if ($url) { return rtrim($url, '/'); } return $env === 'prod' ? 'https://api.huifu.com' : 'https://spin-test.cloudpnr.com'; } public function isProdEnv() { return $this->get('env', 'prod') !== 'test'; } /** * 供汇付 SDK 使用:仅返回 base64 密钥体(不含 PEM 头尾)。 * SDK 内部会自行包裹 -----BEGIN PRIVATE KEY----- 后再签名。 */ public function getPrivateKey() { return $this->extractKeyBody($this->get('merchant_private_key')); } public function getPublicKey() { return $this->extractKeyBody($this->get('huifu_public_key')); } public function validateKeys() { $privateBody = $this->getPrivateKey(); $publicBody = $this->getPublicKey(); if ($privateBody === '') { throw new \RuntimeException('商户RSA私钥未配置'); } if ($publicBody === '') { throw new \RuntimeException('汇付RSA公钥未配置'); } $privatePem = $this->wrapPem($privateBody, 'PRIVATE KEY'); $privateRes = openssl_pkey_get_private($privatePem); if ($privateRes === false) { $privatePem = $this->wrapPem($privateBody, 'RSA PRIVATE KEY'); $privateRes = openssl_pkey_get_private($privatePem); } if ($privateRes === false) { throw new \RuntimeException('商户RSA私钥格式无效,请从汇付控台复制完整私钥后重新保存'); } $publicPem = $this->wrapPem($publicBody, 'PUBLIC KEY'); $publicRes = openssl_pkey_get_public($publicPem); if ($publicRes === false) { throw new \RuntimeException('汇付RSA公钥格式无效,请从汇付控台复制完整公钥后重新保存'); } $signature = ''; if (!openssl_sign('huifu_key_probe', $signature, $privateRes, OPENSSL_ALGO_SHA256) || $signature === '') { throw new \RuntimeException('商户RSA私钥无法用于签名,请重新粘贴汇付控台商户私钥'); } } public function shouldDelayAcct() { $split = Db::name('shopro_split_config')->column('value', 'name'); if (($split['split_enable'] ?? '0') !== '1') { return false; } if (($split['delay_acct_enable'] ?? '0') !== '1') { return false; } if (in_array($split['split_mode'] ?? '', ['huifu', 'both'])) { return true; } return ($split['split_provider'] ?? 'auto') === 'huifu'; } /** * 站点公网 HTTPS 根地址(notify 必须稳定可达,不能依赖单次 request()->root()) */ public function getSiteBaseUrl() { $custom = trim((string) $this->get('site_base_url')); if ($custom !== '') { return rtrim($custom, '/'); } try { $row = Db::name('shopro_config')->where('name', 'shopro')->find(); if ($row && !empty($row['value'])) { $value = json_decode($row['value'], true); if (!empty($value['domain'])) { $domain = rtrim((string) $value['domain'], '/'); if (strpos($domain, 'http') !== 0) { $domain = 'https://' . ltrim($domain, '/'); } return $domain; } } } catch (\Exception $e) { // ignore } $root = request()->root(true); if (strpos($root, 'https://') !== 0) { if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $scheme = trim(explode(',', (string) $_SERVER['HTTP_X_FORWARDED_PROTO'])[0]); if ($scheme === 'https') { $root = preg_replace('#^https?://#', 'https://', $root); } } if (strpos($root, 'https://') !== 0 && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { $root = preg_replace('#^http://#', 'https://', $root); } } return rtrim($root, '/'); } public function getNotifyUrl($type = 'pay', $platform = 'wxMiniProgram') { if ($type === 'refund') { $custom = trim((string) $this->get('refund_notify_url')); if ($custom !== '') { return rtrim($custom, '/'); } return $this->getSiteBaseUrl() . '/addons/shopro/pay/notifyhuifurefund/platform/' . $platform; } $custom = trim((string) $this->get('pay_notify_url')); if ($custom !== '') { return rtrim($custom, '/'); } // 走 shopro 主插件路由(与 prepay 同入口,避免 shopro_huifu 子插件路由不可达) return $this->getSiteBaseUrl() . '/addons/shopro/pay/notifyhuifu'; } protected function extractKeyBody($key) { $key = trim((string) $key); if ($key === '') { return ''; } $key = preg_replace('/-----BEGIN[^-]+-----/', '', $key); $key = preg_replace('/-----END[^-]+-----/', '', $key); return preg_replace('/\s+/', '', $key); } protected function wrapPem($body, $label) { return "-----BEGIN {$label}-----\n" . wordwrap($body, 64, "\n", true) . "\n-----END {$label}-----"; } public function formatGoodsDesc($orderSn) { $tpl = $this->get('goods_desc_tpl', '商城订单{order_sn}'); return str_replace('{order_sn}', $orderSn, $tpl); } }