@wiicode/youcanpay-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/README.md +848 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +171 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +12 -0
- package/dist/constants.js.map +1 -0
- package/dist/enums/currency.enum.d.ts +8 -0
- package/dist/enums/currency.enum.js +13 -0
- package/dist/enums/currency.enum.js.map +1 -0
- package/dist/enums/index.d.ts +2 -0
- package/dist/enums/index.js +19 -0
- package/dist/enums/index.js.map +1 -0
- package/dist/enums/lang.enum.d.ts +5 -0
- package/dist/enums/lang.enum.js +10 -0
- package/dist/enums/lang.enum.js.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +18 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/youcanpay.error.d.ts +16 -0
- package/dist/errors/youcanpay.error.js +32 -0
- package/dist/errors/youcanpay.error.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/index.d.ts +5 -0
- package/dist/interfaces/index.js +22 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/options.interface.d.ts +13 -0
- package/dist/interfaces/options.interface.js +3 -0
- package/dist/interfaces/options.interface.js.map +1 -0
- package/dist/interfaces/payment.interface.d.ts +22 -0
- package/dist/interfaces/payment.interface.js +3 -0
- package/dist/interfaces/payment.interface.js.map +1 -0
- package/dist/interfaces/token.interface.d.ts +26 -0
- package/dist/interfaces/token.interface.js +3 -0
- package/dist/interfaces/token.interface.js.map +1 -0
- package/dist/interfaces/transaction.interface.d.ts +10 -0
- package/dist/interfaces/transaction.interface.js +3 -0
- package/dist/interfaces/transaction.interface.js.map +1 -0
- package/dist/interfaces/webhook.interface.d.ts +16 -0
- package/dist/interfaces/webhook.interface.js +10 -0
- package/dist/interfaces/webhook.interface.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +20 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/interfaces.d.ts +22 -0
- package/dist/logging/interfaces.js +3 -0
- package/dist/logging/interfaces.js.map +1 -0
- package/dist/logging/logger.d.ts +6 -0
- package/dist/logging/logger.js +37 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/sanitizer.d.ts +1 -0
- package/dist/logging/sanitizer.js +55 -0
- package/dist/logging/sanitizer.js.map +1 -0
- package/dist/nestjs/decorators.d.ts +2 -0
- package/dist/nestjs/decorators.js +8 -0
- package/dist/nestjs/decorators.js.map +1 -0
- package/dist/nestjs/guards/index.d.ts +1 -0
- package/dist/nestjs/guards/index.js +9 -0
- package/dist/nestjs/guards/index.js.map +1 -0
- package/dist/nestjs/guards/webhook.guard.d.ts +16 -0
- package/dist/nestjs/guards/webhook.guard.js +59 -0
- package/dist/nestjs/guards/webhook.guard.js.map +1 -0
- package/dist/nestjs/index.d.ts +5 -0
- package/dist/nestjs/index.js +22 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/nestjs/pipes/index.d.ts +1 -0
- package/dist/nestjs/pipes/index.js +7 -0
- package/dist/nestjs/pipes/index.js.map +1 -0
- package/dist/nestjs/pipes/webhook.pipe.d.ts +6 -0
- package/dist/nestjs/pipes/webhook.pipe.js +34 -0
- package/dist/nestjs/pipes/webhook.pipe.js.map +1 -0
- package/dist/nestjs/youcanpay.module.d.ts +6 -0
- package/dist/nestjs/youcanpay.module.js +48 -0
- package/dist/nestjs/youcanpay.module.js.map +1 -0
- package/dist/nestjs/youcanpay.service.d.ts +5 -0
- package/dist/nestjs/youcanpay.service.js +30 -0
- package/dist/nestjs/youcanpay.service.js.map +1 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +24 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/validators.d.ts +37 -0
- package/dist/security/validators.js +183 -0
- package/dist/security/validators.js.map +1 -0
- package/dist/security/webhook.d.ts +80 -0
- package/dist/security/webhook.js +146 -0
- package/dist/security/webhook.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUPPORTED_CURRENCIES = void 0;
|
|
4
|
+
exports.validateAmount = validateAmount;
|
|
5
|
+
exports.validateCurrency = validateCurrency;
|
|
6
|
+
exports.validateRedirectURL = validateRedirectURL;
|
|
7
|
+
exports.validateOrderId = validateOrderId;
|
|
8
|
+
exports.validateIP = validateIP;
|
|
9
|
+
exports.validateEmail = validateEmail;
|
|
10
|
+
exports.validatePaymentInput = validatePaymentInput;
|
|
11
|
+
exports.sanitizeString = sanitizeString;
|
|
12
|
+
exports.toCentimes = toCentimes;
|
|
13
|
+
exports.fromCentimes = fromCentimes;
|
|
14
|
+
exports.formatAmount = formatAmount;
|
|
15
|
+
exports.SUPPORTED_CURRENCIES = ['MAD', 'USD', 'EUR'];
|
|
16
|
+
function validateAmount(amount, options = {}) {
|
|
17
|
+
const { min = 100, max = 100000000, currency = 'centimes' } = options;
|
|
18
|
+
if (amount === null || amount === undefined) {
|
|
19
|
+
return { valid: false, error: 'Amount is required' };
|
|
20
|
+
}
|
|
21
|
+
if (typeof amount !== 'number') {
|
|
22
|
+
return { valid: false, error: 'Amount must be a number' };
|
|
23
|
+
}
|
|
24
|
+
const numAmount = amount;
|
|
25
|
+
if (!Number.isInteger(numAmount)) {
|
|
26
|
+
return { valid: false, error: 'Amount must be an integer (in centimes)' };
|
|
27
|
+
}
|
|
28
|
+
if (numAmount < min) {
|
|
29
|
+
return { valid: false, error: `Amount must be at least ${min} ${currency}` };
|
|
30
|
+
}
|
|
31
|
+
if (numAmount > max) {
|
|
32
|
+
return { valid: false, error: `Amount must not exceed ${max} ${currency}` };
|
|
33
|
+
}
|
|
34
|
+
return { valid: true };
|
|
35
|
+
}
|
|
36
|
+
function validateCurrency(currency) {
|
|
37
|
+
if (!currency || typeof currency !== 'string') {
|
|
38
|
+
return { valid: false, error: 'Currency is required' };
|
|
39
|
+
}
|
|
40
|
+
const upperCurrency = currency.toUpperCase();
|
|
41
|
+
if (!exports.SUPPORTED_CURRENCIES.includes(upperCurrency)) {
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
error: `Currency must be one of: ${exports.SUPPORTED_CURRENCIES.join(', ')}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return { valid: true };
|
|
48
|
+
}
|
|
49
|
+
function validateRedirectURL(url, options = {}) {
|
|
50
|
+
const { protocols = ['https', 'http'], allowedDomains, blockedDomains = [], allowLocalhost = true, requireTLD = false, } = options;
|
|
51
|
+
if (!url) {
|
|
52
|
+
return { valid: true };
|
|
53
|
+
}
|
|
54
|
+
if (typeof url !== 'string') {
|
|
55
|
+
return { valid: false, error: 'URL must be a string' };
|
|
56
|
+
}
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = new URL(url);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { valid: false, error: 'Invalid URL format' };
|
|
63
|
+
}
|
|
64
|
+
const protocol = parsed.protocol.replace(':', '');
|
|
65
|
+
if (!protocols.includes(protocol)) {
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
error: `URL protocol must be one of: ${protocols.join(', ')}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
72
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
|
73
|
+
if (isLocalhost && !allowLocalhost) {
|
|
74
|
+
return { valid: false, error: 'Localhost URLs are not allowed' };
|
|
75
|
+
}
|
|
76
|
+
if (!isLocalhost && requireTLD) {
|
|
77
|
+
const hasTLD = hostname.includes('.') && !hostname.endsWith('.');
|
|
78
|
+
if (!hasTLD) {
|
|
79
|
+
return { valid: false, error: 'URL must have a valid domain with TLD' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const blocked of blockedDomains) {
|
|
83
|
+
if (hostname === blocked || hostname.endsWith(`.${blocked}`)) {
|
|
84
|
+
return { valid: false, error: `Domain "${blocked}" is not allowed` };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (allowedDomains && allowedDomains.length > 0) {
|
|
88
|
+
const isAllowed = allowedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
89
|
+
if (!isAllowed) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: `Domain must be one of: ${allowedDomains.join(', ')}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (parsed.username || parsed.password) {
|
|
97
|
+
return { valid: false, error: 'URL must not contain credentials' };
|
|
98
|
+
}
|
|
99
|
+
return { valid: true };
|
|
100
|
+
}
|
|
101
|
+
function validateOrderId(orderId) {
|
|
102
|
+
if (!orderId || typeof orderId !== 'string') {
|
|
103
|
+
return { valid: false, error: 'Order ID is required' };
|
|
104
|
+
}
|
|
105
|
+
if (orderId.length < 1 || orderId.length > 255) {
|
|
106
|
+
return { valid: false, error: 'Order ID must be between 1 and 255 characters' };
|
|
107
|
+
}
|
|
108
|
+
const safePattern = /^[a-zA-Z0-9_\-:.]+$/;
|
|
109
|
+
if (!safePattern.test(orderId)) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
error: 'Order ID contains invalid characters (allowed: alphanumeric, _, -, :, .)',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { valid: true };
|
|
116
|
+
}
|
|
117
|
+
function validateIP(ip) {
|
|
118
|
+
if (!ip || typeof ip !== 'string') {
|
|
119
|
+
return { valid: false, error: 'IP address is required' };
|
|
120
|
+
}
|
|
121
|
+
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
122
|
+
const ipv6Pattern = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
123
|
+
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip) && ip !== '::1') {
|
|
124
|
+
return { valid: false, error: 'Invalid IP address format' };
|
|
125
|
+
}
|
|
126
|
+
if (ipv4Pattern.test(ip)) {
|
|
127
|
+
const octets = ip.split('.').map(Number);
|
|
128
|
+
if (octets.some((octet) => octet > 255)) {
|
|
129
|
+
return { valid: false, error: 'Invalid IP address: octet out of range' };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { valid: true };
|
|
133
|
+
}
|
|
134
|
+
function validateEmail(email) {
|
|
135
|
+
if (!email) {
|
|
136
|
+
return { valid: true };
|
|
137
|
+
}
|
|
138
|
+
if (typeof email !== 'string') {
|
|
139
|
+
return { valid: false, error: 'Email must be a string' };
|
|
140
|
+
}
|
|
141
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
142
|
+
if (!emailPattern.test(email)) {
|
|
143
|
+
return { valid: false, error: 'Invalid email format' };
|
|
144
|
+
}
|
|
145
|
+
if (email.length > 254) {
|
|
146
|
+
return { valid: false, error: 'Email is too long' };
|
|
147
|
+
}
|
|
148
|
+
return { valid: true };
|
|
149
|
+
}
|
|
150
|
+
function validatePaymentInput(input) {
|
|
151
|
+
const checks = [
|
|
152
|
+
validateAmount(input.amount),
|
|
153
|
+
validateCurrency(input.currency),
|
|
154
|
+
input.orderId !== undefined ? validateOrderId(input.orderId) : { valid: true },
|
|
155
|
+
input.customerIp !== undefined ? validateIP(input.customerIp) : { valid: true },
|
|
156
|
+
validateRedirectURL(input.successUrl),
|
|
157
|
+
validateRedirectURL(input.errorUrl),
|
|
158
|
+
validateEmail(input.customerEmail),
|
|
159
|
+
];
|
|
160
|
+
const failed = checks.find((check) => !check.valid);
|
|
161
|
+
if (failed) {
|
|
162
|
+
return failed;
|
|
163
|
+
}
|
|
164
|
+
return { valid: true };
|
|
165
|
+
}
|
|
166
|
+
function sanitizeString(input) {
|
|
167
|
+
return input
|
|
168
|
+
.replace(/[<>]/g, '')
|
|
169
|
+
.replace(/javascript:/gi, '')
|
|
170
|
+
.replace(/data:/gi, '')
|
|
171
|
+
.trim();
|
|
172
|
+
}
|
|
173
|
+
function toCentimes(amount) {
|
|
174
|
+
return Math.round(amount * 100);
|
|
175
|
+
}
|
|
176
|
+
function fromCentimes(centimes) {
|
|
177
|
+
return centimes / 100;
|
|
178
|
+
}
|
|
179
|
+
function formatAmount(centimes, currency) {
|
|
180
|
+
const amount = fromCentimes(centimes);
|
|
181
|
+
return `${amount.toFixed(2)} ${currency}`;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/security/validators.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export interface YouCanPayRawWebhook {
|
|
2
|
+
id: string;
|
|
3
|
+
event_name: string;
|
|
4
|
+
sandbox: boolean;
|
|
5
|
+
payload: {
|
|
6
|
+
transaction: {
|
|
7
|
+
id: string;
|
|
8
|
+
status: number;
|
|
9
|
+
order_id: string;
|
|
10
|
+
amount: string | number;
|
|
11
|
+
currency: string;
|
|
12
|
+
base_currency?: string | null;
|
|
13
|
+
base_amount?: string | null;
|
|
14
|
+
created_at: string;
|
|
15
|
+
};
|
|
16
|
+
payment_method?: {
|
|
17
|
+
id: number;
|
|
18
|
+
name: string;
|
|
19
|
+
card?: {
|
|
20
|
+
id: string;
|
|
21
|
+
country_code?: string | null;
|
|
22
|
+
brand?: string | null;
|
|
23
|
+
last_digits: string;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
is_3d_secure: boolean;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
token?: {
|
|
29
|
+
id: string;
|
|
30
|
+
};
|
|
31
|
+
customer?: {
|
|
32
|
+
id: string;
|
|
33
|
+
email?: string | null;
|
|
34
|
+
name?: string | null;
|
|
35
|
+
address?: string | null;
|
|
36
|
+
phone?: string | null;
|
|
37
|
+
country_code?: string | null;
|
|
38
|
+
city?: string | null;
|
|
39
|
+
state?: string | null;
|
|
40
|
+
zip_code?: string | null;
|
|
41
|
+
};
|
|
42
|
+
metadata?: Record<string, string> | unknown[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface ParsedWebhookPayload {
|
|
46
|
+
webhookId: string;
|
|
47
|
+
eventName: string;
|
|
48
|
+
sandbox: boolean;
|
|
49
|
+
transactionId: string;
|
|
50
|
+
orderId: string;
|
|
51
|
+
amount: number;
|
|
52
|
+
currency: string;
|
|
53
|
+
rawStatus: number;
|
|
54
|
+
status: 'paid' | 'failed' | 'refunded' | 'unknown';
|
|
55
|
+
isSuccess: boolean;
|
|
56
|
+
tokenId?: string;
|
|
57
|
+
cardLastDigits?: string;
|
|
58
|
+
cardBrand?: string;
|
|
59
|
+
is3DSecure?: boolean;
|
|
60
|
+
customerEmail?: string;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
raw: YouCanPayRawWebhook;
|
|
63
|
+
}
|
|
64
|
+
export interface WebhookVerifyOptions {
|
|
65
|
+
secret: string;
|
|
66
|
+
query?: Record<string, string>;
|
|
67
|
+
headers?: Record<string, string>;
|
|
68
|
+
signatureHeader?: string;
|
|
69
|
+
secretParam?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function parseWebhookPayload(payload: unknown): ParsedWebhookPayload;
|
|
72
|
+
export declare function verifyWebhookSecret(options: WebhookVerifyOptions): boolean;
|
|
73
|
+
export declare function verifyWebhookHMAC(payload: string | Buffer, signature: string, secret: string, algorithm?: 'sha256' | 'sha512'): boolean;
|
|
74
|
+
export declare function createWebhookSignature(payload: string | object, secret: string, algorithm?: 'sha256' | 'sha512'): string;
|
|
75
|
+
export declare class WebhookParseError extends Error {
|
|
76
|
+
constructor(message: string);
|
|
77
|
+
}
|
|
78
|
+
export declare class WebhookVerifyError extends Error {
|
|
79
|
+
constructor(message: string);
|
|
80
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WebhookVerifyError = exports.WebhookParseError = void 0;
|
|
37
|
+
exports.parseWebhookPayload = parseWebhookPayload;
|
|
38
|
+
exports.verifyWebhookSecret = verifyWebhookSecret;
|
|
39
|
+
exports.verifyWebhookHMAC = verifyWebhookHMAC;
|
|
40
|
+
exports.createWebhookSignature = createWebhookSignature;
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
42
|
+
function parseWebhookPayload(payload) {
|
|
43
|
+
if (!payload || typeof payload !== 'object') {
|
|
44
|
+
throw new WebhookParseError('Invalid webhook payload: not an object');
|
|
45
|
+
}
|
|
46
|
+
const p = payload;
|
|
47
|
+
if (!p['id'] || !p['event_name'] || !p['payload']) {
|
|
48
|
+
throw new WebhookParseError('Invalid webhook payload: missing required fields (id, event_name, payload)');
|
|
49
|
+
}
|
|
50
|
+
const rawPayload = p;
|
|
51
|
+
const transaction = rawPayload.payload?.transaction;
|
|
52
|
+
if (!transaction) {
|
|
53
|
+
throw new WebhookParseError('Invalid webhook payload: missing transaction data');
|
|
54
|
+
}
|
|
55
|
+
if (!transaction.id || !transaction.order_id) {
|
|
56
|
+
throw new WebhookParseError('Invalid webhook payload: missing transaction_id or order_id');
|
|
57
|
+
}
|
|
58
|
+
const eventName = rawPayload.event_name;
|
|
59
|
+
const rawStatus = Number(transaction.status);
|
|
60
|
+
let status;
|
|
61
|
+
if (rawStatus === 1 || eventName === 'transaction.paid') {
|
|
62
|
+
status = 'paid';
|
|
63
|
+
}
|
|
64
|
+
else if (eventName === 'transaction.failed') {
|
|
65
|
+
status = 'failed';
|
|
66
|
+
}
|
|
67
|
+
else if (eventName === 'refund.success' || eventName === 'transaction.refunded') {
|
|
68
|
+
status = 'refunded';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
status = 'unknown';
|
|
72
|
+
}
|
|
73
|
+
const isSuccess = status === 'paid';
|
|
74
|
+
return {
|
|
75
|
+
webhookId: rawPayload.id,
|
|
76
|
+
eventName,
|
|
77
|
+
sandbox: rawPayload.sandbox,
|
|
78
|
+
transactionId: transaction.id,
|
|
79
|
+
orderId: transaction.order_id,
|
|
80
|
+
amount: Number(transaction.amount),
|
|
81
|
+
currency: transaction.currency,
|
|
82
|
+
rawStatus,
|
|
83
|
+
status,
|
|
84
|
+
isSuccess,
|
|
85
|
+
tokenId: rawPayload.payload.token?.id,
|
|
86
|
+
cardLastDigits: rawPayload.payload.payment_method?.card?.last_digits,
|
|
87
|
+
cardBrand: rawPayload.payload.payment_method?.card?.brand || undefined,
|
|
88
|
+
is3DSecure: rawPayload.payload.payment_method?.card?.is_3d_secure,
|
|
89
|
+
customerEmail: rawPayload.payload.customer?.email || undefined,
|
|
90
|
+
createdAt: transaction.created_at,
|
|
91
|
+
raw: rawPayload,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function verifyWebhookSecret(options) {
|
|
95
|
+
const { secret, query, headers, secretParam = 'secret', signatureHeader = 'x-ycp-signature' } = options;
|
|
96
|
+
if (!secret) {
|
|
97
|
+
throw new WebhookVerifyError('Webhook secret is required');
|
|
98
|
+
}
|
|
99
|
+
if (headers) {
|
|
100
|
+
const signature = headers[signatureHeader] || headers[signatureHeader.toLowerCase()];
|
|
101
|
+
if (signature && signature === secret) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (query) {
|
|
106
|
+
const querySecret = query[secretParam];
|
|
107
|
+
if (querySecret && querySecret === secret) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function verifyWebhookHMAC(payload, signature, secret, algorithm = 'sha256') {
|
|
114
|
+
const expectedSignature = crypto
|
|
115
|
+
.createHmac(algorithm, secret)
|
|
116
|
+
.update(payload)
|
|
117
|
+
.digest('hex');
|
|
118
|
+
try {
|
|
119
|
+
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createWebhookSignature(payload, secret, algorithm = 'sha256') {
|
|
126
|
+
const data = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
127
|
+
return crypto
|
|
128
|
+
.createHmac(algorithm, secret)
|
|
129
|
+
.update(data)
|
|
130
|
+
.digest('hex');
|
|
131
|
+
}
|
|
132
|
+
class WebhookParseError extends Error {
|
|
133
|
+
constructor(message) {
|
|
134
|
+
super(message);
|
|
135
|
+
this.name = 'WebhookParseError';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.WebhookParseError = WebhookParseError;
|
|
139
|
+
class WebhookVerifyError extends Error {
|
|
140
|
+
constructor(message) {
|
|
141
|
+
super(message);
|
|
142
|
+
this.name = 'WebhookVerifyError';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.WebhookVerifyError = WebhookVerifyError;
|
|
146
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/security/webhook.ts"],"names":[],"mappings":"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"}
|