Files
YwxAppThink/addon/paycenter/config/paycenter.php
T
yangwx 18101cf11a feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

106 lines
6.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | 支付中心插件静态配置(随插件分发,env 驱动)
// +----------------------------------------------------------------------
// 与表单 config.php 分工:
// config.php → 后台可编辑的「开关 / 默认场景」
// config/paycenter.php → 部署相关的「商户密钥 / 证书」,只从 .env 读,绝不写数据库
//
// 若核心 config/pay.php 已存在且非空的同名项,由 PayConfig 用核心值覆盖(兼容既有部署)。
// 应用私钥兼容处理:
// .env 只能放单行,私钥通常以「裸 base64」贴进来(带 PEM 头尾的多行内容塞不进 .env)。
// SDK 的 get_private_cert()(vendor/yansongda/pay/src/Functions.php)规则很挑食:
// ① 是文件路径 → 读文件内容(内容仍要符合 ②③)
// ② 以 '-----BEGIN PRIVATE KEY-----' 开头 → 原样返回(必须是真正的 PKCS8)
// ③ 其余一律再包一层 '-----BEGIN RSA PRIVATE KEY-----'(按 PKCS1 处理)
// 由此得出:PKCS8 必须自己拼好 PKCS8 的 PEM 头;PKCS1 反而必须交【裸 base64】让 SDK 去包。
// 反过来做(给 PKCS1 套 'PRIVATE KEY' 头 / 给已是 PEM 的内容再套一层 RSA 头)就会:
// openssl_sign(): Supplied key param cannot be coerced into a private key
// 所以这里按 DER 里是否含 rsaEncryption 的 OID 判定 PKCS8/PKCS1,再交给 SDK 正确的一侧。
$normalizePrivateKey = static function (?string $secret): string {
$value = trim((string) $secret);
if ($value === '') {
return '';
}
if (is_file($value)) {
$value = (string) file_get_contents($value);
}
$body = (string) preg_replace('/-----[^-]+-----|\s+/', '', $value);
if ($body === '') {
return '';
}
$der = base64_decode($body, true) ?: '';
// rsaEncryption OID:1.2.840.113549.1.1.1 —— 只出现在 PKCS8 的结构里
$isPkcs8 = str_contains(substr($der, 0, 64), "\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01");
return $isPkcs8
? "-----BEGIN PRIVATE KEY-----\n" . wordwrap($body, 64, "\n", true) . "\n-----END PRIVATE KEY-----"
: $body;
};
// 证书路径统一按项目根目录解析成绝对路径:
// SDK 的 get_public_cert() 是 `is_file($key) ? file_get_contents($key) : $key`,
// 相对路径按【进程 CWD】解析:CLI 在根目录能读到,Web 入口 CWD 是 public/ 就读不到,
// 下单会抛「配置异常: 解析 `app_public_cert_path` 失败」,回调验签同样失败。
$resolveCertPath = static function (?string $path): string {
$path = trim((string) $path);
if ($path === '') {
return '';
}
if (str_starts_with($path, '/') || str_starts_with($path, '\\') || preg_match('/^[A-Za-z]:[\\\\\/]/', $path)) {
return $path;
}
$root = function_exists('root_path') ? root_path() : dirname(__DIR__, 3) . DIRECTORY_SEPARATOR;
return $root . ltrim($path, '/\\');
};
return [
// 模拟支付开关:仅在 APP_DEBUG=true 且无商户号时生效(判定在业务侧,这里只做透传)
'mock_enable' => env('PAY_MOCK', false),
// 支付 SDK 日志开关。
// 不要跟 APP_DEBUG 绑定:生产一旦误开 debug,会把明文请求/签名细节写进 runtime/log/pay.log。
// 需要排查时显式 PAY_LOG=true 打开,查完务必关掉。
'log_enable' => (bool) env('PAY_LOG', false),
'log_level' => (string) env('PAY_LOG_LEVEL', 'debug'),
// 支付宝(Yansongda Pay v3 —— 仅支持「证书模式」,普通公钥模式不再被识别)
'alipay' => [
// mode 必须是【整数】:0=正式 1=沙箱 2=服务商(Yansongda\Pay\Pay::MODE_*)
'mode' => (int) env('PAY_ALIPAY_MODE', 0),
'app_id' => env('PAY_ALIPAY_APP_ID', ''),
'app_secret_cert' => $normalizePrivateKey((string) env('PAY_ALIPAY_APP_SECRET_CERT', '')),
'app_public_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_APP_PUBLIC_CERT_PATH', '')),
'alipay_public_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_PUBLIC_CERT_PATH', '')),
'alipay_root_cert_path' => $resolveCertPath((string) env('PAY_ALIPAY_ROOT_CERT_PATH', '')),
'return_url' => '',
'notify_url' => '',
],
// 微信支付(v3)。注:微信早已下线官方沙箱,mode 留 0 即可。
'wechat' => [
'mode' => (int) env('PAY_WECHAT_MODE', 0),
'mch_id' => env('PAY_WECHAT_MCH_ID', ''),
// 直连模式填公众号/小程序/移动应用的 appid。
// SDK 按场景取不同的键(见 get_wechat_type_key):mp → mp_app_id、mini → mini_app_id、app → app_id,
// 未单独配置时统一回落到 PAY_WECHAT_APP_ID,一个商户只接一种场景时配一个就够。
'app_id' => env('PAY_WECHAT_APP_ID', ''),
'mp_app_id' => env('PAY_WECHAT_MP_APP_ID', env('PAY_WECHAT_APP_ID', '')),
'mini_app_id' => env('PAY_WECHAT_MINI_APP_ID', env('PAY_WECHAT_APP_ID', '')),
// APIv3 密钥(微信商户平台 → API 安全 → 设置 APIv3 密钥)
'mch_secret_key' => env('PAY_WECHAT_SECRET_KEY', ''),
// 商户 API 私钥:PEM 全文、单行 base64(上方已自动识别 PKCS8/PKCS1)或文件路径
'mch_secret_cert' => $normalizePrivateKey((string) env('PAY_WECHAT_SECRET_CERT', '')),
// 商户 API 证书(apiclient_cert.pem):用于提取证书序列号
'mch_public_cert_path' => $resolveCertPath((string) env('PAY_WECHAT_PUBLIC_CERT_PATH', '')),
// 微信平台证书(回调验签用):留空时 SDK 会凭 APIv3 密钥自动下载并缓存在内存,
// 生产环境建议用 `php think paycenter:check --channel=wechat` 触发一次后把证书落到
// runtime/cert/wechat/ 并在此配置 [序列号 => 文件路径],避免每次请求都去拉取。
'wechat_public_cert_path' => [],
'return_url' => '',
'notify_url' => '',
],
];