微信小程序支付遇到问题,因为没有 WX_MCH_PRIVATE_KEY
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { resolve } from 'path';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { normalizePemEnv } from '../../integrations/wechat/wechat-pay.util';
|
||||
import { SYSTEM_CONFIG_KEY_SET } from './system-config.registry';
|
||||
|
||||
const PEM_ENV_KEYS = new Set(['WX_MCH_PRIVATE_KEY', 'WX_PLATFORM_CERT']);
|
||||
|
||||
function apiRoot() {
|
||||
return resolve(__dirname, '..', '..');
|
||||
}
|
||||
@@ -11,6 +14,11 @@ export function resolveEnvFilePath() {
|
||||
return resolve(apiRoot(), isProduction ? '.env.production' : '.env');
|
||||
}
|
||||
|
||||
function normalizeConfigValue(key: string, value: string): string {
|
||||
if (PEM_ENV_KEYS.has(key)) return normalizePemEnv(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** 启动前从 DB 覆盖 process.env(在 Nest 创建前调用) */
|
||||
export async function preloadSystemConfigEnv(): Promise<number> {
|
||||
const prisma = new PrismaClient();
|
||||
@@ -18,7 +26,7 @@ export async function preloadSystemConfigEnv(): Promise<number> {
|
||||
const rows = await prisma.systemConfig.findMany();
|
||||
for (const row of rows) {
|
||||
if (SYSTEM_CONFIG_KEY_SET.has(row.configKey)) {
|
||||
process.env[row.configKey] = row.value;
|
||||
process.env[row.configKey] = normalizeConfigValue(row.configKey, row.value);
|
||||
}
|
||||
}
|
||||
return rows.length;
|
||||
@@ -41,7 +49,7 @@ export async function preloadSystemConfigEnv(): Promise<number> {
|
||||
export function applyEnvOverlay(values: Record<string, string>) {
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
if (SYSTEM_CONFIG_KEY_SET.has(key)) {
|
||||
process.env[key] = value;
|
||||
process.env[key] = normalizeConfigValue(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,17 +205,31 @@ export class SystemConfigService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeByMeta(meta: { type: string }, raw: string): string {
|
||||
private normalizeByMeta(meta: { key?: string; type: string }, raw: string): string {
|
||||
if (meta.type === 'boolean') {
|
||||
return raw === 'true' || raw === '1' ? 'true' : 'false';
|
||||
}
|
||||
if (meta.key === 'WX_MCH_PRIVATE_KEY' || meta.key === 'WX_PLATFORM_CERT') {
|
||||
let value = raw.trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1).trim();
|
||||
}
|
||||
return value.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\r\n/g, '\n').trim();
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function formatEnvLine(key: string, value: string): string {
|
||||
if (/[\s#"'\\]/.test(value)) {
|
||||
return `${key}="${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
return `${key}=${value}`;
|
||||
const needsQuote = /[\s#"'\\]/.test(value) || value.includes('\n') || value.includes('\r');
|
||||
if (!needsQuote) return `${key}=${value}`;
|
||||
const escaped = value
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\r\n/g, '\\n')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/"/g, '\\"');
|
||||
return `${key}="${escaped}"`;
|
||||
}
|
||||
|
||||
@@ -67,3 +67,22 @@ export function safeEqual(a: string, b: string): boolean {
|
||||
if (ba.length !== bb.length) return false;
|
||||
return timingSafeEqual(ba, bb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化 .env / system_config 中的 PEM:
|
||||
* - 去掉外层引号(DB/表单常把整段含引号写入)
|
||||
* - 把字面量 \\n 转成真实换行
|
||||
* OpenSSL 报 1E08010C DECODER unsupported 时多半是这两类污染。
|
||||
*/
|
||||
export function normalizePemEnv(raw: string | undefined | null): string {
|
||||
if (!raw) return '';
|
||||
let value = String(raw).trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1).trim();
|
||||
}
|
||||
value = value.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\r\n/g, '\n');
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { IWechatProvider, WechatCodeSession, WechatOAuthSession } from './w
|
||||
import { logWechatAuth, type WechatActorRef } from './wechat-log.util';
|
||||
import {
|
||||
decryptPayResource,
|
||||
normalizePemEnv,
|
||||
verifyPaySignature,
|
||||
type WechatPayNotifyEnvelope,
|
||||
} from './wechat-pay.util';
|
||||
@@ -27,10 +28,10 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
private readonly miniAppSecret = (process.env.WX_MINI_APP_SECRET ?? this.appSecret).trim();
|
||||
private readonly mchId = process.env.WX_MCH_ID ?? '';
|
||||
private readonly mchSerialNo = process.env.WX_MCH_SERIAL_NO ?? '';
|
||||
private readonly mchPrivateKey = (process.env.WX_MCH_PRIVATE_KEY ?? '').replace(/\\n/g, '\n');
|
||||
private readonly mchPrivateKey = normalizePemEnv(process.env.WX_MCH_PRIVATE_KEY);
|
||||
private readonly apiV3Key = process.env.WX_API_V3_KEY ?? '';
|
||||
private readonly notifyUrl = process.env.WX_PAY_NOTIFY_URL ?? '';
|
||||
private readonly platformCert = (process.env.WX_PLATFORM_CERT ?? '').replace(/\\n/g, '\n');
|
||||
private readonly platformCert = normalizePemEnv(process.env.WX_PLATFORM_CERT);
|
||||
|
||||
constructor(
|
||||
private readonly redis: RedisService,
|
||||
|
||||
Reference in New Issue
Block a user