@@ -87,6 +87,100 @@ jQuery( function ( $ ) {
87
87
$ ( '.variations_form' ) . length > 0 ||
88
88
$ ( '.wc-bookings-booking-form' ) . length > 0 ;
89
89
90
+ const resolveClickEvent = ( event , options ) => {
91
+ const clickOptions = {
92
+ lineItems : useLegacyCartEndpoints
93
+ ? normalizeLineItems ( options . displayItems )
94
+ : options . displayItems ,
95
+ emailRequired : true ,
96
+ shippingAddressRequired : options . requestShipping ,
97
+ phoneNumberRequired : options . requestPhone ,
98
+ shippingRates : options . shippingRates ,
99
+ } ;
100
+
101
+ return event . resolve ( clickOptions ) ;
102
+ } ;
103
+
104
+ const handleProductPageECEButtonClick = async ( event , options ) => {
105
+ const addToCartButton = document . querySelector (
106
+ '.single_add_to_cart_button'
107
+ ) ;
108
+
109
+ // First check if product can be added to cart.
110
+ if ( addToCartButton . classList . contains ( 'disabled' ) ) {
111
+ const defaultMessage = __ (
112
+ 'Please select your product options before proceeding.' ,
113
+ 'woocommerce-gateway-stripe'
114
+ ) ;
115
+ let message ;
116
+ if (
117
+ addToCartButton . classList . contains (
118
+ 'wc-variation-is-unavailable'
119
+ )
120
+ ) {
121
+ message =
122
+ getAddToCartVariationParams ( 'i18n_unavailable_text' ) ||
123
+ __ (
124
+ 'Sorry, this product is unavailable. Please choose a different combination.' ,
125
+ 'woocommerce-gateway-stripe'
126
+ ) ;
127
+ }
128
+
129
+ // eslint-disable-next-line no-alert
130
+ window . alert ( message || defaultMessage ) ;
131
+ return ;
132
+ }
133
+
134
+ if ( wcStripeECEError ) {
135
+ // eslint-disable-next-line no-alert
136
+ window . alert ( wcStripeECEError ) ;
137
+ return ;
138
+ }
139
+
140
+ // Stripe requires event.resolve() to be called within 1s of the click event.
141
+ // Here, we enforce a timeout for the addToCart operation. If the operation
142
+ // takes longer, we will call event.resolve() immediately,
143
+ // and wait for the addToCart operation to finish after.
144
+ const addToCartPromise = wcStripeECE . addToCart ( ) ;
145
+ const timeout = new Promise ( ( resolve ) =>
146
+ setTimeout ( ( ) => {
147
+ resolve ( 'timeout' ) ;
148
+ } , 700 )
149
+ ) ;
150
+ const result = await Promise . race ( [ addToCartPromise , timeout ] ) ;
151
+ if ( result === 'timeout' ) {
152
+ // Immediately resolve the click event to avoid the 1s timeout.
153
+ resolveClickEvent ( event , options ) ;
154
+
155
+ // Wait for the addToCart operation to finish, checking
156
+ // that the product was successfully added to the cart.
157
+ wcStripeECE . isAddToCartSuccessful = false ;
158
+ const response = await addToCartPromise ;
159
+ const isAddToCartSuccessful = response ?. items_count > 0 ;
160
+ const isLegacyAddToCartSuccessful = response ?. result === 'success' ;
161
+ if ( isAddToCartSuccessful || isLegacyAddToCartSuccessful ) {
162
+ wcStripeECE . isAddToCartSuccessful = true ;
163
+ }
164
+
165
+ return ;
166
+ }
167
+
168
+ wcStripeECE . isAddToCartSuccessful = true ;
169
+ return resolveClickEvent ( event , options ) ;
170
+ } ;
171
+
172
+ const handleProductPageShippingAddressChange = async (
173
+ event ,
174
+ elements
175
+ ) => {
176
+ if ( wcStripeECE . isAddToCartSuccessful === false ) {
177
+ // wait 1s for the item to be added to the cart before proceeding
178
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
179
+ }
180
+
181
+ return shippingAddressChangeHandler ( api , event , elements ) ;
182
+ } ;
183
+
90
184
const wcStripeECE = {
91
185
createButton : ( elements , options ) =>
92
186
elements . create ( 'expressCheckout' , options ) ,
@@ -254,66 +348,27 @@ jQuery( function ( $ ) {
254
348
) ;
255
349
}
256
350
257
- if ( getExpressCheckoutData ( 'is_product_page' ) ) {
258
- const addToCartButton = $ ( '.single_add_to_cart_button' ) ;
259
-
260
- // First check if product can be added to cart.
261
- if ( addToCartButton . is ( '.disabled' ) ) {
262
- if (
263
- addToCartButton . is ( '.wc-variation-is-unavailable' )
264
- ) {
265
- // eslint-disable-next-line no-alert
266
- window . alert (
267
- // eslint-disable-next-line camelcase
268
- getAddToCartVariationParams (
269
- 'i18n_unavailable_text'
270
- ) ||
271
- __ (
272
- 'Sorry, this product is unavailable. Please choose a different combination.' ,
273
- 'woocommerce-gateway-stripe'
274
- )
275
- ) ;
276
- } else {
277
- // eslint-disable-next-line no-alert
278
- window . alert (
279
- __ (
280
- 'Please select your product options before proceeding.' ,
281
- 'woocommerce-gateway-stripe'
282
- )
283
- ) ;
284
- }
285
- return ;
286
- }
287
-
288
- if ( wcStripeECEError ) {
289
- // eslint-disable-next-line no-alert
290
- window . alert ( wcStripeECEError ) ;
291
- return ;
292
- }
293
-
294
- // Add products to the cart if everything is right.
295
- await wcStripeECE . addToCart ( ) ;
351
+ if ( ! getExpressCheckoutData ( 'is_product_page' ) ) {
352
+ onClickHandler ( event ) ;
353
+ return resolveClickEvent ( event , options ) ;
296
354
}
297
355
298
- const clickOptions = {
299
- lineItems : useLegacyCartEndpoints
300
- ? normalizeLineItems ( options . displayItems )
301
- : options . displayItems ,
302
- emailRequired : true ,
303
- shippingAddressRequired : options . requestShipping ,
304
- phoneNumberRequired : options . requestPhone ,
305
- shippingRates : options . shippingRates ,
306
- } ;
307
-
308
- onClickHandler ( event ) ;
309
- event . resolve ( clickOptions ) ;
356
+ return await handleProductPageECEButtonClick ( event , options ) ;
310
357
} ) ;
311
358
312
- eceButton . on (
313
- 'shippingaddresschange' ,
314
- async ( event ) =>
315
- await shippingAddressChangeHandler ( api , event , elements )
316
- ) ;
359
+ eceButton . on ( 'shippingaddresschange' , async ( event ) => {
360
+ if ( getExpressCheckoutData ( 'is_product_page' ) ) {
361
+ return await handleProductPageShippingAddressChange (
362
+ event ,
363
+ elements
364
+ ) ;
365
+ }
366
+ return await shippingAddressChangeHandler (
367
+ api ,
368
+ event ,
369
+ elements
370
+ ) ;
371
+ } ) ;
317
372
318
373
eceButton . on (
319
374
'shippingratechange' ,
@@ -322,6 +377,24 @@ jQuery( function ( $ ) {
322
377
) ;
323
378
324
379
eceButton . on ( 'confirm' , async ( event ) => {
380
+ if (
381
+ getExpressCheckoutData ( 'is_product_page' ) &&
382
+ wcStripeECE . isAddToCartSuccessful === false
383
+ ) {
384
+ // wait 1s for the item to be added to the cart before proceeding
385
+ await new Promise ( ( resolve ) =>
386
+ setTimeout ( resolve , 1000 )
387
+ ) ;
388
+
389
+ if ( wcStripeECE . isAddToCartSuccessful === false ) {
390
+ const message = __ (
391
+ 'There was an error adding the product to the cart.' ,
392
+ 'woocommerce-gateway-stripe'
393
+ ) ;
394
+ return wcStripeECE . abortPayment ( event , message ) ;
395
+ }
396
+ }
397
+
325
398
const order = options . order ? options . order : 0 ;
326
399
const orderDetails = options . orderDetails ?? { } ;
327
400
return await onConfirmHandler ( {
@@ -530,7 +603,7 @@ jQuery( function ( $ ) {
530
603
*
531
604
* @return {Promise } Promise for the request to the server.
532
605
*/
533
- addToCart : ( ) => {
606
+ addToCart : async ( ) => {
534
607
let productId = $ ( '.single_add_to_cart_button' ) . val ( ) ;
535
608
536
609
const data = {
@@ -573,6 +646,12 @@ jQuery( function ( $ ) {
573
646
data . id = productId ;
574
647
data . variation = [ ] ;
575
648
649
+ // Clear the cart, so items that are currently in it
650
+ // do not interfere with computed totals.
651
+ // Use the non-StoreAPI method as it is faster; Stripe requires
652
+ // the click event to be resolved within 1 second.
653
+ await api . expressCheckoutEmptyCartLegacy ( { } ) ;
654
+
576
655
return api . expressCheckoutAddToCart ( data ) ;
577
656
} ,
578
657
0 commit comments