@swirepay-developer/swirepay-card-sdk 2.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/index.js +16 -0
- package/package.json +23 -0
- package/web-component/swirepay-checkout.js +799 -0
package/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./web-component/swirepay-checkout.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper init function (optional usage)
|
|
5
|
+
*/
|
|
6
|
+
export const init = (config = {}) => {
|
|
7
|
+
const el = document.createElement("swirepay-checkout");
|
|
8
|
+
|
|
9
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
10
|
+
if (value !== undefined && value !== null) {
|
|
11
|
+
el.setAttribute(key, value);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return el;
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@swirepay-developer/swirepay-card-sdk",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Swirepay Card Payment SDK (Web Component with Modal UI)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"swirepay",
|
|
9
|
+
"payments",
|
|
10
|
+
"sdk",
|
|
11
|
+
"checkout",
|
|
12
|
+
"web-components"
|
|
13
|
+
],
|
|
14
|
+
"author": "Swirepay",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"libphonenumber-js": "^1.10.53",
|
|
18
|
+
"postal-codes-js": "^2.5.2"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import postalCodes from "postal-codes-js";
|
|
2
|
+
import { isValidPhoneNumber } from "libphonenumber-js";
|
|
3
|
+
|
|
4
|
+
const SDK_CONFIG = {
|
|
5
|
+
elliptic: "https://cdnjs.cloudflare.com/ajax/libs/elliptic/6.3.1/elliptic.js",
|
|
6
|
+
crypto: "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"
|
|
7
|
+
};
|
|
8
|
+
class EventEmitter {
|
|
9
|
+
constructor() { this.events = {}; }
|
|
10
|
+
on(e, cb) {
|
|
11
|
+
this.events[e] = this.events[e] || [];
|
|
12
|
+
this.events[e].push(cb);
|
|
13
|
+
}
|
|
14
|
+
emit(e, d) {
|
|
15
|
+
(this.events[e] || []).forEach(cb => cb(d));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const INDIAN_STATES = [
|
|
19
|
+
{ "code": "AN", "name": "Andaman and Nicobar Islands" },
|
|
20
|
+
{ "code": "AP", "name": "Andhra Pradesh" },
|
|
21
|
+
{ "code": "AR", "name": "Arunachal Pradesh" },
|
|
22
|
+
{ "code": "AS", "name": "Assam" },
|
|
23
|
+
{ "code": "BR", "name": "Bihar" },
|
|
24
|
+
{ "code": "CG", "name": "Chandigarh" },
|
|
25
|
+
{ "code": "CH", "name": "Chhattisgarh" },
|
|
26
|
+
{ "code": "DH", "name": "Dadra and Nagar Haveli" },
|
|
27
|
+
{ "code": "DD", "name": "Daman and Diu" },
|
|
28
|
+
{ "code": "DL", "name": "Delhi" },
|
|
29
|
+
{ "code": "GA", "name": "Goa" },
|
|
30
|
+
{ "code": "GJ", "name": "Gujarat" },
|
|
31
|
+
{ "code": "HR", "name": "Haryana" },
|
|
32
|
+
{ "code": "HP", "name": "Himachal Pradesh" },
|
|
33
|
+
{ "code": "JK", "name": "Jammu and Kashmir" },
|
|
34
|
+
{ "code": "JH", "name": "Jharkhand" },
|
|
35
|
+
{ "code": "KA", "name": "Karnataka" },
|
|
36
|
+
{ "code": "KL", "name": "Kerala" },
|
|
37
|
+
{ "code": "LD", "name": "Lakshadweep" },
|
|
38
|
+
{ "code": "MP", "name": "Madhya Pradesh" },
|
|
39
|
+
{ "code": "MH", "name": "Maharashtra" },
|
|
40
|
+
{ "code": "MN", "name": "Manipur" },
|
|
41
|
+
{ "code": "ML", "name": "Meghalaya" },
|
|
42
|
+
{ "code": "MZ", "name": "Mizoram" },
|
|
43
|
+
{ "code": "NL", "name": "Nagaland" },
|
|
44
|
+
{ "code": "OR", "name": "Odisha" },
|
|
45
|
+
{ "code": "PY", "name": "Puducherry" },
|
|
46
|
+
{ "code": "PB", "name": "Punjab" },
|
|
47
|
+
{ "code": "RJ", "name": "Rajasthan" },
|
|
48
|
+
{ "code": "SK", "name": "Sikkim" },
|
|
49
|
+
{ "code": "TN", "name": "Tamil Nadu" },
|
|
50
|
+
{ "code": "TS", "name": "Telangana" },
|
|
51
|
+
{ "code": "TR", "name": "Tripura" },
|
|
52
|
+
{ "code": "UK", "name": "Uttarakhand" },
|
|
53
|
+
{ "code": "UP", "name": "Uttar Pradesh" },
|
|
54
|
+
{ "code": "WB", "name": "West Bengal" }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const USA_STATES = [
|
|
58
|
+
{ "code": "AL", "name": "Alabama" },
|
|
59
|
+
{ "code": "AK", "name": "Alaska" },
|
|
60
|
+
{ "code": "AZ", "name": "Arizona" },
|
|
61
|
+
{ "code": "AR", "name": "Arkansas" },
|
|
62
|
+
{ "code": "CA", "name": "California" },
|
|
63
|
+
{ "code": "CO", "name": "Colorado" },
|
|
64
|
+
{ "code": "CT", "name": "Connecticut" },
|
|
65
|
+
{ "code": "DE", "name": "Delaware" },
|
|
66
|
+
{ "code": "FL", "name": "Florida" },
|
|
67
|
+
{ "code": "GA", "name": "Georgia" },
|
|
68
|
+
{ "code": "HI", "name": "Hawaii" },
|
|
69
|
+
{ "code": "ID", "name": "Idaho" },
|
|
70
|
+
{ "code": "IL", "name": "Illinois" },
|
|
71
|
+
{ "code": "IN", "name": "Indiana" },
|
|
72
|
+
{ "code": "IA", "name": "Iowa" },
|
|
73
|
+
{ "code": "KS", "name": "Kansas" },
|
|
74
|
+
{ "code": "KY", "name": "Kentucky" },
|
|
75
|
+
{ "code": "LY", "name": "Louisiana" },
|
|
76
|
+
{ "code": "ME", "name": "Maine" },
|
|
77
|
+
{ "code": "MD", "name": "Maryland" },
|
|
78
|
+
{ "code": "MA", "name": "Massachusetts" },
|
|
79
|
+
{ "code": "MI", "name": "Michigan" },
|
|
80
|
+
{ "code": "MN", "name": "Minnesota" },
|
|
81
|
+
{ "code": "MS", "name": "Mississippi" },
|
|
82
|
+
{ "code": "MO", "name": "Missouri" },
|
|
83
|
+
{ "code": "MT", "name": "Montana" },
|
|
84
|
+
{ "code": "NE", "name": "Nebraska" },
|
|
85
|
+
{ "code": "NV", "name": "Nevada" },
|
|
86
|
+
{ "code": "NH", "name": "New Hampshire" },
|
|
87
|
+
{ "code": "NJ", "name": "New Jersey" },
|
|
88
|
+
{ "code": "NM", "name": "New Mexico" },
|
|
89
|
+
{ "code": "NY", "name": "New York" },
|
|
90
|
+
{ "code": "NC", "name": "North Carolina" },
|
|
91
|
+
{ "code": "ND", "name": "North Dakota" },
|
|
92
|
+
{ "code": "OH", "name": "Ohio" },
|
|
93
|
+
{ "code": "OK", "name": "Oklahoma" },
|
|
94
|
+
{ "code": "OR", "name": "Oregon" },
|
|
95
|
+
{ "code": "PA", "name": "Pennsylvania" },
|
|
96
|
+
{ "code": "RI", "name": "Rhode Island" },
|
|
97
|
+
{ "code": "SC", "name": "South Carolina" },
|
|
98
|
+
{ "code": "SD", "name": "South Dakota" },
|
|
99
|
+
{ "code": "TN", "name": "Tennessee" },
|
|
100
|
+
{ "code": "TX", "name": "Texas" },
|
|
101
|
+
{ "code": "UT", "name": "Utah" },
|
|
102
|
+
{ "code": "VT", "name": "Vermont" },
|
|
103
|
+
{ "code": "VA", "name": "Virginia" },
|
|
104
|
+
{ "code": "WA", "name": "Washington" },
|
|
105
|
+
{ "code": "WV", "name": "West Virginia" },
|
|
106
|
+
{ "code": "WI", "name": "Wisconsin" },
|
|
107
|
+
{ "code": "WY", "name": "Wyoming" },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const COUNTRIES_LIST = [
|
|
111
|
+
{ "name": "Afghanistan", "code": "AF" },
|
|
112
|
+
{ "name": "Albania", "code": "AL" },
|
|
113
|
+
{ "name": "Algeria", "code": "DZ" },
|
|
114
|
+
{ "name": "Andorra", "code": "AD" },
|
|
115
|
+
{ "name": "Angola", "code": "AO" },
|
|
116
|
+
{ "name": "Antigua and Barbuda", "code": "AG" },
|
|
117
|
+
{ "name": "Argentina", "code": "AR" },
|
|
118
|
+
{ "name": "Armenia", "code": "AM" },
|
|
119
|
+
{ "name": "Australia", "code": "AU" },
|
|
120
|
+
{ "name": "Austria", "code": "AT" },
|
|
121
|
+
{ "name": "Azerbaijan", "code": "AZ" },
|
|
122
|
+
{ "name": "Bahamas", "code": "BS" },
|
|
123
|
+
{ "name": "Bahrain", "code": "BH" },
|
|
124
|
+
{ "name": "Bangladesh", "code": "BD" },
|
|
125
|
+
{ "name": "Barbados", "code": "BB" },
|
|
126
|
+
{ "name": "Belarus", "code": "BY" },
|
|
127
|
+
{ "name": "Belgium", "code": "BE" },
|
|
128
|
+
{ "name": "Belize", "code": "BZ" },
|
|
129
|
+
{ "name": "Benin", "code": "BJ" },
|
|
130
|
+
{ "name": "Bhutan", "code": "BT" },
|
|
131
|
+
{ "name": "Bolivia", "code": "BO" },
|
|
132
|
+
{ "name": "Bosnia and Herzegovina", "code": "BA" },
|
|
133
|
+
{ "name": "Botswana", "code": "BW" },
|
|
134
|
+
{ "name": "Brazil", "code": "BR" },
|
|
135
|
+
{ "name": "Brunei", "code": "BN" },
|
|
136
|
+
{ "name": "Bulgaria", "code": "BG" },
|
|
137
|
+
{ "name": "Burkina Faso", "code": "BF" },
|
|
138
|
+
{ "name": "Burundi", "code": "BI" },
|
|
139
|
+
{ "name": "Cambodia", "code": "KH" },
|
|
140
|
+
{ "name": "Cameroon", "code": "CM" },
|
|
141
|
+
{ "name": "Canada", "code": "CA" },
|
|
142
|
+
{ "name": "Cape Verde", "code": "CV" },
|
|
143
|
+
{ "name": "Central African Republic", "code": "CF" },
|
|
144
|
+
{ "name": "Chad", "code": "TD" },
|
|
145
|
+
{ "name": "Chile", "code": "CL" },
|
|
146
|
+
{ "name": "China", "code": "CN" },
|
|
147
|
+
{ "name": "Colombia", "code": "CO" },
|
|
148
|
+
{ "name": "Comoros", "code": "KM" },
|
|
149
|
+
{ "name": "Congo", "code": "CG" },
|
|
150
|
+
{ "name": "Costa Rica", "code": "CR" },
|
|
151
|
+
{ "name": "Croatia", "code": "HR" },
|
|
152
|
+
{ "name": "Cuba", "code": "CU" },
|
|
153
|
+
{ "name": "Cyprus", "code": "CY" },
|
|
154
|
+
{ "name": "Czech Republic", "code": "CZ" },
|
|
155
|
+
{ "name": "Denmark", "code": "DK" },
|
|
156
|
+
{ "name": "Djibouti", "code": "DJ" },
|
|
157
|
+
{ "name": "Dominica", "code": "DM" },
|
|
158
|
+
{ "name": "Dominican Republic", "code": "DO" },
|
|
159
|
+
{ "name": "Ecuador", "code": "EC" },
|
|
160
|
+
{ "name": "Egypt", "code": "EG" },
|
|
161
|
+
{ "name": "El Salvador", "code": "SV" },
|
|
162
|
+
{ "name": "Equatorial Guinea", "code": "GQ" },
|
|
163
|
+
{ "name": "Eritrea", "code": "ER" },
|
|
164
|
+
{ "name": "Estonia", "code": "EE" },
|
|
165
|
+
{ "name": "Ethiopia", "code": "ET" },
|
|
166
|
+
{ "name": "Fiji", "code": "FJ" },
|
|
167
|
+
{ "name": "Finland", "code": "FI" },
|
|
168
|
+
{ "name": "France", "code": "FR" },
|
|
169
|
+
{ "name": "Gabon", "code": "GA" },
|
|
170
|
+
{ "name": "Gambia", "code": "GM" },
|
|
171
|
+
{ "name": "Georgia", "code": "GE" },
|
|
172
|
+
{ "name": "Germany", "code": "DE" },
|
|
173
|
+
{ "name": "Ghana", "code": "GH" },
|
|
174
|
+
{ "name": "Greece", "code": "GR" },
|
|
175
|
+
{ "name": "Grenada", "code": "GD" },
|
|
176
|
+
{ "name": "Guatemala", "code": "GT" },
|
|
177
|
+
{ "name": "Guinea", "code": "GN" },
|
|
178
|
+
{ "name": "Guyana", "code": "GY" },
|
|
179
|
+
{ "name": "Haiti", "code": "HT" },
|
|
180
|
+
{ "name": "Honduras", "code": "HN" },
|
|
181
|
+
{ "name": "Hungary", "code": "HU" },
|
|
182
|
+
{ "name": "Iceland", "code": "IS" },
|
|
183
|
+
{ "name": "India", "code": "IN" },
|
|
184
|
+
{ "name": "Indonesia", "code": "ID" },
|
|
185
|
+
{ "name": "Iran", "code": "IR" },
|
|
186
|
+
{ "name": "Iraq", "code": "IQ" },
|
|
187
|
+
{ "name": "Ireland", "code": "IE" },
|
|
188
|
+
{ "name": "Israel", "code": "IL" },
|
|
189
|
+
{ "name": "Italy", "code": "IT" },
|
|
190
|
+
{ "name": "Jamaica", "code": "JM" },
|
|
191
|
+
{ "name": "Japan", "code": "JP" },
|
|
192
|
+
{ "name": "Jordan", "code": "JO" },
|
|
193
|
+
{ "name": "Kazakhstan", "code": "KZ" },
|
|
194
|
+
{ "name": "Kenya", "code": "KE" },
|
|
195
|
+
{ "name": "Korea, South", "code": "KR" },
|
|
196
|
+
{ "name": "Kuwait", "code": "KW" },
|
|
197
|
+
{ "name": "Laos", "code": "LA" },
|
|
198
|
+
{ "name": "Latvia", "code": "LV" },
|
|
199
|
+
{ "name": "Lebanon", "code": "LB" },
|
|
200
|
+
{ "name": "Libya", "code": "LY" },
|
|
201
|
+
{ "name": "Lithuania", "code": "LT" },
|
|
202
|
+
{ "name": "Luxembourg", "code": "LU" },
|
|
203
|
+
{ "name": "Malaysia", "code": "MY" },
|
|
204
|
+
{ "name": "Maldives", "code": "MV" },
|
|
205
|
+
{ "name": "Mexico", "code": "MX" },
|
|
206
|
+
{ "name": "Monaco", "code": "MC" },
|
|
207
|
+
{ "name": "Morocco", "code": "MA" },
|
|
208
|
+
{ "name": "Nepal", "code": "NP" },
|
|
209
|
+
{ "name": "Netherlands", "code": "NL" },
|
|
210
|
+
{ "name": "New Zealand", "code": "NZ" },
|
|
211
|
+
{ "name": "Nigeria", "code": "NG" },
|
|
212
|
+
{ "name": "Norway", "code": "NO" },
|
|
213
|
+
{ "name": "Oman", "code": "OM" },
|
|
214
|
+
{ "name": "Pakistan", "code": "PK" },
|
|
215
|
+
{ "name": "Panama", "code": "PA" },
|
|
216
|
+
{ "name": "Peru", "code": "PE" },
|
|
217
|
+
{ "name": "Philippines", "code": "PH" },
|
|
218
|
+
{ "name": "Poland", "code": "PL" },
|
|
219
|
+
{ "name": "Portugal", "code": "PT" },
|
|
220
|
+
{ "name": "Qatar", "code": "QA" },
|
|
221
|
+
{ "name": "Romania", "code": "RO" },
|
|
222
|
+
{ "name": "Russia", "code": "RU" },
|
|
223
|
+
{ "name": "Saudi Arabia", "code": "SA" },
|
|
224
|
+
{ "name": "Singapore", "code": "SG" },
|
|
225
|
+
{ "name": "South Africa", "code": "ZA" },
|
|
226
|
+
{ "name": "Spain", "code": "ES" },
|
|
227
|
+
{ "name": "Sri Lanka", "code": "LK" },
|
|
228
|
+
{ "name": "Sweden", "code": "SE" },
|
|
229
|
+
{ "name": "Switzerland", "code": "CH" },
|
|
230
|
+
{ "name": "Thailand", "code": "TH" },
|
|
231
|
+
{ "name": "Turkey", "code": "TR" },
|
|
232
|
+
{ "name": "Ukraine", "code": "UA" },
|
|
233
|
+
{ "name": "United Arab Emirates", "code": "AE" },
|
|
234
|
+
{ "name": "United Kingdom", "code": "GB" },
|
|
235
|
+
{ "name": "United States", "code": "US" },
|
|
236
|
+
{ "name": "Vietnam", "code": "VN" },
|
|
237
|
+
{ "name": "Zimbabwe", "code": "ZW" }
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
const DEFAULT_THEME = {
|
|
241
|
+
bg: "#ffffff",
|
|
242
|
+
text: "#111827",
|
|
243
|
+
primary: "#2563eb",
|
|
244
|
+
border: "#e5e7eb",
|
|
245
|
+
inputBg: "#ffffff",
|
|
246
|
+
placeholder: "#9ca3af"
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const COUNTRY_PHONE_MAP = {
|
|
250
|
+
US: "+1",
|
|
251
|
+
IN: "+91"
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
function getStates(country) {
|
|
255
|
+
if (country === "IN") return INDIAN_STATES;
|
|
256
|
+
if (country === "US") return USA_STATES;
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function loadScript(url) {
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
if (document.querySelector(`script[src="${url}"]`)) {
|
|
263
|
+
resolve();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const script = document.createElement('script');
|
|
267
|
+
script.src = url;
|
|
268
|
+
script.onload = resolve;
|
|
269
|
+
script.onerror = reject;
|
|
270
|
+
document.head.appendChild(script);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export class SwirepayCheckout extends HTMLElement {
|
|
275
|
+
constructor() {
|
|
276
|
+
super();
|
|
277
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
278
|
+
this.events = new EventEmitter();
|
|
279
|
+
this.theme = DEFAULT_THEME;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async connectedCallback() {
|
|
283
|
+
this.amount = parseInt(this.getAttribute("amount")) || 0;
|
|
284
|
+
this.test = this.getAttribute("mode") === "test";
|
|
285
|
+
this.amount = parseInt(this.getAttribute("amount")) || 0;
|
|
286
|
+
this.currencyCode = this.getAttribute("currencyCode");
|
|
287
|
+
this.apiKey = this.getAttribute("api-key");
|
|
288
|
+
await Promise.all([
|
|
289
|
+
loadScript(SDK_CONFIG.elliptic),
|
|
290
|
+
loadScript(SDK_CONFIG.crypto)
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
this.render();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async encryptData(data, serverPublicKey, clientPrivateKey, keyId) {
|
|
297
|
+
try {
|
|
298
|
+
const EC = window.elliptic.ec;
|
|
299
|
+
const ec = new EC('p256');
|
|
300
|
+
const publicKeyBytes = atob(serverPublicKey);
|
|
301
|
+
let keyDataStart = -1;
|
|
302
|
+
for (let i = 0; i < publicKeyBytes.length - 4; i++) {
|
|
303
|
+
if (publicKeyBytes.charCodeAt(i) === 0x03 &&
|
|
304
|
+
publicKeyBytes.charCodeAt(i + 1) === 0x42 &&
|
|
305
|
+
publicKeyBytes.charCodeAt(i + 2) === 0x00 &&
|
|
306
|
+
publicKeyBytes.charCodeAt(i + 3) === 0x04) {
|
|
307
|
+
keyDataStart = i + 4; // Skip the 03 42 00 04 header
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (keyDataStart === -1) {
|
|
313
|
+
throw new Error('Could not find public key data in DER format');
|
|
314
|
+
}
|
|
315
|
+
const keyData = publicKeyBytes.slice(keyDataStart, keyDataStart + 64);
|
|
316
|
+
|
|
317
|
+
if (keyData.length !== 64) {
|
|
318
|
+
throw new Error(`Invalid key data length: ${keyData.length}, expected 64`);
|
|
319
|
+
}
|
|
320
|
+
const publicKeyHex = '04' + Array.from(keyData)
|
|
321
|
+
.map(byte => byte.charCodeAt(0).toString(16).padStart(2, '0'))
|
|
322
|
+
.join('');
|
|
323
|
+
const serverPubKey = ec.keyFromPublic(publicKeyHex, 'hex');
|
|
324
|
+
const sharedSecret = clientPrivateKey.derive(serverPubKey.getPublic());
|
|
325
|
+
const sharedSecretBytes = sharedSecret.toArray();
|
|
326
|
+
const sharedSecretHex = sharedSecretBytes.map(byte =>
|
|
327
|
+
(byte < 0 ? byte + 256 : byte).toString(16).padStart(2, '0')
|
|
328
|
+
).join('');
|
|
329
|
+
const aesKeyHash = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(sharedSecretHex));
|
|
330
|
+
const aesKey = aesKeyHash.toString(CryptoJS.enc.Hex);
|
|
331
|
+
const iv = CryptoJS.lib.WordArray.random(16);
|
|
332
|
+
const key = CryptoJS.enc.Hex.parse(aesKey);
|
|
333
|
+
const encrypted = CryptoJS.AES.encrypt(data, key, {
|
|
334
|
+
iv: iv,
|
|
335
|
+
mode: CryptoJS.mode.CBC,
|
|
336
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
encryptedData: encrypted.toString(),
|
|
341
|
+
keyId: keyId,
|
|
342
|
+
iv: iv.toString(CryptoJS.enc.Base64),
|
|
343
|
+
};
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('Error encrypting data:', error);
|
|
346
|
+
throw new Error('Failed to encrypt data');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getEndpoints() {
|
|
351
|
+
if (this.test) {
|
|
352
|
+
return { gateway: 'https://staging-backend.swirepay.com' };
|
|
353
|
+
}
|
|
354
|
+
return { gateway: 'https://api.swirepay.com' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async createCustomer(payload) {
|
|
358
|
+
const gateway = this.getEndpoints().gateway;
|
|
359
|
+
let url = `${gateway}/v3/customer`;
|
|
360
|
+
const response = await fetch(url, {
|
|
361
|
+
method: 'POST',
|
|
362
|
+
headers: {
|
|
363
|
+
'x-enc-public-key': payload?.publicKey,
|
|
364
|
+
'x-enc-key-id': payload?.keyId,
|
|
365
|
+
'x-enc-iv': payload?.iv,
|
|
366
|
+
'Content-Type': 'application/json',
|
|
367
|
+
'x-api-key': this.apiKey,
|
|
368
|
+
},
|
|
369
|
+
body: JSON.stringify({ data: payload?.data })
|
|
370
|
+
})
|
|
371
|
+
return await response.json();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async addPaymentMethod(payload) {
|
|
375
|
+
const gateway = this.getEndpoints().gateway;
|
|
376
|
+
let url = `${gateway}/v3/payment-method`;
|
|
377
|
+
const response = await fetch(url, {
|
|
378
|
+
method: 'POST',
|
|
379
|
+
headers: {
|
|
380
|
+
'x-enc-public-key': payload?.publicKey,
|
|
381
|
+
'x-enc-key-id': payload?.keyId,
|
|
382
|
+
'x-enc-iv': payload?.iv,
|
|
383
|
+
'Content-Type': 'application/json',
|
|
384
|
+
'x-api-key': this.apiKey,
|
|
385
|
+
},
|
|
386
|
+
body: JSON.stringify({ data: payload?.data })
|
|
387
|
+
})
|
|
388
|
+
return await response.json();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async getServerEncription() {
|
|
392
|
+
try {
|
|
393
|
+
const gateway = this.getEndpoints().gateway;
|
|
394
|
+
const response = await fetch(`${gateway}/v1/encryption/session`, {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
});
|
|
397
|
+
const responseData = await response.json();
|
|
398
|
+
return responseData?.entity;
|
|
399
|
+
} catch (err) {
|
|
400
|
+
console.error("Failed to get encryption session:", err);
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async generateClientKeyPair() {
|
|
406
|
+
try {
|
|
407
|
+
if (!window.elliptic) {
|
|
408
|
+
throw new Error("Elliptic failed to load");
|
|
409
|
+
}
|
|
410
|
+
const EC = window.elliptic.ec;
|
|
411
|
+
const ec = new EC("p256");
|
|
412
|
+
const keyPair = ec.genKeyPair();
|
|
413
|
+
const publicKey = keyPair.getPublic('hex');
|
|
414
|
+
const privateKey = keyPair;
|
|
415
|
+
return { privateKey, publicKey };
|
|
416
|
+
} catch (err) {
|
|
417
|
+
console.error("Elliptic load error:", err);
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
onSuccess(cb) { this.events.on("success", cb); }
|
|
423
|
+
onError(cb) { this.events.on("error", cb); }
|
|
424
|
+
|
|
425
|
+
open(options = {}) {
|
|
426
|
+
this.theme = { ...DEFAULT_THEME, ...(options.theme || {}) };
|
|
427
|
+
this.render();
|
|
428
|
+
this.shadow.getElementById("overlay").style.display = "flex";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
close() {
|
|
432
|
+
this.shadow.getElementById("overlay").style.display = "none";
|
|
433
|
+
}
|
|
434
|
+
formatCard(v) {
|
|
435
|
+
return v.replace(/\D/g, "").replace(/(.{4})/g, "$1 ").trim();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
formatExpiry(v) {
|
|
439
|
+
return v.replace(/\D/g, "").replace(/(\d{2})(\d{1,2})/, "$1/$2");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
formatPhone(v) {
|
|
443
|
+
return v.replace(/\D/g, "").slice(0, 10);
|
|
444
|
+
}
|
|
445
|
+
handleCountryChange() {
|
|
446
|
+
const country = this.shadow.getElementById("country").value;
|
|
447
|
+
const phoneCodeEl = this.shadow.getElementById("phone-code");
|
|
448
|
+
const stateEl = this.shadow.getElementById("state");
|
|
449
|
+
phoneCodeEl.value = COUNTRY_PHONE_MAP[country] || "";
|
|
450
|
+
const states = getStates(country);
|
|
451
|
+
stateEl.innerHTML = states.map(
|
|
452
|
+
s => `<option value="${s.code}">${s.name}</option>`
|
|
453
|
+
).join("");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
validate() {
|
|
457
|
+
const get = id => this.shadow.getElementById(id)?.value?.trim();
|
|
458
|
+
|
|
459
|
+
const name = get("name");
|
|
460
|
+
const email = get("email");
|
|
461
|
+
const phone = get("phone");
|
|
462
|
+
const code = get("phone-code");
|
|
463
|
+
const zip = get("zip");
|
|
464
|
+
const country = get("country");
|
|
465
|
+
|
|
466
|
+
const cardName = get("card-name");
|
|
467
|
+
const cardNumber = get("card")?.replace(/\s/g, "");
|
|
468
|
+
const expiry = get("expiry");
|
|
469
|
+
const cvv = get("cvv");
|
|
470
|
+
const street = get("street");
|
|
471
|
+
if (!name) return "Name required";
|
|
472
|
+
if (!email) return "Email required";
|
|
473
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "Invalid email address";
|
|
474
|
+
if (!cardName) return "Card holder name required";
|
|
475
|
+
if (!cardNumber || !/^\d{12,19}$/.test(cardNumber)) {
|
|
476
|
+
return "Invalid card number";
|
|
477
|
+
}
|
|
478
|
+
if (!expiry || !/^\d{2}\/\d{2}$/.test(expiry)) {
|
|
479
|
+
return "Invalid expiry format (MM/YY)";
|
|
480
|
+
} else {
|
|
481
|
+
const [mm, yy] = expiry.split("/").map(Number);
|
|
482
|
+
if (mm < 1 || mm > 12) return "Invalid expiry month";
|
|
483
|
+
const now = new Date();
|
|
484
|
+
const currentYear = now.getFullYear() % 100;
|
|
485
|
+
const currentMonth = now.getMonth() + 1;
|
|
486
|
+
|
|
487
|
+
if (yy < currentYear || (yy === currentYear && mm < currentMonth)) {
|
|
488
|
+
return "Card expired";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!cvv || !/^\d{3,4}$/.test(cvv)) {
|
|
492
|
+
return "Invalid CVV";
|
|
493
|
+
}
|
|
494
|
+
if (!street) return "Street address required";
|
|
495
|
+
if (phone && !isValidPhoneNumber(code + phone)) {
|
|
496
|
+
return "Invalid phone";
|
|
497
|
+
}
|
|
498
|
+
if (zip && postalCodes.validate(country, zip) !== true) {
|
|
499
|
+
return "Invalid ZIP";
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async handleSubmit(e) {
|
|
506
|
+
e.preventDefault();
|
|
507
|
+
|
|
508
|
+
const payButton = this.shadow.getElementById("form").querySelector(".btn");
|
|
509
|
+
payButton.disabled = true;
|
|
510
|
+
payButton.textContent = "Processing...";
|
|
511
|
+
|
|
512
|
+
const err = this.validate();
|
|
513
|
+
if (err) {
|
|
514
|
+
alert(err);
|
|
515
|
+
payButton.disabled = false;
|
|
516
|
+
payButton.textContent = `Pay $${(this.amount / 100).toFixed(2)}`;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const values = await this.getServerEncription();
|
|
522
|
+
if (values?.public_key) {
|
|
523
|
+
const keys = await this.generateClientKeyPair();
|
|
524
|
+
if (keys?.privateKey) {
|
|
525
|
+
const getVal = id => this.shadow.getElementById(id)?.value?.trim();
|
|
526
|
+
|
|
527
|
+
const phoneCode = getVal("phone-code") || "";
|
|
528
|
+
const phone = getVal("phone");
|
|
529
|
+
|
|
530
|
+
const cardNumber = getVal("card")?.replace(/\s/g, "");
|
|
531
|
+
const expiry = getVal("expiry")?.split("/") || [];
|
|
532
|
+
const cvv = getVal("cvv");
|
|
533
|
+
const cardHolderName = getVal("card-name");
|
|
534
|
+
|
|
535
|
+
const customer = {
|
|
536
|
+
name: getVal("name"),
|
|
537
|
+
email: getVal("email"),
|
|
538
|
+
phone: phone ? `${phoneCode}${phone}` : null
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const address = {
|
|
542
|
+
street: getVal("street"),
|
|
543
|
+
city: getVal("city"),
|
|
544
|
+
state: getVal("state"),
|
|
545
|
+
countryCode: getVal("country"),
|
|
546
|
+
postalCode: getVal("zip")
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const encriptedData = await this.encryptData(JSON.stringify(customer), values.public_key, keys.privateKey, values.key_id);
|
|
550
|
+
const payload = {
|
|
551
|
+
publicKey: keys?.publicKey,
|
|
552
|
+
keyId: encriptedData?.keyId,
|
|
553
|
+
iv: encriptedData?.iv,
|
|
554
|
+
data: encriptedData?.encryptedData,
|
|
555
|
+
}
|
|
556
|
+
const newCustomer = await this.createCustomer(payload);
|
|
557
|
+
const customerGid = newCustomer.entity?.gid;
|
|
558
|
+
|
|
559
|
+
const pmPayload = {
|
|
560
|
+
type: 'CARD',
|
|
561
|
+
card: {
|
|
562
|
+
number: cardNumber,
|
|
563
|
+
expiryMonth: parseInt(expiry[0]),
|
|
564
|
+
expiryYear: 2000 + parseInt(expiry[1]),
|
|
565
|
+
cvv: cvv,
|
|
566
|
+
name: cardHolderName
|
|
567
|
+
},
|
|
568
|
+
postalCode: address?.zip,
|
|
569
|
+
paymentMethodBillingAddress: address,
|
|
570
|
+
customerGid: customerGid
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const encriptedData1 = await this.encryptData(JSON.stringify(pmPayload), values.public_key, keys.privateKey, values.key_id);
|
|
574
|
+
const payload1 = {
|
|
575
|
+
publicKey: keys?.publicKey,
|
|
576
|
+
keyId: encriptedData1?.keyId,
|
|
577
|
+
iv: encriptedData1?.iv,
|
|
578
|
+
data: encriptedData1?.encryptedData,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const result = await this.addPaymentMethod(payload1);
|
|
582
|
+
const gid = result?.entity?.gid;
|
|
583
|
+
|
|
584
|
+
const sessionData = {
|
|
585
|
+
amount: this.amount,
|
|
586
|
+
currencyCode: this.currencyCode,
|
|
587
|
+
paymentMethodGid: gid,
|
|
588
|
+
paymentMethodType: ['CARD'],
|
|
589
|
+
statementDescriptor: 'Card Payment',
|
|
590
|
+
confirmMethod: 'AUTOMATIC',
|
|
591
|
+
captureMethod: 'AUTOMATIC',
|
|
592
|
+
};
|
|
593
|
+
const encriptedData4 = await this.encryptData(JSON.stringify(sessionData), values.public_key, keys.privateKey, values.key_id);
|
|
594
|
+
const payload2 = {
|
|
595
|
+
publicKey: keys?.publicKey,
|
|
596
|
+
keyId: encriptedData4?.keyId,
|
|
597
|
+
iv: encriptedData4?.iv,
|
|
598
|
+
data: encriptedData4?.encryptedData,
|
|
599
|
+
}
|
|
600
|
+
const gateway = this.getEndpoints().gateway;
|
|
601
|
+
const url = `${gateway}/v3/payment-session`;
|
|
602
|
+
const res = await fetch(url, {
|
|
603
|
+
method: 'POST',
|
|
604
|
+
headers: {
|
|
605
|
+
'x-enc-public-key': payload2?.publicKey,
|
|
606
|
+
'x-enc-key-id': payload2?.keyId,
|
|
607
|
+
'x-enc-iv': payload2?.iv,
|
|
608
|
+
'Content-Type': 'application/json',
|
|
609
|
+
'x-api-key': this.apiKey,
|
|
610
|
+
},
|
|
611
|
+
body: JSON.stringify({ data: payload2?.data })
|
|
612
|
+
});
|
|
613
|
+
const resData = await res.json();
|
|
614
|
+
if (resData?.entity?.errorCode === null){
|
|
615
|
+
this.events.emit("success", resData);
|
|
616
|
+
this.close();
|
|
617
|
+
} else if (resData?.entity?.errorCode !== null){
|
|
618
|
+
console.error("Payment Error:", resData?.entity?.errorDescription);
|
|
619
|
+
this.events.emit("error", resData?.entity?.errorDescription);
|
|
620
|
+
alert(resData?.entity?.errorDescription || "Payment failed");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.error("Payment Error:", error);
|
|
626
|
+
this.events.emit("error", error);
|
|
627
|
+
alert(error.message || "Payment failed");
|
|
628
|
+
} finally {
|
|
629
|
+
payButton.disabled = false;
|
|
630
|
+
payButton.textContent = `Pay $${(this.amount / 100).toFixed(2)}`;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
render() {
|
|
634
|
+
const t = this.theme;
|
|
635
|
+
const amountText = (this.amount / 100).toFixed(2);
|
|
636
|
+
|
|
637
|
+
this.shadow.innerHTML = `
|
|
638
|
+
<style>
|
|
639
|
+
:host {
|
|
640
|
+
--bg:${t.bg};
|
|
641
|
+
--text:${t.text};
|
|
642
|
+
--primary:${t.primary};
|
|
643
|
+
--border:${t.border};
|
|
644
|
+
--input-bg:${t.inputBg};
|
|
645
|
+
--placeholder:${t.placeholder};
|
|
646
|
+
color: var(--text);
|
|
647
|
+
font-family: sans-serif;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.overlay {
|
|
651
|
+
display:none;
|
|
652
|
+
position:fixed;
|
|
653
|
+
inset:0;
|
|
654
|
+
background:rgba(0,0,0,0.6);
|
|
655
|
+
justify-content:center;
|
|
656
|
+
align-items:center;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.modal {
|
|
660
|
+
width:420px;
|
|
661
|
+
background:var(--bg);
|
|
662
|
+
color:var(--text);
|
|
663
|
+
border-radius:16px;
|
|
664
|
+
padding:20px;
|
|
665
|
+
font-family:sans-serif;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
input, select {
|
|
669
|
+
width:100%;
|
|
670
|
+
padding:10px;
|
|
671
|
+
margin:6px 0;
|
|
672
|
+
border:1px solid var(--border);
|
|
673
|
+
background: white;
|
|
674
|
+
color: black;
|
|
675
|
+
border-radius:8px;
|
|
676
|
+
outline:none;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
input::placeholder {
|
|
680
|
+
color: var(--placeholder);
|
|
681
|
+
}
|
|
682
|
+
select {
|
|
683
|
+
color: var(--text);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
input {
|
|
687
|
+
background: #ffffff;
|
|
688
|
+
color: #000000;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
input::placeholder {
|
|
692
|
+
color: #9ca3af !important;
|
|
693
|
+
opacity: 1 !important;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
select {
|
|
697
|
+
width:100%;
|
|
698
|
+
padding:10px;
|
|
699
|
+
margin:6px 0;
|
|
700
|
+
border:1px solid var(--border);
|
|
701
|
+
border-radius:8px;
|
|
702
|
+
|
|
703
|
+
background-color: #ffffff !important;
|
|
704
|
+
color: #000000 !important;
|
|
705
|
+
|
|
706
|
+
-webkit-text-fill-color: #000000; /* Chrome fix */
|
|
707
|
+
appearance: none; /* removes default weird styling */
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
select option {
|
|
711
|
+
background: #ffffff;
|
|
712
|
+
color: #000000;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.row { display:flex; gap:10px; }
|
|
716
|
+
|
|
717
|
+
.phone-code { width:70px; }
|
|
718
|
+
|
|
719
|
+
.btn {
|
|
720
|
+
background:var(--primary);
|
|
721
|
+
color:#fff;
|
|
722
|
+
padding:12px;
|
|
723
|
+
border:none;
|
|
724
|
+
border-radius:10px;
|
|
725
|
+
cursor:pointer;
|
|
726
|
+
width:100%;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.close {
|
|
730
|
+
float:right;
|
|
731
|
+
cursor:pointer;
|
|
732
|
+
}
|
|
733
|
+
</style>
|
|
734
|
+
|
|
735
|
+
<div class="overlay" id="overlay">
|
|
736
|
+
<div class="modal">
|
|
737
|
+
<span class="close" id="close">✕</span>
|
|
738
|
+
|
|
739
|
+
<h3>Pay with Card</h3>
|
|
740
|
+
|
|
741
|
+
<form id="form">
|
|
742
|
+
<input id="name" placeholder="Name"/>
|
|
743
|
+
<input id="email" placeholder="Email"/>
|
|
744
|
+
|
|
745
|
+
<div class="row">
|
|
746
|
+
<input class="phone-code" id="phone-code" value="+1"/>
|
|
747
|
+
<input id="phone" placeholder="Phone"/>
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
<input id="card-name" placeholder="Card Holder Name"/>
|
|
751
|
+
<input id="card" placeholder="Card Number"/>
|
|
752
|
+
|
|
753
|
+
<div class="row">
|
|
754
|
+
<input id="expiry" placeholder="MM/YY"/>
|
|
755
|
+
<input id="cvv" type="password" placeholder="CVV"/>
|
|
756
|
+
</div>
|
|
757
|
+
|
|
758
|
+
<input id="street" placeholder="Street Address"/>
|
|
759
|
+
<input id="city" placeholder="City"/>
|
|
760
|
+
|
|
761
|
+
<!-- STATE FIRST -->
|
|
762
|
+
<select id="state"></select>
|
|
763
|
+
|
|
764
|
+
<!-- COUNTRY AFTER -->
|
|
765
|
+
<select id="country">
|
|
766
|
+
${COUNTRIES_LIST.map(c =>
|
|
767
|
+
`<option value="${c.code}" ${c.code === "US" ? "selected" : ""}>
|
|
768
|
+
${c.name}
|
|
769
|
+
</option>`
|
|
770
|
+
).join("")}
|
|
771
|
+
</select>
|
|
772
|
+
|
|
773
|
+
<input id="zip" placeholder="ZIP Code"/>
|
|
774
|
+
|
|
775
|
+
<button class="btn">Pay $${amountText}</button>
|
|
776
|
+
</form>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
`;
|
|
780
|
+
|
|
781
|
+
const $ = id => this.shadow.getElementById(id);
|
|
782
|
+
|
|
783
|
+
$("close").onclick = () => this.close();
|
|
784
|
+
$("form").onsubmit = e => this.handleSubmit(e);
|
|
785
|
+
|
|
786
|
+
$("country").onchange = () => this.handleCountryChange();
|
|
787
|
+
|
|
788
|
+
const countryEl = $("country");
|
|
789
|
+
countryEl.value = "US";
|
|
790
|
+
this.handleCountryChange();
|
|
791
|
+
|
|
792
|
+
$("card").oninput = e => e.target.value = this.formatCard(e.target.value);
|
|
793
|
+
$("expiry").oninput = e => e.target.value = this.formatExpiry(e.target.value);
|
|
794
|
+
$("phone").oninput = e => e.target.value = this.formatPhone(e.target.value);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!customElements.get("swirepay-checkout")) {
|
|
798
|
+
customElements.define("swirepay-checkout", SwirepayCheckout);
|
|
799
|
+
}
|