'init'
This commit is contained in:
101
lib/sdk/README.md
Normal file
101
lib/sdk/README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# SDK 使用说明
|
||||
|
||||
ZPay 支付网关、PocketBase 数据库、MinIO 对象存储,配置成 SDK 随时调用。复制自 digital 项目。
|
||||
|
||||
## 环境变量
|
||||
|
||||
在 `.env.local` 中配置:
|
||||
|
||||
```bash
|
||||
# PocketBase
|
||||
NEXT_PUBLIC_POCKETBASE_URL=https://pocketbase.hackrobot.cn
|
||||
POCKETBASE_JOIN_COLLECTION=solan
|
||||
POCKETBASE_EMAIL=admin@example.com # Admin 登录(可选,用于 createRecord 403 时重试)
|
||||
POCKETBASE_PASSWORD=xxx
|
||||
|
||||
# ZPay 支付
|
||||
PAYMENT_API_URL=http://127.0.0.1:8700 # 开发;生产用 https://api.hackrobot.cn
|
||||
PAYMENT_PROVIDER=zpay
|
||||
PAYMENT_DEFAULT_AMOUNT=80
|
||||
PAYMENT_DEFAULT_TYPE=meetup
|
||||
ZPAY_SUBMIT_URL=https://zpayz.cn/submit.php
|
||||
|
||||
# MinIO
|
||||
MINIO_ENDPOINT=minioweb.hackrobot.cn
|
||||
MINIO_PORT=9035
|
||||
MINIO_USE_SSL=false
|
||||
MINIO_BUCKET=hackrobot
|
||||
MINIO_ACCESS_KEY=xxx
|
||||
MINIO_SECRET_KEY=xxx
|
||||
MINIO_UPLOAD_PREFIX=joins
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### PocketBase
|
||||
|
||||
```ts
|
||||
import {
|
||||
pbLogin,
|
||||
pbRegister,
|
||||
pbSaveAuth,
|
||||
pbLogout,
|
||||
getStoredUserEmail,
|
||||
createRecord,
|
||||
} from "@/lib/sdk";
|
||||
|
||||
// 客户端:登录
|
||||
const { token, record } = await pbLogin("user@example.com", "password");
|
||||
pbSaveAuth(token, record);
|
||||
|
||||
// 客户端:注册
|
||||
const result = await pbRegister("user@example.com", "pass", "pass");
|
||||
pbSaveAuth(result.token, result.record);
|
||||
|
||||
// 客户端:登出
|
||||
pbLogout();
|
||||
|
||||
// 客户端:获取当前用户
|
||||
const email = getStoredUserEmail();
|
||||
|
||||
// 服务端:创建记录
|
||||
await createRecord("solan", { nickname: "张三", reason: "想加入" });
|
||||
```
|
||||
|
||||
### ZPay
|
||||
|
||||
```ts
|
||||
import { createPayOrder, getOrderStatus, buildPayHtml } from "@/lib/sdk";
|
||||
|
||||
// 创建订单
|
||||
const { params, payUrl } = await createPayOrder({
|
||||
user_id: "user123",
|
||||
return_url: "https://yoursite.com/paid",
|
||||
total_fee: 80, // 分
|
||||
type: "meetup",
|
||||
channel: "alipay",
|
||||
});
|
||||
|
||||
// 查询支付状态
|
||||
const { paid } = await getOrderStatus("out_trade_no_xxx");
|
||||
|
||||
// 构建跳转 HTML
|
||||
const html = buildPayHtml(payUrl, params);
|
||||
```
|
||||
|
||||
### MinIO
|
||||
|
||||
```ts
|
||||
import { uploadBuffer, generateObjectKey } from "@/lib/sdk";
|
||||
|
||||
// 生成对象键
|
||||
const objectKey = generateObjectKey("jpg");
|
||||
|
||||
// 上传
|
||||
const { url } = await uploadBuffer(
|
||||
Buffer.from(fileArrayBuffer),
|
||||
objectKey,
|
||||
"image/jpeg"
|
||||
);
|
||||
// url 即公开访问地址
|
||||
```
|
||||
85
lib/sdk/config.ts
Normal file
85
lib/sdk/config.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* SDK 统一配置 - 从环境变量读取,可随时调用
|
||||
* 复制自 digital 项目,去除主题依赖
|
||||
*/
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
export interface PocketBaseConfig {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
usersCollection: string;
|
||||
joinCollection: string;
|
||||
}
|
||||
|
||||
export interface ZPayConfig {
|
||||
enabled: boolean;
|
||||
apiUrl: string;
|
||||
provider: "zpay" | "xorpay";
|
||||
defaultAmount: number;
|
||||
defaultType: string;
|
||||
zpaySubmitUrl: string;
|
||||
}
|
||||
|
||||
export interface MinioConfig {
|
||||
enabled: boolean;
|
||||
endPoint: string;
|
||||
port: number;
|
||||
useSSL: boolean;
|
||||
bucket: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
region: string;
|
||||
publicUrl: string;
|
||||
uploadPrefix: string;
|
||||
}
|
||||
|
||||
/** PocketBase 配置 */
|
||||
export function getPocketBaseConfig(overrides?: { joinCollection?: string }): PocketBaseConfig {
|
||||
return {
|
||||
enabled: true,
|
||||
url: process.env.NEXT_PUBLIC_POCKETBASE_URL || process.env.POCKETBASE_URL || "https://pocketbase.hackrobot.cn",
|
||||
usersCollection: "users",
|
||||
joinCollection: overrides?.joinCollection || process.env.POCKETBASE_JOIN_COLLECTION || "solan",
|
||||
};
|
||||
}
|
||||
|
||||
/** ZPay 支付配置 */
|
||||
export function getZPayConfig(overrides?: { amount?: number; type?: string }): ZPayConfig {
|
||||
const base = {
|
||||
enabled: true,
|
||||
apiUrl: process.env.PAYMENT_API_URL || (isDev ? "http://127.0.0.1:8700" : "https://api.hackrobot.cn"),
|
||||
provider: (process.env.PAYMENT_PROVIDER as "zpay" | "xorpay") || "zpay",
|
||||
defaultAmount: Number(process.env.PAYMENT_DEFAULT_AMOUNT) || 80,
|
||||
defaultType: process.env.PAYMENT_DEFAULT_TYPE || "meetup",
|
||||
zpaySubmitUrl: process.env.ZPAY_SUBMIT_URL || "https://zpayz.cn/submit.php",
|
||||
};
|
||||
if (overrides) {
|
||||
if (overrides.amount != null) base.defaultAmount = overrides.amount;
|
||||
if (overrides.type) base.defaultType = overrides.type;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/** MinIO 配置 */
|
||||
export function getMinioConfig(overrides?: { bucket?: string; uploadPrefix?: string }): MinioConfig {
|
||||
const endPoint = process.env.MINIO_ENDPOINT || "minioweb.hackrobot.cn";
|
||||
const port = parseInt(process.env.MINIO_PORT || "9035", 10);
|
||||
const useSSL = process.env.MINIO_USE_SSL === "true";
|
||||
const bucket = overrides?.bucket || process.env.MINIO_BUCKET || "hackrobot";
|
||||
const publicUrl = process.env.MINIO_PUBLIC_URL || `https://${endPoint}/${bucket}`;
|
||||
const uploadPrefix = overrides?.uploadPrefix || process.env.MINIO_UPLOAD_PREFIX || "joins";
|
||||
|
||||
return {
|
||||
enabled: true,
|
||||
endPoint,
|
||||
port,
|
||||
useSSL,
|
||||
bucket,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || "6i56HHfg4zPfYItCZtnp",
|
||||
secretKey: process.env.MINIO_SECRET_KEY || "vDJCqEit3ejH5UmWKAZnvqhziNfbVsoOlBW12G8Q",
|
||||
region: process.env.MINIO_REGION || "china",
|
||||
publicUrl: publicUrl.replace(/\/$/, ""),
|
||||
uploadPrefix,
|
||||
};
|
||||
}
|
||||
39
lib/sdk/index.ts
Normal file
39
lib/sdk/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* SDK 统一入口 - ZPay / PocketBase / MinIO 随时调用
|
||||
* 配置通过环境变量,复制自 digital 项目
|
||||
*/
|
||||
|
||||
// 配置
|
||||
export {
|
||||
getPocketBaseConfig,
|
||||
getZPayConfig,
|
||||
getMinioConfig,
|
||||
} from "./config";
|
||||
export type { PocketBaseConfig, ZPayConfig, MinioConfig } from "./config";
|
||||
|
||||
// PocketBase
|
||||
export {
|
||||
pbLogin,
|
||||
pbRegister,
|
||||
pbSaveAuth,
|
||||
pbLogout,
|
||||
getStoredUserEmail,
|
||||
getStoredToken,
|
||||
createRecord,
|
||||
getAdminToken,
|
||||
PB_STORAGE_KEYS,
|
||||
} from "./pocketbase";
|
||||
export type { AuthUser, AuthResult } from "./pocketbase";
|
||||
|
||||
// ZPay
|
||||
export {
|
||||
createPayOrder,
|
||||
getOrderStatus,
|
||||
buildPayHtml,
|
||||
ZPAY_REQUIRED,
|
||||
} from "./zpay";
|
||||
export type { CreateOrderOptions, CreateOrderResult } from "./zpay";
|
||||
|
||||
// MinIO
|
||||
export { getMinioClient, uploadBuffer, generateObjectKey } from "./minio";
|
||||
export type { UploadResult } from "./minio";
|
||||
60
lib/sdk/minio/index.ts
Normal file
60
lib/sdk/minio/index.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* MinIO SDK - 上传、下载,随时可调用
|
||||
* 复制自 digital 项目
|
||||
*/
|
||||
|
||||
import * as Minio from "minio";
|
||||
import { getMinioConfig } from "../config";
|
||||
|
||||
export interface UploadResult {
|
||||
url: string;
|
||||
objectKey: string;
|
||||
}
|
||||
|
||||
/** 获取 MinIO 客户端实例 */
|
||||
export function getMinioClient(overrides?: { bucket?: string; uploadPrefix?: string }): Minio.Client {
|
||||
const cfg = getMinioConfig(overrides);
|
||||
return new Minio.Client({
|
||||
endPoint: cfg.endPoint,
|
||||
port: cfg.port,
|
||||
useSSL: cfg.useSSL,
|
||||
accessKey: cfg.accessKey,
|
||||
secretKey: cfg.secretKey,
|
||||
pathStyle: true,
|
||||
region: cfg.region,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到 MinIO
|
||||
* @param buffer 文件内容
|
||||
* @param objectKey 对象键(含路径)
|
||||
* @param contentType MIME 类型
|
||||
*/
|
||||
export async function uploadBuffer(
|
||||
buffer: Buffer,
|
||||
objectKey: string,
|
||||
contentType: string = "application/octet-stream",
|
||||
overrides?: { bucket?: string; uploadPrefix?: string }
|
||||
): Promise<UploadResult> {
|
||||
const cfg = getMinioConfig(overrides);
|
||||
if (!cfg.enabled) throw new Error("MinIO 存储未启用");
|
||||
|
||||
const client = getMinioClient(overrides);
|
||||
await client.putObject(cfg.bucket, objectKey, buffer, buffer.length, {
|
||||
"Content-Type": contentType,
|
||||
});
|
||||
|
||||
const url = `${cfg.publicUrl}/${objectKey}`;
|
||||
return { url, objectKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成上传用的对象键
|
||||
* @param ext 文件扩展名
|
||||
*/
|
||||
export function generateObjectKey(ext: string, overrides?: { uploadPrefix?: string }): string {
|
||||
const cfg = getMinioConfig(overrides);
|
||||
const prefix = cfg.uploadPrefix.replace(/\/$/, "");
|
||||
return `${prefix}/${Date.now()}_${Math.random().toString(36).slice(2)}.${ext}`;
|
||||
}
|
||||
91
lib/sdk/pocketbase/auth.ts
Normal file
91
lib/sdk/pocketbase/auth.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* PocketBase 认证 - 登录、注册、登出(客户端可用)
|
||||
*/
|
||||
|
||||
import { getPocketBaseConfig } from "../config";
|
||||
import { PB_STORAGE_KEYS } from "./constants";
|
||||
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
email: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface AuthResult {
|
||||
token: string;
|
||||
record: AuthUser;
|
||||
}
|
||||
|
||||
function getPBUrl(): string {
|
||||
return getPocketBaseConfig().url;
|
||||
}
|
||||
|
||||
/** 登录 */
|
||||
export async function pbLogin(identity: string, password: string): Promise<AuthResult> {
|
||||
const url = getPBUrl();
|
||||
const res = await fetch(`${url}/api/collections/users/auth-with-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ identity, password }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
throw new Error(err?.message || "登录失败");
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!data?.token) throw new Error("登录响应异常");
|
||||
return { token: data.token, record: data.record };
|
||||
}
|
||||
|
||||
/** 注册 */
|
||||
export async function pbRegister(
|
||||
email: string,
|
||||
password: string,
|
||||
passwordConfirm: string
|
||||
): Promise<AuthResult> {
|
||||
const url = getPBUrl();
|
||||
const config = getPocketBaseConfig();
|
||||
const res = await fetch(`${url}/api/collections/${config.usersCollection}/records`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password, passwordConfirm }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
throw new Error(err?.message || "注册失败");
|
||||
}
|
||||
return pbLogin(email, password);
|
||||
}
|
||||
|
||||
/** 保存认证到 localStorage */
|
||||
export function pbSaveAuth(token: string, record: AuthUser): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(PB_STORAGE_KEYS.token, token);
|
||||
localStorage.setItem(PB_STORAGE_KEYS.user, JSON.stringify(record));
|
||||
}
|
||||
|
||||
/** 登出 */
|
||||
export function pbLogout(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(PB_STORAGE_KEYS.token);
|
||||
localStorage.removeItem(PB_STORAGE_KEYS.user);
|
||||
}
|
||||
|
||||
/** 获取存储的用户邮箱 */
|
||||
export function getStoredUserEmail(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(PB_STORAGE_KEYS.user);
|
||||
if (!raw) return null;
|
||||
const user = JSON.parse(raw);
|
||||
return user?.email ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取存储的 token */
|
||||
export function getStoredToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(PB_STORAGE_KEYS.token);
|
||||
}
|
||||
5
lib/sdk/pocketbase/constants.ts
Normal file
5
lib/sdk/pocketbase/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/** localStorage 存储 key */
|
||||
export const PB_STORAGE_KEYS = {
|
||||
token: "pb_token",
|
||||
user: "pb_user",
|
||||
} as const;
|
||||
12
lib/sdk/pocketbase/index.ts
Normal file
12
lib/sdk/pocketbase/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* PocketBase SDK - 认证与数据操作,随时可调用
|
||||
* 复制自 digital 项目
|
||||
*/
|
||||
|
||||
import { getPocketBaseConfig } from "../config";
|
||||
import { PB_STORAGE_KEYS } from "./constants";
|
||||
|
||||
export { PB_STORAGE_KEYS } from "./constants";
|
||||
export type { AuthUser, AuthResult } from "./auth";
|
||||
export { pbLogin, pbRegister, pbSaveAuth, pbLogout, getStoredUserEmail, getStoredToken } from "./auth";
|
||||
export { createRecord, getAdminToken } from "./records";
|
||||
52
lib/sdk/pocketbase/records.ts
Normal file
52
lib/sdk/pocketbase/records.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* PocketBase 记录操作 - 创建、查询(服务端可用)
|
||||
*/
|
||||
|
||||
import { getPocketBaseConfig } from "../config";
|
||||
|
||||
/** 获取 Admin Token(需配置 POCKETBASE_EMAIL + POCKETBASE_PASSWORD) */
|
||||
export async function getAdminToken(): Promise<string | null> {
|
||||
const email = process.env.POCKETBASE_EMAIL;
|
||||
const password = process.env.POCKETBASE_PASSWORD;
|
||||
if (!email || !password) return null;
|
||||
|
||||
const pb = getPocketBaseConfig();
|
||||
const res = await fetch(`${pb.url}/api/admins/auth-with-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ identity: email, password }),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data?.token ?? null;
|
||||
}
|
||||
|
||||
/** 创建记录 */
|
||||
export async function createRecord(
|
||||
collection: string,
|
||||
data: Record<string, unknown>,
|
||||
authToken?: string
|
||||
): Promise<{ id: string; [key: string]: unknown }> {
|
||||
const pb = getPocketBaseConfig();
|
||||
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||
if (authToken) headers["Authorization"] = authToken;
|
||||
|
||||
const res = await fetch(`${pb.url}/api/collections/${collection}/records`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
let errMsg = "创建失败";
|
||||
try {
|
||||
const errJson = JSON.parse(errText);
|
||||
if (errJson?.message) errMsg = errJson.message;
|
||||
} catch {
|
||||
if (errText.length < 200) errMsg = errText;
|
||||
}
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
130
lib/sdk/zpay/index.ts
Normal file
130
lib/sdk/zpay/index.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* ZPay 支付网关 SDK - 创建订单、查询状态,随时可调用
|
||||
* 复制自 digital 项目 pay API
|
||||
*/
|
||||
|
||||
import { getZPayConfig } from "../config";
|
||||
|
||||
const ZPAY_REQUIRED = [
|
||||
"pid",
|
||||
"type",
|
||||
"out_trade_no",
|
||||
"notify_url",
|
||||
"return_url",
|
||||
"name",
|
||||
"money",
|
||||
"sign",
|
||||
"sign_type",
|
||||
];
|
||||
|
||||
function escapeAttr(s: string): string {
|
||||
return String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
export interface CreateOrderOptions {
|
||||
user_id: string;
|
||||
return_url: string;
|
||||
total_fee?: number;
|
||||
type?: string;
|
||||
channel?: string;
|
||||
}
|
||||
|
||||
export interface CreateOrderResult {
|
||||
params: Record<string, string>;
|
||||
payUrl: string;
|
||||
}
|
||||
|
||||
/** 创建支付订单 */
|
||||
export async function createPayOrder(options: CreateOrderOptions): Promise<CreateOrderResult> {
|
||||
const config = getZPayConfig();
|
||||
if (!config.apiUrl) throw new Error("未配置 PAYMENT_API_URL");
|
||||
|
||||
const { user_id, return_url, total_fee, type, channel = "alipay" } = options;
|
||||
const fee = total_fee ?? config.defaultAmount;
|
||||
const orderType = type ?? config.defaultType;
|
||||
|
||||
const payload = {
|
||||
user_id,
|
||||
total_fee: Number(fee) || config.defaultAmount,
|
||||
type: orderType,
|
||||
channel,
|
||||
return_url,
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
const res = await fetch(`${config.apiUrl}/payh5`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal,
|
||||
}).finally(() => clearTimeout(timeout));
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || data?.status !== "ok") {
|
||||
throw new Error(data?.detail || data?.msg || data?.message || "支付创建失败");
|
||||
}
|
||||
|
||||
let params = data.params || data.xorpay_params || {};
|
||||
const payUrl = data.pay_url || data.xorpay_url || config.zpaySubmitUrl;
|
||||
if (!params || typeof params !== "object") {
|
||||
throw new Error("payjsapi 未返回支付参数");
|
||||
}
|
||||
params = JSON.parse(JSON.stringify(params));
|
||||
if (data.provider === "xorpay") {
|
||||
throw new Error("当前 payjsapi 使用 xorpay,请设置 PAYMENT_PROVIDER=zpay");
|
||||
}
|
||||
return { params, payUrl };
|
||||
}
|
||||
|
||||
/** 查询订单支付状态 */
|
||||
export async function getOrderStatus(orderId: string): Promise<{ paid: boolean }> {
|
||||
const config = getZPayConfig();
|
||||
if (!config.apiUrl) throw new Error("未配置 PAYMENT_API_URL");
|
||||
|
||||
const res = await fetch(
|
||||
`${config.apiUrl}/zpay_order_status?out_trade_no=${encodeURIComponent(orderId)}`,
|
||||
{ cache: "no-store" }
|
||||
);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return { paid: !!data?.paid };
|
||||
}
|
||||
|
||||
/** 构建 ZPay 表单跳转 HTML */
|
||||
export function buildPayHtml(payUrl: string, params: Record<string, unknown>): string {
|
||||
const flat: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v == null) continue;
|
||||
const s = String(v).trim();
|
||||
if (s === "") continue;
|
||||
flat[k] = s;
|
||||
}
|
||||
const inputs = Object.entries(flat)
|
||||
.map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}" />`)
|
||||
.join("\n");
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>跳转支付...</title>
|
||||
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f0f4f8;}p{color:#64748b;}</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>正在跳转到支付页面...</p>
|
||||
<form id="zpayForm" action="${escapeAttr(payUrl)}" method="POST" enctype="application/x-www-form-urlencoded">
|
||||
${inputs}
|
||||
</form>
|
||||
<script>document.getElementById("zpayForm").submit();</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/** 获取 ZPay 必填参数列表(用于校验) */
|
||||
export { ZPAY_REQUIRED };
|
||||
Reference in New Issue
Block a user