swiftshopr-payments 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/LICENSE +21 -0
- package/README.md +339 -0
- package/package.json +54 -0
- package/src/branding.js +127 -0
- package/src/client.js +111 -0
- package/src/config.js +273 -0
- package/src/dashboard.js +265 -0
- package/src/index.js +79 -0
- package/src/payments.js +319 -0
- package/src/refunds.js +209 -0
- package/src/types/index.d.ts +460 -0
- package/src/utils/http.js +217 -0
- package/src/webhooks.js +163 -0
package/src/webhooks.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhooks Module
|
|
3
|
+
* Helpers for receiving and verifying SwiftShopr webhooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { SwiftShoprError } = require('./utils/http');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Webhook event types
|
|
11
|
+
*/
|
|
12
|
+
const WebhookEvents = {
|
|
13
|
+
PAYMENT_COMPLETED: 'payment.completed',
|
|
14
|
+
PAYMENT_FAILED: 'payment.failed',
|
|
15
|
+
PAYMENT_EXPIRED: 'payment.expired',
|
|
16
|
+
REFUND_REQUESTED: 'refund.requested',
|
|
17
|
+
REFUND_COMPLETED: 'refund.completed',
|
|
18
|
+
REFUND_FAILED: 'refund.failed',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum age of webhook timestamp (5 minutes)
|
|
23
|
+
*/
|
|
24
|
+
const MAX_TIMESTAMP_AGE_MS = 5 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create Webhooks helper instance
|
|
28
|
+
*/
|
|
29
|
+
function createWebhooksHelper(webhookSecret) {
|
|
30
|
+
/**
|
|
31
|
+
* Verify webhook signature
|
|
32
|
+
*
|
|
33
|
+
* @param {string|Object} payload - Raw request body (string) or parsed JSON
|
|
34
|
+
* @param {string} signature - X-Webhook-Signature header
|
|
35
|
+
* @param {string} timestamp - X-Webhook-Timestamp header
|
|
36
|
+
* @returns {boolean} True if signature is valid
|
|
37
|
+
*/
|
|
38
|
+
function verify(payload, signature, timestamp) {
|
|
39
|
+
if (!webhookSecret) {
|
|
40
|
+
throw new SwiftShoprError(
|
|
41
|
+
'CONFIG_ERROR',
|
|
42
|
+
'Webhook secret is required for verification',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!payload || !signature || !timestamp) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check timestamp is not too old (replay attack prevention)
|
|
51
|
+
const timestampMs = parseInt(timestamp, 10);
|
|
52
|
+
if (isNaN(timestampMs)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const age = Date.now() - timestampMs;
|
|
57
|
+
if (age > MAX_TIMESTAMP_AGE_MS || age < -MAX_TIMESTAMP_AGE_MS) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Compute expected signature
|
|
62
|
+
const payloadString =
|
|
63
|
+
typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
64
|
+
const signaturePayload = `${timestamp}.${payloadString}`;
|
|
65
|
+
|
|
66
|
+
const expectedSignature = crypto
|
|
67
|
+
.createHmac('sha256', webhookSecret)
|
|
68
|
+
.update(signaturePayload)
|
|
69
|
+
.digest('hex');
|
|
70
|
+
|
|
71
|
+
// Constant-time comparison
|
|
72
|
+
try {
|
|
73
|
+
return crypto.timingSafeEqual(
|
|
74
|
+
Buffer.from(signature),
|
|
75
|
+
Buffer.from(expectedSignature),
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse and verify webhook event
|
|
84
|
+
*
|
|
85
|
+
* @param {string|Object} payload - Raw request body
|
|
86
|
+
* @param {Object} headers - Request headers
|
|
87
|
+
* @returns {Object} Parsed and verified webhook event
|
|
88
|
+
* @throws {SwiftShoprError} If verification fails
|
|
89
|
+
*/
|
|
90
|
+
function constructEvent(payload, headers) {
|
|
91
|
+
const signature =
|
|
92
|
+
headers['x-webhook-signature'] || headers['X-Webhook-Signature'];
|
|
93
|
+
const timestamp =
|
|
94
|
+
headers['x-webhook-timestamp'] || headers['X-Webhook-Timestamp'];
|
|
95
|
+
|
|
96
|
+
if (!verify(payload, signature, timestamp)) {
|
|
97
|
+
throw new SwiftShoprError(
|
|
98
|
+
'WEBHOOK_SIGNATURE_INVALID',
|
|
99
|
+
'Webhook signature verification failed',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const event = typeof payload === 'string' ? JSON.parse(payload) : payload;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
type: event.event,
|
|
107
|
+
data: {
|
|
108
|
+
intentId: event.intentId,
|
|
109
|
+
orderId: event.orderId,
|
|
110
|
+
txHash: event.txHash,
|
|
111
|
+
amount: event.amount,
|
|
112
|
+
status: event.status,
|
|
113
|
+
currency: event.currency,
|
|
114
|
+
network: event.network,
|
|
115
|
+
explorerUrl: event.explorerUrl,
|
|
116
|
+
storeId: event.storeId,
|
|
117
|
+
timestamp: event.timestamp,
|
|
118
|
+
// Refund-specific fields
|
|
119
|
+
refundId: event.refundId,
|
|
120
|
+
refundAmount: event.refundAmount,
|
|
121
|
+
refundReason: event.refundReason,
|
|
122
|
+
},
|
|
123
|
+
receivedAt: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
verify,
|
|
129
|
+
constructEvent,
|
|
130
|
+
events: WebhookEvents,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Express middleware for webhook verification
|
|
136
|
+
*
|
|
137
|
+
* @param {string} secret - Webhook secret
|
|
138
|
+
* @returns {Function} Express middleware
|
|
139
|
+
*/
|
|
140
|
+
function webhookMiddleware(secret) {
|
|
141
|
+
const helper = createWebhooksHelper(secret);
|
|
142
|
+
|
|
143
|
+
return (req, res, next) => {
|
|
144
|
+
try {
|
|
145
|
+
// Need raw body for signature verification
|
|
146
|
+
const payload = req.rawBody || req.body;
|
|
147
|
+
const event = helper.constructEvent(payload, req.headers);
|
|
148
|
+
req.webhookEvent = event;
|
|
149
|
+
next();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
res.status(400).json({
|
|
152
|
+
error: 'webhook_verification_failed',
|
|
153
|
+
message: error.message,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
createWebhooksHelper,
|
|
161
|
+
webhookMiddleware,
|
|
162
|
+
WebhookEvents,
|
|
163
|
+
};
|