@salla.sa/applepay 3.0.0-alpha.0 → 3.0.0-beta.1
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/deploy.json +8 -0
- package/package.json +8 -9
- package/src/DetectOS.js +77 -68
- package/src/index.js +224 -75
- package/src/utils.js +119 -25
package/deploy.json
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salla.sa/applepay",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-beta.1",
|
|
4
4
|
"description": "Salla Apple Pay light",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"watch": "echo \"There is no watch!\"",
|
|
8
|
-
"build": "echo \"There is no build!\"",
|
|
9
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
-
},
|
|
11
6
|
"repository": {
|
|
12
7
|
"type": "git",
|
|
13
8
|
"url": "git+https://github.com/SallaApp/twilight.git"
|
|
@@ -24,7 +19,11 @@
|
|
|
24
19
|
"license": "UNLICENSED",
|
|
25
20
|
"homepage": "https://github.com/SallaApp/twilight#readme",
|
|
26
21
|
"dependencies": {
|
|
27
|
-
"axios": "^
|
|
22
|
+
"axios": "^1.10.0"
|
|
28
23
|
},
|
|
29
|
-
"
|
|
30
|
-
|
|
24
|
+
"scripts": {
|
|
25
|
+
"watch": "echo \"There is no watch!\"",
|
|
26
|
+
"build": "echo \"There is no build!\"",
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/DetectOS.js
CHANGED
|
@@ -1,75 +1,84 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
2
|
+
options: [],
|
|
3
|
+
header:
|
|
4
|
+
typeof navigator !== "undefined" && typeof window !== "undefined"
|
|
5
|
+
? [
|
|
6
|
+
navigator.platform,
|
|
7
|
+
navigator.userAgent,
|
|
8
|
+
navigator.appVersion,
|
|
9
|
+
navigator.vendor,
|
|
10
|
+
window.opera,
|
|
11
|
+
]
|
|
12
|
+
: [],
|
|
13
|
+
dataos: [
|
|
14
|
+
{ name: "Windows Phone", value: "Windows Phone", version: "OS" },
|
|
15
|
+
{ name: "Windows", value: "Win", version: "NT" },
|
|
16
|
+
{ name: "iPhone", value: "iPhone", version: "OS" },
|
|
17
|
+
{ name: "iPad", value: "iPad", version: "OS" },
|
|
18
|
+
{ name: "Kindle", value: "Silk", version: "Silk" },
|
|
19
|
+
{ name: "Android", value: "Android", version: "Android" },
|
|
20
|
+
{ name: "PlayBook", value: "PlayBook", version: "OS" },
|
|
21
|
+
{ name: "BlackBerry", value: "BlackBerry", version: "/" },
|
|
22
|
+
{ name: "Macintosh", value: "Mac", version: "OS X" },
|
|
23
|
+
{ name: "Linux", value: "Linux", version: "rv" },
|
|
24
|
+
{ name: "Palm", value: "Palm", version: "PalmOS" },
|
|
25
|
+
],
|
|
26
|
+
databrowser: [
|
|
27
|
+
{ name: "Chrome", value: "Chrome", version: "Chrome" },
|
|
28
|
+
{ name: "Firefox", value: "Firefox", version: "Firefox" },
|
|
29
|
+
{ name: "Safari", value: "Safari", version: "Version" },
|
|
30
|
+
{ name: "Internet Explorer", value: "MSIE", version: "MSIE" },
|
|
31
|
+
{ name: "Opera", value: "Opera", version: "Opera" },
|
|
32
|
+
{ name: "BlackBerry", value: "CLDC", version: "CLDC" },
|
|
33
|
+
{ name: "Mozilla", value: "Mozilla", version: "Mozilla" },
|
|
34
|
+
],
|
|
35
|
+
init: function () {
|
|
36
|
+
var agent = this.header.join(" "),
|
|
37
|
+
os = this.matchItem(agent, this.dataos),
|
|
38
|
+
browser = this.matchItem(agent, this.databrowser);
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
return { os: os, browser: browser };
|
|
41
|
+
},
|
|
42
|
+
matchItem: function (string, data) {
|
|
43
|
+
var i = 0,
|
|
44
|
+
j = 0,
|
|
45
|
+
html = "",
|
|
46
|
+
regex,
|
|
47
|
+
regexv,
|
|
48
|
+
match,
|
|
49
|
+
matches,
|
|
50
|
+
version;
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
version = '0';
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
name: data[i].name,
|
|
69
|
-
version: parseFloat(version)
|
|
70
|
-
};
|
|
52
|
+
for (i = 0; i < data.length; i += 1) {
|
|
53
|
+
regex = new RegExp(data[i].value, "i");
|
|
54
|
+
match = regex.test(string);
|
|
55
|
+
if (match) {
|
|
56
|
+
regexv = new RegExp(data[i].version + "[- /:;]([\\d._]+)", "i");
|
|
57
|
+
matches = string.match(regexv);
|
|
58
|
+
version = "";
|
|
59
|
+
if (matches) {
|
|
60
|
+
if (matches[1]) {
|
|
61
|
+
matches = matches[1];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (matches) {
|
|
65
|
+
matches = matches.split(/[._]+/);
|
|
66
|
+
for (j = 0; j < matches.length; j += 1) {
|
|
67
|
+
if (j === 0) {
|
|
68
|
+
version += matches[j] + ".";
|
|
69
|
+
} else {
|
|
70
|
+
version += matches[j];
|
|
71
71
|
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
version = "0";
|
|
72
75
|
}
|
|
73
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
name: data[i].name,
|
|
78
|
+
version: parseFloat(version),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
74
81
|
}
|
|
82
|
+
return { name: "unknown", version: 0 };
|
|
83
|
+
},
|
|
75
84
|
};
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import http from './http';
|
|
2
2
|
import DetectOS from './DetectOS';
|
|
3
|
-
import {
|
|
3
|
+
import { mutateShippingContact } from './utils';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// SSR-safe: only access window in browser environment
|
|
6
|
+
if (typeof window !== 'undefined') {
|
|
7
|
+
window.Salla = window.Salla || {};
|
|
8
|
+
window.Salla.Payments = window.Salla.Payments || {};
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Full Example
|
|
@@ -35,7 +38,9 @@ window.Salla.Payments = window.Salla.Payments || {};
|
|
|
35
38
|
* // }
|
|
36
39
|
* });
|
|
37
40
|
*/
|
|
38
|
-
|
|
41
|
+
|
|
42
|
+
// SSR-safe: define SallaApplePay as a stub during SSR, actual object in browser
|
|
43
|
+
const SallaApplePay = typeof window !== 'undefined' ? {
|
|
39
44
|
session: null,
|
|
40
45
|
detail: null,
|
|
41
46
|
address_id: null,
|
|
@@ -43,6 +48,9 @@ window.SallaApplePay = {
|
|
|
43
48
|
total: undefined,
|
|
44
49
|
request: undefined,
|
|
45
50
|
id: undefined,
|
|
51
|
+
countryCode: null,
|
|
52
|
+
totals: [],
|
|
53
|
+
shippingCompany: null,
|
|
46
54
|
init: function () {
|
|
47
55
|
document.removeEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession);
|
|
48
56
|
Salla.event.addEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession);
|
|
@@ -66,7 +74,7 @@ window.SallaApplePay = {
|
|
|
66
74
|
if (!SallaApplePay.detail.validateMerchant.onFailed) {
|
|
67
75
|
SallaApplePay.detail.validateMerchant.onFailed = (response) => {
|
|
68
76
|
salla.logger.log(JSON.stringify(response))
|
|
69
|
-
salla.api.handleErrorResponse(response);
|
|
77
|
+
salla.api.handleErrorResponse({response});
|
|
70
78
|
SallaApplePay.onCancel({}, response.data.error.message);
|
|
71
79
|
};
|
|
72
80
|
}
|
|
@@ -80,11 +88,11 @@ window.SallaApplePay = {
|
|
|
80
88
|
},
|
|
81
89
|
|
|
82
90
|
prepareLineItems: function () {
|
|
83
|
-
if(!SallaApplePay.detail?.items?.length){
|
|
91
|
+
if (!SallaApplePay.detail?.items?.length) {
|
|
84
92
|
SallaApplePay.detail.items = [
|
|
85
93
|
{
|
|
86
94
|
label: salla.lang.get('pages.cart.items_total'),
|
|
87
|
-
amount: SallaApplePay.detail.amount
|
|
95
|
+
amount: parseFloat(SallaApplePay.detail.amount).toString()
|
|
88
96
|
}
|
|
89
97
|
]
|
|
90
98
|
}
|
|
@@ -93,12 +101,16 @@ window.SallaApplePay = {
|
|
|
93
101
|
},
|
|
94
102
|
|
|
95
103
|
prepareTotal: function () {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
return {
|
|
105
|
+
// apple ask to use business name
|
|
106
|
+
label: window.location.hostname || 'Salla',
|
|
107
|
+
//label: salla.lang.get('pages.cart.final_total'),
|
|
108
|
+
amount: parseFloat(SallaApplePay.detail.amount).toString()
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
isPhysical() {
|
|
113
|
+
return SallaApplePay.detail?.requiredShippingContactFields?.includes('postalAddress');
|
|
102
114
|
},
|
|
103
115
|
|
|
104
116
|
startSession: async function (event) {
|
|
@@ -117,7 +129,7 @@ window.SallaApplePay = {
|
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
SallaApplePay.request = {
|
|
120
|
-
countryCode: 'SA',
|
|
132
|
+
countryCode: SallaApplePay.detail.countryCode || 'SA',
|
|
121
133
|
supportsCouponCode: true,
|
|
122
134
|
couponCode: '',
|
|
123
135
|
currencyCode: SallaApplePay.detail.currency || 'SAR',
|
|
@@ -141,30 +153,70 @@ window.SallaApplePay = {
|
|
|
141
153
|
SallaApplePay.session.onvalidatemerchant = SallaApplePay.onValidateMerchant;
|
|
142
154
|
SallaApplePay.session.onpaymentauthorized = SallaApplePay.onPaymentAuthorized;
|
|
143
155
|
SallaApplePay.session.oncancel = SallaApplePay.onCancel;
|
|
144
|
-
SallaApplePay.session.oncouponcodechanged = SallaApplePay.
|
|
145
|
-
SallaApplePay.session.onpaymentmethodselected = SallaApplePay.
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
SallaApplePay.session.oncouponcodechanged = SallaApplePay.onCouponCodeChanged;
|
|
157
|
+
SallaApplePay.session.onpaymentmethodselected = SallaApplePay.onPaymentMethodSelected;
|
|
158
|
+
|
|
159
|
+
// Hook between session creation and begin(). Return truthy to skip
|
|
160
|
+
// begin() (caller handles it later, e.g. after async work).
|
|
161
|
+
let skipBegin;
|
|
162
|
+
if (typeof SallaApplePay.detail.onStartedSession === 'function') {
|
|
163
|
+
skipBegin = SallaApplePay.detail.onStartedSession(SallaApplePay.session);
|
|
164
|
+
}
|
|
165
|
+
if (!skipBegin) {
|
|
166
|
+
SallaApplePay.session.begin();
|
|
167
|
+
}
|
|
148
168
|
},
|
|
149
|
-
async
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
onPaymentMethodSelected: async (event) => {
|
|
170
|
+
salla.logger.log('🍏 Pay: onPaymentMethodSelected', event);
|
|
171
|
+
|
|
172
|
+
// perform recalculate here only if digital product
|
|
173
|
+
// if physical recalculate performed with shipping company
|
|
174
|
+
if (!SallaApplePay.isPhysical()) {
|
|
175
|
+
try {
|
|
176
|
+
await SallaApplePay.recalculateTotal();
|
|
177
|
+
|
|
178
|
+
const updatedPaymentDetails = {
|
|
179
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
180
|
+
newLineItems: SallaApplePay.prepareLineItems()
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
salla.logger.log('🍏 Pay: completePaymentMethodSelection', updatedPaymentDetails);
|
|
184
|
+
|
|
185
|
+
SallaApplePay.session.completePaymentMethodSelection(updatedPaymentDetails);
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
salla.logger.warn('🍏 Pay: Failed recalculate total', error);
|
|
189
|
+
|
|
190
|
+
SallaApplePay.session.completePaymentMethodSelection({
|
|
191
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
192
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
193
|
+
status: SallaApplePay.session.STATUS_FAILURE,
|
|
194
|
+
errors: [new window.ApplePayError("unknown", undefined, error?.response?.data?.error?.message || error?.response?.data?.error?.code || 'Failed to recalculate total')]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
salla.logger.log('🍏 Pay: completePaymentMethodSelection', {
|
|
152
201
|
newTotal: SallaApplePay.prepareTotal(),
|
|
153
202
|
newLineItems: SallaApplePay.prepareLineItems(),
|
|
154
|
-
};
|
|
203
|
+
});
|
|
155
204
|
|
|
156
|
-
SallaApplePay.session.completePaymentMethodSelection(
|
|
205
|
+
SallaApplePay.session.completePaymentMethodSelection({
|
|
206
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
207
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
208
|
+
});
|
|
157
209
|
},
|
|
158
210
|
|
|
159
|
-
|
|
211
|
+
onCouponCodeChanged(event) {
|
|
160
212
|
Salla.event.dispatch('payments::apple-pay.coupon.change', event);
|
|
161
213
|
|
|
162
|
-
return
|
|
214
|
+
return http.post(SallaApplePay.detail.onCouponCodeChanged.url.replace('{id}', SallaApplePay.id), {
|
|
163
215
|
'coupon': event.couponCode,
|
|
164
216
|
'payment_method': 'apple_pay',
|
|
165
|
-
}, async ({data}) => {
|
|
166
|
-
if (typeof SallaApplePay.detail.
|
|
167
|
-
SallaApplePay.detail.
|
|
217
|
+
}, async ({ data }) => {
|
|
218
|
+
if (typeof SallaApplePay.detail.onCouponCodeChanged.onSuccess === 'function') {
|
|
219
|
+
SallaApplePay.detail.onCouponCodeChanged.onSuccess(data);
|
|
168
220
|
}
|
|
169
221
|
|
|
170
222
|
salla.log('🍏 Pay: Coupon applied success');
|
|
@@ -181,8 +233,8 @@ window.SallaApplePay = {
|
|
|
181
233
|
Salla.event.dispatch('payments::apple-pay.coupon.failed', response);
|
|
182
234
|
|
|
183
235
|
// SallaApplePay.abortSession();
|
|
184
|
-
if (typeof SallaApplePay.detail.
|
|
185
|
-
SallaApplePay.detail.
|
|
236
|
+
if (typeof SallaApplePay.detail.onCouponCodeChanged.onFailed === 'function') {
|
|
237
|
+
SallaApplePay.detail.onCouponCodeChanged.onFailed(response);
|
|
186
238
|
}
|
|
187
239
|
|
|
188
240
|
await SallaApplePay.recalculateTotal();
|
|
@@ -208,35 +260,80 @@ window.SallaApplePay = {
|
|
|
208
260
|
onPaymentAuthorized: async (event) => {
|
|
209
261
|
salla.logger.log('🍏 Pay: onPaymentAuthorized', event.payment);
|
|
210
262
|
|
|
211
|
-
//
|
|
212
|
-
|
|
263
|
+
// update guest details
|
|
264
|
+
try {
|
|
265
|
+
await mutateShippingContact(SallaApplePay, event.payment.shippingContact, true);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
salla.logger.error('🍏 Pay: Failed to update guest contact details', error);
|
|
268
|
+
|
|
269
|
+
const response = error?.response || error;
|
|
270
|
+
|
|
271
|
+
// Parse backend errors for shipping contact fields
|
|
272
|
+
const fields = response?.data?.error?.fields || {};
|
|
273
|
+
const errors = [];
|
|
274
|
+
|
|
275
|
+
// Map backend field errors to Apple Pay contact field errors
|
|
276
|
+
if (fields?.email && fields.email.length > 0) {
|
|
277
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'emailAddress', fields.email[0]));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (fields?.phone && fields.phone.length > 0) {
|
|
281
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'phoneNumber', fields.phone[0]));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (fields?.first_name && fields.first_name.length > 0) {
|
|
285
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', fields.first_name[0]));
|
|
286
|
+
} else if (fields?.last_name && fields.last_name.length > 0) {
|
|
287
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', fields.last_name[0]));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// If no specific field errors, use general error message
|
|
291
|
+
if (errors.length === 0) {
|
|
292
|
+
const errorMessage = response?.data?.error?.message || response?.data?.error?.code || 'Invalid shipping contact details';
|
|
293
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', errorMessage));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
Salla.event.dispatch('payments::apple-pay.authorized.failed', response);
|
|
297
|
+
|
|
298
|
+
if (typeof SallaApplePay.detail.authorized.onFailed === 'function') {
|
|
299
|
+
SallaApplePay.detail.authorized.onFailed(response);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Complete shipping contact selection with invalid contact status
|
|
303
|
+
SallaApplePay.session.completePayment({
|
|
304
|
+
status: ApplePaySession.STATUS_INVALID_SHIPPING_CONTACT,
|
|
305
|
+
errors: errors
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
213
310
|
|
|
214
311
|
Salla.event.dispatch('payments::apple-pay.authorized.init', event);
|
|
215
|
-
|
|
312
|
+
http.post(SallaApplePay.detail.authorized.url.replace('{id}', SallaApplePay.id), {
|
|
216
313
|
payment_method: 'apple_pay',
|
|
217
314
|
applepay_token: JSON.stringify(event.payment)
|
|
218
|
-
}, ({data}) => {
|
|
315
|
+
}, ({ data }) => {
|
|
219
316
|
Salla.event.dispatch('payments::apple-pay.authorized.success', data);
|
|
220
317
|
|
|
221
|
-
SallaApplePay.session.completePayment(ApplePaySession.STATUS_SUCCESS);
|
|
222
|
-
|
|
223
318
|
if (typeof SallaApplePay.detail.authorized.onSuccess === 'function') {
|
|
224
319
|
SallaApplePay.detail.authorized.onSuccess(data);
|
|
225
320
|
}
|
|
321
|
+
|
|
322
|
+
SallaApplePay.session.completePayment(ApplePaySession.STATUS_SUCCESS);
|
|
226
323
|
}, (error) => {
|
|
227
324
|
|
|
228
325
|
let response = error?.response;
|
|
229
326
|
|
|
230
327
|
Salla.event.dispatch('payments::apple-pay.authorized.failed', response);
|
|
231
328
|
|
|
232
|
-
SallaApplePay.session.completePayment({
|
|
233
|
-
status: ApplePaySession.STATUS_FAILURE,
|
|
234
|
-
errors: [new ApplePayError("unknown", undefined, response?.data?.error?.message || response?.data?.error?.code || 'Failed to parse authorized response')]
|
|
235
|
-
})
|
|
236
|
-
|
|
237
329
|
if (typeof SallaApplePay.detail.authorized.onFailed === 'function') {
|
|
238
330
|
SallaApplePay.detail.authorized.onFailed(response);
|
|
239
331
|
}
|
|
332
|
+
|
|
333
|
+
SallaApplePay.session.completePayment({
|
|
334
|
+
status: ApplePaySession.STATUS_FAILURE,
|
|
335
|
+
errors: [new window.ApplePayError("unknown", undefined, response?.data?.error?.message || response?.data?.error?.code || 'Failed to parse authorized response')]
|
|
336
|
+
})
|
|
240
337
|
});
|
|
241
338
|
},
|
|
242
339
|
|
|
@@ -251,7 +348,7 @@ window.SallaApplePay = {
|
|
|
251
348
|
Salla.event.dispatch('payments::apple-pay.validate-merchant.init', event);
|
|
252
349
|
|
|
253
350
|
// Post request to validate merchant
|
|
254
|
-
const { data } = await
|
|
351
|
+
const { data } = await http.post(SallaApplePay.detail.validateMerchant.url.replace('{id}', SallaApplePay.id), {
|
|
255
352
|
validation_url: event.validationURL
|
|
256
353
|
});
|
|
257
354
|
|
|
@@ -300,7 +397,7 @@ window.SallaApplePay = {
|
|
|
300
397
|
salla.logger.log('🍏 Pay: onShippingContactSelected', event.shippingContact);
|
|
301
398
|
|
|
302
399
|
// create address for shipping calculation
|
|
303
|
-
|
|
400
|
+
await mutateShippingContact(SallaApplePay, event.shippingContact);
|
|
304
401
|
},
|
|
305
402
|
|
|
306
403
|
/**
|
|
@@ -310,25 +407,54 @@ window.SallaApplePay = {
|
|
|
310
407
|
*
|
|
311
408
|
*/
|
|
312
409
|
onShippingMethodSelected: async (event) => {
|
|
313
|
-
salla.logger.log(event);
|
|
314
410
|
|
|
315
411
|
let shipping_ids = event.shippingMethod.identifier.split(',');
|
|
412
|
+
let shippingMethod = {
|
|
413
|
+
ship_id: shipping_ids[0],
|
|
414
|
+
private_ship_id: typeof shipping_ids[1] === 'undefined' ? null : shipping_ids[1],
|
|
415
|
+
type: typeof shipping_ids[2] === 'undefined' ? null : shipping_ids[2],
|
|
416
|
+
route_id: typeof shipping_ids[3] === 'undefined' ? null : shipping_ids[3],
|
|
417
|
+
};
|
|
316
418
|
|
|
317
|
-
|
|
318
|
-
|
|
419
|
+
salla.logger.log('🍏 Pay: onShippingMethodSelected', {
|
|
420
|
+
event,
|
|
421
|
+
previous: SallaApplePay.shippingCompany,
|
|
422
|
+
current: shippingMethod,
|
|
423
|
+
});
|
|
319
424
|
|
|
320
|
-
|
|
425
|
+
if (SallaApplePay.shouldUpdateShippingCompany(shippingMethod)) {
|
|
426
|
+
try {
|
|
427
|
+
|
|
428
|
+
await SallaApplePay.selectApplePayShippingMethod(shippingMethod);
|
|
429
|
+
|
|
430
|
+
await SallaApplePay.recalculateTotal();
|
|
431
|
+
|
|
432
|
+
} catch (error) {
|
|
433
|
+
salla.logger.warn('🍏 Pay: Failed set the shipping details to api', error);
|
|
434
|
+
|
|
435
|
+
// todo :: find a better handling for error without abort session
|
|
436
|
+
SallaApplePay.session.completeShippingMethodSelection({
|
|
437
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
438
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
439
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
440
|
+
errors: [
|
|
441
|
+
new window.ApplePayError('addressUnserviceable')
|
|
442
|
+
]
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
321
448
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
} catch (error) {
|
|
327
|
-
salla.logger.warn('🍏 Pay: Failed set the shipping details to api', error);
|
|
449
|
+
salla.logger.log('🍏 Pay: completeShippingMethodSelection', {
|
|
450
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
451
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
452
|
+
});
|
|
328
453
|
|
|
329
|
-
|
|
330
|
-
SallaApplePay.
|
|
331
|
-
|
|
454
|
+
SallaApplePay.session.completeShippingMethodSelection({
|
|
455
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
456
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
457
|
+
});
|
|
332
458
|
},
|
|
333
459
|
|
|
334
460
|
|
|
@@ -338,6 +464,10 @@ window.SallaApplePay = {
|
|
|
338
464
|
}
|
|
339
465
|
},
|
|
340
466
|
|
|
467
|
+
shouldUpdateShippingCompany(shippingCompany) {
|
|
468
|
+
return SallaApplePay.shippingCompany?.ship_id != shippingCompany?.ship_id || SallaApplePay.shippingCompany?.private_ship_id != shippingCompany?.private_ship_id
|
|
469
|
+
},
|
|
470
|
+
|
|
341
471
|
getApplePaySessionVersion: () => {
|
|
342
472
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
343
473
|
|
|
@@ -363,24 +493,32 @@ window.SallaApplePay = {
|
|
|
363
493
|
},
|
|
364
494
|
|
|
365
495
|
recalculateTotal: () => {
|
|
366
|
-
salla.logger.log('
|
|
496
|
+
salla.logger.log('🍏 Pay: recalculate total');
|
|
367
497
|
|
|
368
|
-
return
|
|
498
|
+
return http.get(SallaApplePay.detail.recalculateTotal.url.replace('{id}', SallaApplePay.id), ({ data }) => {
|
|
499
|
+
salla.logger.log('🍏 Pay: recalculate total success', data);
|
|
369
500
|
let cart = data.data.initial_data?.cart || data.data.cart;
|
|
501
|
+
let payments = data.data.initial_data?.payments || data.data.payments;
|
|
502
|
+
|
|
503
|
+
salla.logger.log('🍏 Pay: recalculate total success', {
|
|
504
|
+
cart,
|
|
505
|
+
payments
|
|
506
|
+
});
|
|
507
|
+
|
|
370
508
|
// todo :: enhance response from backend
|
|
371
|
-
SallaApplePay.detail.amount = cart.total;
|
|
509
|
+
SallaApplePay.detail.amount = payments?.gateways?.applePay?.supportedMultiCurrency ? cart.total_in_customer_currency : cart.total;
|
|
372
510
|
SallaApplePay.detail.items = (cart.totals || cart.items).map((item) => {
|
|
373
511
|
return {
|
|
374
512
|
label: item.title,
|
|
375
|
-
amount: item.amount === 'مجاني' ? 0 : item.amount.toString().replace('+', ''),
|
|
513
|
+
amount: (item.amount === 'مجاني' || item.amount === 'Free') ? 0 : item.amount.toString().replace('+', ''),
|
|
376
514
|
};
|
|
377
515
|
})
|
|
378
516
|
|
|
379
517
|
// lets remove last element (final total)
|
|
380
518
|
SallaApplePay.detail.items.pop();
|
|
519
|
+
SallaApplePay.totals = SallaApplePay.detail.items;
|
|
381
520
|
|
|
382
|
-
|
|
383
|
-
}).catch((error) => {
|
|
521
|
+
}, (error) => {
|
|
384
522
|
salla.logger.warn('🍏 Pay: recalculate total failed', error);
|
|
385
523
|
|
|
386
524
|
// general error
|
|
@@ -389,22 +527,26 @@ window.SallaApplePay = {
|
|
|
389
527
|
},
|
|
390
528
|
|
|
391
529
|
|
|
392
|
-
selectApplePayShippingMethod: (
|
|
393
|
-
salla.logger.log('🍏 Pay: select shipping method ',
|
|
530
|
+
selectApplePayShippingMethod: (shippingMethod) => {
|
|
531
|
+
salla.logger.log('🍏 Pay: select shipping method ', shippingMethod);
|
|
532
|
+
SallaApplePay.shippingCompany = shippingMethod;
|
|
394
533
|
|
|
395
|
-
|
|
534
|
+
const payload = {
|
|
396
535
|
address_id: SallaApplePay.address_id,
|
|
397
|
-
company_id:
|
|
398
|
-
private_company_id:
|
|
536
|
+
company_id: shippingMethod.ship_id,
|
|
537
|
+
private_company_id: shippingMethod.private_ship_id,
|
|
538
|
+
type: shippingMethod.type || undefined,
|
|
539
|
+
route_id: shippingMethod.route_id || undefined,
|
|
399
540
|
payment_method: 'apple_pay'
|
|
400
|
-
}
|
|
401
|
-
|
|
541
|
+
}
|
|
542
|
+
return http.post(SallaApplePay.detail.shippingMethodSelected.url.replace('{id}', SallaApplePay.id), payload, ({ data }) => {
|
|
543
|
+
if (typeof SallaApplePay.detail.shippingMethodSelected.onSuccess === 'function') {
|
|
402
544
|
SallaApplePay.detail.shippingMethodSelected.onSuccess(data);
|
|
403
545
|
}
|
|
404
546
|
|
|
405
547
|
// we don't have any data in this request, lets resolve the promise
|
|
406
548
|
return true;
|
|
407
|
-
}
|
|
549
|
+
}, (error) => {
|
|
408
550
|
salla.logger.warn('🍏 Pay: Set shipping method failed', error);
|
|
409
551
|
|
|
410
552
|
if (typeof SallaApplePay.detail.shippingMethodSelected.onFailed === 'function') {
|
|
@@ -427,15 +569,22 @@ window.SallaApplePay = {
|
|
|
427
569
|
'label': method.shipping_title,
|
|
428
570
|
'amount': method.enable_free_shipping ? 0 : method.ship_cost,
|
|
429
571
|
'detail': '',
|
|
430
|
-
'identifier': method.ship_id.toString() + (method.private_ship_id ? ',' + method.private_ship_id.toString() : '')
|
|
572
|
+
'identifier': method.ship_id.toString() + (method.private_ship_id ? ',' + method.private_ship_id.toString() : '') + (method.type ? ',' + method.type : '') + (method.route_id ? ',' + method.route_id : '')
|
|
431
573
|
}))
|
|
574
|
+
} : {};
|
|
575
|
+
|
|
576
|
+
// SSR-safe: export and assign to window in browser
|
|
577
|
+
if (typeof window !== 'undefined') {
|
|
578
|
+
window.SallaApplePay = SallaApplePay;
|
|
432
579
|
}
|
|
433
580
|
|
|
434
581
|
//applePay doesn't allow iframes
|
|
435
|
-
//
|
|
436
|
-
if (window.ApplePaySession?.canMakePayments()) {
|
|
582
|
+
// SSR-safe: only initialize in browser
|
|
583
|
+
if (typeof window !== 'undefined' && window.ApplePaySession?.canMakePayments()) {
|
|
437
584
|
SallaApplePay.init();
|
|
438
|
-
} else {
|
|
585
|
+
} else if (typeof document !== 'undefined') {
|
|
439
586
|
// You can hide the Apple Pay button easy with add data-show-if-apple-pay-supported to element like <div data-show-if-apple-pay-supported>
|
|
440
587
|
document.querySelectorAll('data-show-if-apple-pay-supported').forEach(element => element.style.display = 'none');
|
|
441
588
|
}
|
|
589
|
+
|
|
590
|
+
export default SallaApplePay;
|
package/src/utils.js
CHANGED
|
@@ -26,10 +26,55 @@ import http from "./http";
|
|
|
26
26
|
* @param {ApplePayPaymentContact} shippingContact
|
|
27
27
|
*
|
|
28
28
|
*/
|
|
29
|
-
export function
|
|
30
|
-
|
|
29
|
+
export async function mutateShippingContact(SallaApplePay, shippingContact, isAuthorized = false) {
|
|
30
|
+
salla.logger.log('🍏 Pay: mutateShippingContact called', shippingContact, isAuthorized);
|
|
31
31
|
|
|
32
|
-
if (!SallaApplePay.detail.requiredShippingContactFields) {
|
|
32
|
+
if (!SallaApplePay.detail.requiredShippingContactFields || SallaApplePay.detail.requiredShippingContactFields.length == 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isAuthorized) {
|
|
37
|
+
if (isGuestCheckout()) {
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
!shippingContact.emailAddress ||
|
|
41
|
+
!shippingContact.givenName ||
|
|
42
|
+
!shippingContact.familyName ||
|
|
43
|
+
!shippingContact.phoneNumber
|
|
44
|
+
) {
|
|
45
|
+
|
|
46
|
+
salla.logger.warn('🍏 Pay: Guest contact fields are required', shippingContact);
|
|
47
|
+
|
|
48
|
+
const errors = [];
|
|
49
|
+
if (!shippingContact.emailAddress) {
|
|
50
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'emailAddress', 'Email address is required'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!shippingContact.phoneNumber) {
|
|
54
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'phoneNumber', 'Phone number is required'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!shippingContact.givenName || !shippingContact.familyName) {
|
|
58
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', 'Name is required'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
SallaApplePay.session.completePayment({
|
|
62
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_CONTACT,
|
|
63
|
+
errors: errors
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await updateGuestContact(SallaApplePay, shippingContact);
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// if authorized and not guest checkout, do nothing address already added
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!SallaApplePay.detail.requiredShippingContactFields.includes('postalAddress')) {
|
|
33
78
|
return;
|
|
34
79
|
}
|
|
35
80
|
|
|
@@ -38,15 +83,13 @@ export function mutateShipmentAddress(SallaApplePay, shippingContact, isAuthoriz
|
|
|
38
83
|
{
|
|
39
84
|
'country': shippingContact.country,
|
|
40
85
|
'city': shippingContact.locality,
|
|
41
|
-
'local': shippingContact.subLocality,
|
|
86
|
+
'local': shippingContact.subLocality || shippingContact.administrativeArea || shippingContact.locality,
|
|
42
87
|
'description': shippingContact.subAdministrativeArea,
|
|
43
88
|
'street': shippingContact.addressLines?.join(", ") || shippingContact.administrativeArea,
|
|
44
89
|
'country_code': shippingContact.countryCode,
|
|
45
90
|
'postal_code': shippingContact.postalCode,
|
|
46
|
-
'is_authorized': isAuthorized
|
|
47
91
|
},
|
|
48
92
|
async ({ data }) => {
|
|
49
|
-
if (isAuthorized) { return }
|
|
50
93
|
if (typeof SallaApplePay.detail.shippingContactSelected.onSuccess === 'function') {
|
|
51
94
|
SallaApplePay.detail.shippingContactSelected.onSuccess(data);
|
|
52
95
|
}
|
|
@@ -57,25 +100,29 @@ export function mutateShipmentAddress(SallaApplePay, shippingContact, isAuthoriz
|
|
|
57
100
|
if (!SallaApplePay.shipping_methods || (SallaApplePay.shipping_methods && !SallaApplePay.shipping_methods.length)) {
|
|
58
101
|
salla.logger.warn('🍏 Pay: We dont found any supported methods', data);
|
|
59
102
|
|
|
60
|
-
|
|
103
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
61
104
|
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
62
105
|
errors: [
|
|
63
106
|
new window.ApplePayError('addressUnserviceable')
|
|
64
107
|
]
|
|
65
108
|
});
|
|
109
|
+
|
|
110
|
+
return
|
|
66
111
|
}
|
|
67
112
|
|
|
68
113
|
try {
|
|
69
|
-
await SallaApplePay.selectApplePayShippingMethod(SallaApplePay.shipping_methods[0]
|
|
114
|
+
await SallaApplePay.selectApplePayShippingMethod(SallaApplePay.shipping_methods[0]);
|
|
70
115
|
} catch (error) {
|
|
71
116
|
salla.logger.warn('Failed set the shipping details to api', error);
|
|
72
117
|
|
|
73
|
-
|
|
118
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
74
119
|
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
75
120
|
errors: [
|
|
76
121
|
new window.ApplePayError('addressUnserviceable')
|
|
77
122
|
]
|
|
78
123
|
});
|
|
124
|
+
|
|
125
|
+
return;
|
|
79
126
|
}
|
|
80
127
|
|
|
81
128
|
try {
|
|
@@ -83,20 +130,25 @@ export function mutateShipmentAddress(SallaApplePay, shippingContact, isAuthoriz
|
|
|
83
130
|
} catch (error) {
|
|
84
131
|
salla.logger.warn('🍏 Pay: Failed recalculate total', error);
|
|
85
132
|
|
|
86
|
-
|
|
133
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
87
134
|
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
88
135
|
errors: [
|
|
89
136
|
new window.ApplePayError('addressUnserviceable')
|
|
90
137
|
]
|
|
91
138
|
});
|
|
139
|
+
|
|
140
|
+
return;
|
|
92
141
|
}
|
|
93
142
|
|
|
94
|
-
|
|
143
|
+
const updatedShippingContactSelection = {
|
|
95
144
|
newTotal: SallaApplePay.prepareTotal(),
|
|
96
145
|
newLineItems: SallaApplePay.prepareLineItems(),
|
|
97
146
|
newShippingMethods: SallaApplePay.mappingShippingMethods(SallaApplePay.shipping_methods)
|
|
98
|
-
}
|
|
147
|
+
};
|
|
99
148
|
|
|
149
|
+
salla.logger.log('🍏 Pay: completeShippingContactSelection', updatedShippingContactSelection);
|
|
150
|
+
|
|
151
|
+
SallaApplePay.session.completeShippingContactSelection(updatedShippingContactSelection);
|
|
100
152
|
},
|
|
101
153
|
({ response }) => {
|
|
102
154
|
salla.logger.warn('🍏 Pay: Failed add address via api', response);
|
|
@@ -108,19 +160,11 @@ export function mutateShipmentAddress(SallaApplePay, shippingContact, isAuthoriz
|
|
|
108
160
|
// parse 422 errors
|
|
109
161
|
let fields = response?.data?.error?.fields;
|
|
110
162
|
|
|
111
|
-
let errors =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (fields?.city) {
|
|
118
|
-
errors.push(new window.ApplePayError('shippingContactInvalid', 'locality', fields?.city[0]))
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (fields?.country) {
|
|
122
|
-
errors.push(new window.ApplePayError('shippingContactInvalid', 'country', fields?.country[0]))
|
|
123
|
-
}
|
|
163
|
+
let errors = getApplePayErrors({
|
|
164
|
+
countryCode: fields?.country_code && fields.country_code.length > 0 ? fields.country_code[0] : null,
|
|
165
|
+
locality: fields?.city && fields.city.length > 0 ? fields.city[0] : null,
|
|
166
|
+
country: fields?.country && fields.country.length > 0 ? fields.country[0] : null,
|
|
167
|
+
});
|
|
124
168
|
|
|
125
169
|
if (errors.length === 0 && response?.data?.error?.message) {
|
|
126
170
|
errors.push(new window.ApplePayError('shippingContactInvalid', 'locality', response?.data?.error?.message))
|
|
@@ -135,3 +179,53 @@ export function mutateShipmentAddress(SallaApplePay, shippingContact, isAuthoriz
|
|
|
135
179
|
}
|
|
136
180
|
);
|
|
137
181
|
}
|
|
182
|
+
|
|
183
|
+
function isGuestCheckout() {
|
|
184
|
+
return salla.config.isGuest() && salla.config.get('store.features').includes('guest-checkout');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Update guest contact
|
|
189
|
+
*
|
|
190
|
+
* @param {SallaApplePay} SallaApplePay
|
|
191
|
+
* @param {ApplePayPaymentContact} shippingContact
|
|
192
|
+
*
|
|
193
|
+
*/
|
|
194
|
+
async function updateGuestContact(SallaApplePay, shippingContact) {
|
|
195
|
+
salla.logger.log('🍏 Pay: Updating guest contact', shippingContact);
|
|
196
|
+
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
http.post(
|
|
199
|
+
SallaApplePay.detail.guestContactSelected.url.replace('{id}', SallaApplePay.id),
|
|
200
|
+
{
|
|
201
|
+
'email': shippingContact.emailAddress || null,
|
|
202
|
+
'first_name': shippingContact.givenName || null,
|
|
203
|
+
'last_name': shippingContact.familyName || null,
|
|
204
|
+
'phone_number': shippingContact.phoneNumber || null,
|
|
205
|
+
'country_code': shippingContact.countryCode || null,
|
|
206
|
+
},
|
|
207
|
+
async ({ data }) => {
|
|
208
|
+
if (typeof SallaApplePay.detail.guestContactSelected?.onSuccess === 'function') {
|
|
209
|
+
SallaApplePay.detail.guestContactSelected.onSuccess(data);
|
|
210
|
+
}
|
|
211
|
+
resolve(data);
|
|
212
|
+
},
|
|
213
|
+
({ response }) => {
|
|
214
|
+
salla.logger.warn('🍏 Pay: Failed to update guest contact via api', response);
|
|
215
|
+
|
|
216
|
+
if (typeof SallaApplePay.detail.guestContactSelected?.onFailed === 'function') {
|
|
217
|
+
SallaApplePay.detail.guestContactSelected.onFailed(response);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Reject the promise so it can be caught in onPaymentAuthorized
|
|
221
|
+
reject({ response });
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getApplePayErrors(fields) {
|
|
228
|
+
return Object.entries(fields)
|
|
229
|
+
.filter(([field, messages]) => messages && messages.length > 0)
|
|
230
|
+
.map(([field, messages]) => new window.ApplePayError('shippingContactInvalid', field, messages[0]));
|
|
231
|
+
}
|