// +---------------------------------------------------------------------- // 应用私钥兼容处理: // .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 正确的一侧。 $alipaySecretCert = (string) env('PAY_ALIPAY_APP_SECRET_CERT', ''); if ($alipaySecretCert !== '') { if (is_file($alipaySecretCert)) { $alipaySecretCert = (string) file_get_contents($alipaySecretCert); } $privateKeyBody = (string) preg_replace('/-----[^-]+-----|\s+/', '', $alipaySecretCert); $der = base64_decode($privateKeyBody, 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"); $wrapped = wordwrap($privateKeyBody, 64, "\n", true); $alipaySecretCert = $isPkcs8 ? "-----BEGIN PRIVATE KEY-----\n{$wrapped}\n-----END PRIVATE KEY-----" : $privateKeyBody; } // 证书路径统一按项目根目录解析成绝对路径: // SDK 的 get_public_cert() 是 `is_file($key) ? file_get_contents($key) : $key` // (见 vendor/yansongda/pay/src/Functions.php),相对路径按【进程 CWD】解析: // CLI 在项目根目录 → 能读到;Web 入口是 public/index.php、CWD 变成 public/ → 读不到, // 下单会抛「配置异常: 解析 `app_public_cert_path` 失败」,回调验签同样失败。 // 所以在配置层补全一次,业务代码(MarketService / Market / PayCheck)都无需各自处理。 $resolveCertPath = static function (string $path): string { 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__) . DIRECTORY_SEPARATOR; return $root . ltrim($path, '/\\'); }; return [ // 模拟支付开关:仅在 APP_DEBUG=true 且无商户号时生效,便于本地/自托管环境跑通购买闭环。 // 默认 false(防御性默认,避免默认开启后被人"免费"购得付费插件授权)。 // 三重防御确保生产安全: // 1) 本开关默认关闭(防止默认值泄露); // 2) 安装向导 public/install/Installer.php::writeEnv 会强制把 PAY_MOCK=false 写入 .env; // 3) 即使 .env 被手误改错,MarketService / Market 在生产(APP_DEBUG=false)也会拒绝执行 mock。 // 本地调试:.env 中显式设置 PAY_MOCK=true(与 APP_DEBUG=true 配对使用)。 'mock_enable' => env('PAY_MOCK', false), // 支付宝(Yansongda Pay v3.7 —— 仅支持「证书模式」,普通公钥模式不再被识别)。 // 证书在开放平台 → 应用 → 接口加签方式(沙箱同理)下载: // app_public_cert_path → appCertPublicKey_*.crt(应用公钥证书) // alipay_public_cert_path → alipayCertPublicKey_*.crt(支付宝公钥证书,验签用) // alipay_root_cert_path → alipayRootCert.crt(支付宝根证书) // 缺任一项都会抛「配置异常: 缺少支付宝配置 -- [xxx]」,见 // vendor/yansongda/pay/src/Plugin/Alipay/V2/StartPlugin.php // vendor/yansongda/pay/src/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php 'alipay' => [ // mode 必须是【整数】:0=正式 1=沙箱 2=服务商(Yansongda\Pay\Pay::MODE_*)。 // 写成 'normal'/'dev' 字符串会在拼网关 URL 时数组越界。 'mode' => (int) env('PAY_ALIPAY_MODE', 0), 'app_id' => env('PAY_ALIPAY_APP_ID', ''), // 应用私钥:PEM 全文、单行 base64(已在上方自动补全 PEM 头并识别 PKCS8/PKCS1),或私钥文件路径 'app_secret_cert' => $alipaySecretCert, '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 API)。注:微信早已下线官方沙箱,mode 留 0 即可; // 没有商户号时只能走 mock 或小额真实支付再退款。 // 说明:真实的下单 / 验签 / 回调已迁到支付中心插件(addon/paycenter), // 这里保留仅为「既有部署的密钥来源」——插件 PayConfig 会用下面的值覆盖自己的同名项。 'wechat' => [ 'mode' => (int) env('PAY_WECHAT_MODE', 0), 'mch_id' => env('PAY_WECHAT_MCH_ID', ''), // SDK 按场景取键:mp → mp_app_id、mini → mini_app_id、app → 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 密钥 'mch_secret_key' => env('PAY_WECHAT_SECRET_KEY', ''), // 商户 API 私钥:文件路径或 PEM 内容(插件会再做一次 PKCS8/PKCS1 判定) 'mch_secret_cert' => env('PAY_WECHAT_SECRET_CERT', ''), // 商户 API 证书:用于提取证书序列号 'mch_public_cert_path' => env('PAY_WECHAT_PUBLIC_CERT_PATH', ''), // 微信平台证书(回调验签用):留空时 SDK 凭 APIv3 密钥自动下载; // 生产环境建议落到本地并配置 [证书序列号 => 文件路径],避免每次请求都去拉取 'wechat_public_cert_path' => [], 'return_url' => '', 'notify_url' => '', ], ];