205 行
6.4 KiB
PHP
205 行
6.4 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro_huifu\library;
|
||
|
||
use think\Db;
|
||
|
||
class HuifuConfig
|
||
{
|
||
protected static $instance;
|
||
protected $items = [];
|
||
|
||
public static function getInstance()
|
||
{
|
||
if (!self::$instance) {
|
||
self::$instance = new self();
|
||
}
|
||
return self::$instance;
|
||
}
|
||
|
||
public static function reload()
|
||
{
|
||
self::$instance = new self();
|
||
return self::$instance;
|
||
}
|
||
|
||
protected function __construct()
|
||
{
|
||
try {
|
||
$this->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);
|
||
}
|
||
}
|