@youidian/pay-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +80 -0
- package/dist/client.d.ts +80 -0
- package/dist/client.js +209 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +185 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +351 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +314 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +160 -0
- package/dist/server.d.ts +160 -0
- package/dist/server.js +168 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +135 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +54 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
31
|
+
|
|
32
|
+
// src/server.ts
|
|
33
|
+
var server_exports = {};
|
|
34
|
+
__export(server_exports, {
|
|
35
|
+
PaymentClient: () => PaymentClient
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(server_exports);
|
|
38
|
+
var import_crypto = __toESM(require("crypto"));
|
|
39
|
+
var PaymentClient = class {
|
|
40
|
+
constructor(options) {
|
|
41
|
+
__publicField(this, "appId");
|
|
42
|
+
__publicField(this, "appSecret");
|
|
43
|
+
__publicField(this, "baseUrl");
|
|
44
|
+
if (!options.appId) throw new Error("appId is required");
|
|
45
|
+
if (!options.appSecret) throw new Error("appSecret is required");
|
|
46
|
+
if (!options.baseUrl) throw new Error("baseUrl is required");
|
|
47
|
+
this.appId = options.appId;
|
|
48
|
+
this.appSecret = options.appSecret;
|
|
49
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate SHA256 signature for the request
|
|
53
|
+
* Logic: SHA256(appId + appSecret + timestamp)
|
|
54
|
+
*/
|
|
55
|
+
generateSignature(timestamp) {
|
|
56
|
+
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
57
|
+
return import_crypto.default.createHash("sha256").update(str).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Internal request helper for Gateway API
|
|
61
|
+
*/
|
|
62
|
+
async request(method, path, body) {
|
|
63
|
+
const timestamp = Date.now();
|
|
64
|
+
const signature = this.generateSignature(timestamp);
|
|
65
|
+
const url = `${this.baseUrl}/api/v1/gateway/${this.appId}${path}`;
|
|
66
|
+
const headers = {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
"X-Pay-Timestamp": timestamp.toString(),
|
|
69
|
+
"X-Pay-Sign": signature
|
|
70
|
+
};
|
|
71
|
+
const options = {
|
|
72
|
+
method,
|
|
73
|
+
headers,
|
|
74
|
+
body: body ? JSON.stringify(body) : void 0
|
|
75
|
+
};
|
|
76
|
+
const response = await fetch(url, options);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const errorText = await response.text();
|
|
79
|
+
throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);
|
|
80
|
+
}
|
|
81
|
+
const json = await response.json();
|
|
82
|
+
if (json.error) {
|
|
83
|
+
throw new Error(`Payment API Error: ${json.error}`);
|
|
84
|
+
}
|
|
85
|
+
return json.data;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Decrypts the callback notification payload using AES-256-GCM.
|
|
89
|
+
* @param notification - The encrypted notification from payment webhook
|
|
90
|
+
* @returns Decrypted payment callback data
|
|
91
|
+
*/
|
|
92
|
+
decryptCallback(notification) {
|
|
93
|
+
try {
|
|
94
|
+
const { iv, encryptedData, authTag } = notification;
|
|
95
|
+
const key = import_crypto.default.createHash("sha256").update(this.appSecret).digest();
|
|
96
|
+
const decipher = import_crypto.default.createDecipheriv(
|
|
97
|
+
"aes-256-gcm",
|
|
98
|
+
key,
|
|
99
|
+
Buffer.from(iv, "hex")
|
|
100
|
+
);
|
|
101
|
+
decipher.setAuthTag(Buffer.from(authTag, "hex"));
|
|
102
|
+
let decrypted = decipher.update(encryptedData, "hex", "utf8");
|
|
103
|
+
decrypted += decipher.final("utf8");
|
|
104
|
+
return JSON.parse(decrypted);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Fetch products for the configured app.
|
|
111
|
+
*/
|
|
112
|
+
async getProducts(options) {
|
|
113
|
+
const params = new URLSearchParams();
|
|
114
|
+
params.append("appId", this.appId);
|
|
115
|
+
if (options?.locale) params.append("locale", options.locale);
|
|
116
|
+
if (options?.currency) params.append("currency", options.currency);
|
|
117
|
+
const url = `${this.baseUrl}/api/v1/pay/products?${params.toString()}`;
|
|
118
|
+
const res = await fetch(url, {
|
|
119
|
+
method: "GET",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"Accept": "application/json"
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);
|
|
127
|
+
}
|
|
128
|
+
const json = await res.json();
|
|
129
|
+
return json.data?.products || [];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Create a new order
|
|
133
|
+
* @param params - Order creation parameters
|
|
134
|
+
* @returns Order details with payment parameters
|
|
135
|
+
*/
|
|
136
|
+
async createOrder(params) {
|
|
137
|
+
return this.request("POST", "/orders", params);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Pay for an existing order
|
|
141
|
+
* @param orderId - The order ID to pay
|
|
142
|
+
* @param params - Payment parameters including channel
|
|
143
|
+
*/
|
|
144
|
+
async payOrder(orderId, params) {
|
|
145
|
+
return this.request("POST", `/orders/${orderId}/pay`, params);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Query order status
|
|
149
|
+
* @param orderId - The order ID to query
|
|
150
|
+
*/
|
|
151
|
+
async getOrderStatus(orderId) {
|
|
152
|
+
return this.request("GET", `/orders/${orderId}`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Generate checkout URL for client-side payment
|
|
156
|
+
* @param productId - Product ID
|
|
157
|
+
* @param priceId - Price ID
|
|
158
|
+
* @returns Checkout page URL
|
|
159
|
+
*/
|
|
160
|
+
getCheckoutUrl(productId, priceId) {
|
|
161
|
+
return `${this.baseUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
165
|
+
0 && (module.exports = {
|
|
166
|
+
PaymentClient
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: any;\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n /** API Base URL (e.g. https://pay.youidian.com) */\n baseUrl: string;\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n productId?: string;\n priceId?: string;\n channel?: string;\n userId: string;\n returnUrl?: string;\n metadata?: Record<string, any>;\n merchantOrderId?: string;\n openid?: string;\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n orderId: string;\n internalId: string;\n amount: number;\n currency: string;\n payParams: any;\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n iv: string;\n encryptedData: string;\n authTag: string;\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n orderId: string;\n merchantOrderId?: string;\n status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n paidAt: string;\n channelTransactionId?: string;\n metadata?: Record<string, any>;\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n private appId: string;\n private appSecret: string;\n private baseUrl: string;\n\n constructor(options: PaymentClientOptions) {\n if (!options.appId) throw new Error(\"appId is required\");\n if (!options.appSecret) throw new Error(\"appSecret is required\");\n if (!options.baseUrl) throw new Error(\"baseUrl is required\");\n\n this.appId = options.appId;\n this.appSecret = options.appSecret;\n this.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Generate SHA256 signature for the request\n * Logic: SHA256(appId + appSecret + timestamp)\n */\n private generateSignature(timestamp: number): string {\n const str = `${this.appId}${this.appSecret}${timestamp}`;\n return crypto.createHash(\"sha256\").update(str).digest(\"hex\");\n }\n\n /**\n * Internal request helper for Gateway API\n */\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const timestamp = Date.now();\n const signature = this.generateSignature(timestamp);\n\n const url = `${this.baseUrl}/api/v1/gateway/${this.appId}${path}`;\n\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n \"X-Pay-Timestamp\": timestamp.toString(),\n \"X-Pay-Sign\": signature,\n };\n\n const options: RequestInit = {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n };\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);\n }\n\n const json = await response.json();\n if (json.error) {\n throw new Error(`Payment API Error: ${json.error}`);\n }\n\n return json.data as T;\n }\n\n /**\n * Decrypts the callback notification payload using AES-256-GCM.\n * @param notification - The encrypted notification from payment webhook\n * @returns Decrypted payment callback data\n */\n decryptCallback(notification: PaymentNotification): PaymentCallbackData {\n try {\n const { iv, encryptedData, authTag } = notification;\n const key = crypto.createHash('sha256').update(this.appSecret).digest();\n const decipher = crypto.createDecipheriv(\n 'aes-256-gcm',\n key,\n Buffer.from(iv, 'hex')\n );\n\n decipher.setAuthTag(Buffer.from(authTag, 'hex'));\n\n let decrypted = decipher.update(encryptedData, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return JSON.parse(decrypted);\n } catch (error) {\n throw new Error(\"Failed to decrypt payment callback: Invalid secret or tampered data.\");\n }\n }\n\n /**\n * Fetch products for the configured app.\n */\n async getProducts(options?: {\n locale?: string;\n currency?: string;\n }): Promise<Product[]> {\n const params = new URLSearchParams();\n params.append('appId', this.appId);\n if (options?.locale) params.append('locale', options.locale);\n if (options?.currency) params.append('currency', options.currency);\n\n const url = `${this.baseUrl}/api/v1/pay/products?${params.toString()}`;\n\n const res = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n }\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);\n }\n\n const json = await res.json();\n return json.data?.products || [];\n }\n\n /**\n * Create a new order\n * @param params - Order creation parameters\n * @returns Order details with payment parameters\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n return this.request(\"POST\", \"/orders\", params);\n }\n\n /**\n * Pay for an existing order\n * @param orderId - The order ID to pay\n * @param params - Payment parameters including channel\n */\n async payOrder(orderId: string, params: {\n channel: string;\n returnUrl?: string;\n openid?: string;\n [key: string]: any;\n }): Promise<CreateOrderResponse> {\n return this.request(\"POST\", `/orders/${orderId}/pay`, params);\n }\n\n /**\n * Query order status\n * @param orderId - The order ID to query\n */\n async getOrderStatus(orderId: string): Promise<OrderStatus> {\n return this.request(\"GET\", `/orders/${orderId}`);\n }\n\n /**\n * Generate checkout URL for client-side payment\n * @param productId - Product ID\n * @param priceId - Price ID\n * @returns Checkout page URL\n */\n getCheckoutUrl(productId: string, priceId: string): string {\n return `${this.baseUrl}/checkout/${this.appId}/${productId}/${priceId}`;\n }\n}\n"],"mappings":"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","names":["crypto"]}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/server.ts
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
var PaymentClient = class {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
__publicField(this, "appId");
|
|
10
|
+
__publicField(this, "appSecret");
|
|
11
|
+
__publicField(this, "baseUrl");
|
|
12
|
+
if (!options.appId) throw new Error("appId is required");
|
|
13
|
+
if (!options.appSecret) throw new Error("appSecret is required");
|
|
14
|
+
if (!options.baseUrl) throw new Error("baseUrl is required");
|
|
15
|
+
this.appId = options.appId;
|
|
16
|
+
this.appSecret = options.appSecret;
|
|
17
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate SHA256 signature for the request
|
|
21
|
+
* Logic: SHA256(appId + appSecret + timestamp)
|
|
22
|
+
*/
|
|
23
|
+
generateSignature(timestamp) {
|
|
24
|
+
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
25
|
+
return crypto.createHash("sha256").update(str).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Internal request helper for Gateway API
|
|
29
|
+
*/
|
|
30
|
+
async request(method, path, body) {
|
|
31
|
+
const timestamp = Date.now();
|
|
32
|
+
const signature = this.generateSignature(timestamp);
|
|
33
|
+
const url = `${this.baseUrl}/api/v1/gateway/${this.appId}${path}`;
|
|
34
|
+
const headers = {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"X-Pay-Timestamp": timestamp.toString(),
|
|
37
|
+
"X-Pay-Sign": signature
|
|
38
|
+
};
|
|
39
|
+
const options = {
|
|
40
|
+
method,
|
|
41
|
+
headers,
|
|
42
|
+
body: body ? JSON.stringify(body) : void 0
|
|
43
|
+
};
|
|
44
|
+
const response = await fetch(url, options);
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const errorText = await response.text();
|
|
47
|
+
throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);
|
|
48
|
+
}
|
|
49
|
+
const json = await response.json();
|
|
50
|
+
if (json.error) {
|
|
51
|
+
throw new Error(`Payment API Error: ${json.error}`);
|
|
52
|
+
}
|
|
53
|
+
return json.data;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decrypts the callback notification payload using AES-256-GCM.
|
|
57
|
+
* @param notification - The encrypted notification from payment webhook
|
|
58
|
+
* @returns Decrypted payment callback data
|
|
59
|
+
*/
|
|
60
|
+
decryptCallback(notification) {
|
|
61
|
+
try {
|
|
62
|
+
const { iv, encryptedData, authTag } = notification;
|
|
63
|
+
const key = crypto.createHash("sha256").update(this.appSecret).digest();
|
|
64
|
+
const decipher = crypto.createDecipheriv(
|
|
65
|
+
"aes-256-gcm",
|
|
66
|
+
key,
|
|
67
|
+
Buffer.from(iv, "hex")
|
|
68
|
+
);
|
|
69
|
+
decipher.setAuthTag(Buffer.from(authTag, "hex"));
|
|
70
|
+
let decrypted = decipher.update(encryptedData, "hex", "utf8");
|
|
71
|
+
decrypted += decipher.final("utf8");
|
|
72
|
+
return JSON.parse(decrypted);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch products for the configured app.
|
|
79
|
+
*/
|
|
80
|
+
async getProducts(options) {
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
params.append("appId", this.appId);
|
|
83
|
+
if (options?.locale) params.append("locale", options.locale);
|
|
84
|
+
if (options?.currency) params.append("currency", options.currency);
|
|
85
|
+
const url = `${this.baseUrl}/api/v1/pay/products?${params.toString()}`;
|
|
86
|
+
const res = await fetch(url, {
|
|
87
|
+
method: "GET",
|
|
88
|
+
headers: {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"Accept": "application/json"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
const json = await res.json();
|
|
97
|
+
return json.data?.products || [];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a new order
|
|
101
|
+
* @param params - Order creation parameters
|
|
102
|
+
* @returns Order details with payment parameters
|
|
103
|
+
*/
|
|
104
|
+
async createOrder(params) {
|
|
105
|
+
return this.request("POST", "/orders", params);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Pay for an existing order
|
|
109
|
+
* @param orderId - The order ID to pay
|
|
110
|
+
* @param params - Payment parameters including channel
|
|
111
|
+
*/
|
|
112
|
+
async payOrder(orderId, params) {
|
|
113
|
+
return this.request("POST", `/orders/${orderId}/pay`, params);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Query order status
|
|
117
|
+
* @param orderId - The order ID to query
|
|
118
|
+
*/
|
|
119
|
+
async getOrderStatus(orderId) {
|
|
120
|
+
return this.request("GET", `/orders/${orderId}`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate checkout URL for client-side payment
|
|
124
|
+
* @param productId - Product ID
|
|
125
|
+
* @param priceId - Price ID
|
|
126
|
+
* @returns Checkout page URL
|
|
127
|
+
*/
|
|
128
|
+
getCheckoutUrl(productId, priceId) {
|
|
129
|
+
return `${this.baseUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
export {
|
|
133
|
+
PaymentClient
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: any;\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n /** API Base URL (e.g. https://pay.youidian.com) */\n baseUrl: string;\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n productId?: string;\n priceId?: string;\n channel?: string;\n userId: string;\n returnUrl?: string;\n metadata?: Record<string, any>;\n merchantOrderId?: string;\n openid?: string;\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n orderId: string;\n internalId: string;\n amount: number;\n currency: string;\n payParams: any;\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n iv: string;\n encryptedData: string;\n authTag: string;\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n orderId: string;\n merchantOrderId?: string;\n status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n paidAt: string;\n channelTransactionId?: string;\n metadata?: Record<string, any>;\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n private appId: string;\n private appSecret: string;\n private baseUrl: string;\n\n constructor(options: PaymentClientOptions) {\n if (!options.appId) throw new Error(\"appId is required\");\n if (!options.appSecret) throw new Error(\"appSecret is required\");\n if (!options.baseUrl) throw new Error(\"baseUrl is required\");\n\n this.appId = options.appId;\n this.appSecret = options.appSecret;\n this.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Generate SHA256 signature for the request\n * Logic: SHA256(appId + appSecret + timestamp)\n */\n private generateSignature(timestamp: number): string {\n const str = `${this.appId}${this.appSecret}${timestamp}`;\n return crypto.createHash(\"sha256\").update(str).digest(\"hex\");\n }\n\n /**\n * Internal request helper for Gateway API\n */\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const timestamp = Date.now();\n const signature = this.generateSignature(timestamp);\n\n const url = `${this.baseUrl}/api/v1/gateway/${this.appId}${path}`;\n\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n \"X-Pay-Timestamp\": timestamp.toString(),\n \"X-Pay-Sign\": signature,\n };\n\n const options: RequestInit = {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n };\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);\n }\n\n const json = await response.json();\n if (json.error) {\n throw new Error(`Payment API Error: ${json.error}`);\n }\n\n return json.data as T;\n }\n\n /**\n * Decrypts the callback notification payload using AES-256-GCM.\n * @param notification - The encrypted notification from payment webhook\n * @returns Decrypted payment callback data\n */\n decryptCallback(notification: PaymentNotification): PaymentCallbackData {\n try {\n const { iv, encryptedData, authTag } = notification;\n const key = crypto.createHash('sha256').update(this.appSecret).digest();\n const decipher = crypto.createDecipheriv(\n 'aes-256-gcm',\n key,\n Buffer.from(iv, 'hex')\n );\n\n decipher.setAuthTag(Buffer.from(authTag, 'hex'));\n\n let decrypted = decipher.update(encryptedData, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return JSON.parse(decrypted);\n } catch (error) {\n throw new Error(\"Failed to decrypt payment callback: Invalid secret or tampered data.\");\n }\n }\n\n /**\n * Fetch products for the configured app.\n */\n async getProducts(options?: {\n locale?: string;\n currency?: string;\n }): Promise<Product[]> {\n const params = new URLSearchParams();\n params.append('appId', this.appId);\n if (options?.locale) params.append('locale', options.locale);\n if (options?.currency) params.append('currency', options.currency);\n\n const url = `${this.baseUrl}/api/v1/pay/products?${params.toString()}`;\n\n const res = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n }\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);\n }\n\n const json = await res.json();\n return json.data?.products || [];\n }\n\n /**\n * Create a new order\n * @param params - Order creation parameters\n * @returns Order details with payment parameters\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n return this.request(\"POST\", \"/orders\", params);\n }\n\n /**\n * Pay for an existing order\n * @param orderId - The order ID to pay\n * @param params - Payment parameters including channel\n */\n async payOrder(orderId: string, params: {\n channel: string;\n returnUrl?: string;\n openid?: string;\n [key: string]: any;\n }): Promise<CreateOrderResponse> {\n return this.request(\"POST\", `/orders/${orderId}/pay`, params);\n }\n\n /**\n * Query order status\n * @param orderId - The order ID to query\n */\n async getOrderStatus(orderId: string): Promise<OrderStatus> {\n return this.request(\"GET\", `/orders/${orderId}`);\n }\n\n /**\n * Generate checkout URL for client-side payment\n * @param productId - Product ID\n * @param priceId - Price ID\n * @returns Checkout page URL\n */\n getCheckoutUrl(productId: string, priceId: string): string {\n return `${this.baseUrl}/checkout/${this.appId}/${productId}/${priceId}`;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@youidian/pay-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Youidian Payment SDK - 统一支付集成 SDK",
|
|
5
|
+
"author": "Youidian",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"types": "./dist/client.d.ts",
|
|
21
|
+
"import": "./dist/client.mjs",
|
|
22
|
+
"require": "./dist/client.js"
|
|
23
|
+
},
|
|
24
|
+
"./server": {
|
|
25
|
+
"types": "./dist/server.d.ts",
|
|
26
|
+
"import": "./dist/server.mjs",
|
|
27
|
+
"require": "./dist/server.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@repo/typescript-config": "*",
|
|
40
|
+
"tsup": "^8.0.0",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"payment",
|
|
45
|
+
"sdk",
|
|
46
|
+
"paypal",
|
|
47
|
+
"wechat-pay",
|
|
48
|
+
"alipay"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/youidian/youidian-pay"
|
|
53
|
+
}
|
|
54
|
+
}
|