Completed
Push — master ( 3aed8d...86871a )
by Roy
02:09
created

wcStripePaymentRequest.updateShippingOptions   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 29
rs 8.8571
1
/*global jQuery, wcStripePaymentRequestParams, PaymentRequest, Stripe, Promise */
2
/*jshint es3: false */
3
/*jshint devel: true */
4
(function( $ ) {
5
6
	/**
7
	 * WooCommerce Stripe PaymentRequest class.
8
	 *
9
	 * @type {Object}
10
	 */
11
	var wcStripePaymentRequest = {
12
13
		/**
14
		 * Initialize class events.
15
		 */
16
		init: function() {
17
			var self = this;
18
19
			if ( self.hasPaymentRequestSupport() ) {
20
				$( document.body )
21
					.on( 'click', '.cart_totals a.checkout-button', self.initPaymentRequest );
22
			}
23
		},
24
25
		/**
26
		 * Check if browser support PaymentRequest class and if is under HTTPS.
27
		 *
28
		 * @return {Bool}
29
		 */
30
		hasPaymentRequestSupport: function() {
31
			return window.PaymentRequest && 'https:' === window.location.protocol;
32
		},
33
34
		/**
35
		 * Get Stripe supported methods.
36
		 *
37
		 * @return {Array}
38
		 */
39
		getSupportedMethods: function() {
40
			return [
41
				'amex',
42
				'diners',
43
				'discover',
44
				'jcb',
45
				'mastercard',
46
				'visa'
47
			];
48
		},
49
50
		/**
51
		 * Get WC AJAX endpoint URL.
52
		 *
53
		 * @param  {String} endpoint Endpoint.
54
		 * @return {String}
55
		 */
56
		getAjaxURL: function( endpoint ) {
57
			return wcStripePaymentRequestParams.ajax_url
58
				.toString()
59
				.replace( '%%endpoint%%', 'wc_stripe_' + endpoint );
60
		},
61
62
		/**
63
		 * Initialize the PaymentRequest.
64
		 *
65
		 * @param {Object} evt DOM events.
66
		 */
67
		initPaymentRequest: function( evt ) {
68
			evt.preventDefault();
69
			var self = wcStripePaymentRequest;
70
			var data = {
71
				security: wcStripePaymentRequestParams.nonce.payment
72
			};
73
74
			$.ajax({
75
				type:    'POST',
76
				data:    data,
77
				url:     self.getAjaxURL( 'get_cart_details' ),
78
				success: function( response ) {
79
					self.openPaymentRequest( response );
80
				}
81
			});
82
		},
83
84
		/**
85
		 * Open Payment Request modal.
86
		 *
87
		 * @param {Object} details Payment request details.
88
		 */
89
		openPaymentRequest: function( details ) {
90
			var self = this;
91
92
			// PaymentRequest options.
93
			var supportedInstruments = [{
94
				supportedMethods: self.getSupportedMethods()
95
			}];
96
			var options = {
97
				requestPayerPhone: true,
98
				requestPayerEmail: true
99
			};
100
			if ( details.shipping_required ) {
101
				options.requestShipping = true;
102
			}
103
			var paymentDetails = details.order_data;
104
105
			// Init PaymentRequest.
106
			var request = new PaymentRequest( supportedInstruments, paymentDetails, options );
107
108
			// Set up shipping.
109
			request.addEventListener( 'shippingaddresschange', function( evt ) {
110
				evt.updateWith( new Promise( function( resolve, reject ) {
111
					self.updateShippingOptions( paymentDetails, request.shippingAddress, resolve, reject );
112
				}));
113
			});
114
			request.addEventListener( 'shippingoptionchange', function( evt ) {
115
				evt.updateWith( new Promise( function( resolve, reject ) {
116
					self.updateShippingDetails( paymentDetails, request.shippingOption, resolve, reject );
117
				}));
118
			});
119
120
			// Open Payment Request UI.
121
			request.show().then( function( payment ) {
122
				self.processPayment( payment );
123
			})
124
			.catch( function( err ) {
125
				console.error( err );
126
			});
127
		},
128
129
		/**
130
		 * Update shipping options.
131
		 *
132
		 * @param {Object}         details Payment details.
133
		 * @param {PaymentAddress} address Shipping address.
134
		 * @param {Function}       resolve The callback to invoke with updated line items and shipping options.
135
		 * @param {Function}	   reject  The callback to invoke in case of failure.
136
		 */
137
		updateShippingOptions: function( details, address, resolve, reject ) {
138
			var self = this;
139
			var data = {
140
				security:  wcStripePaymentRequestParams.nonce.shipping,
141
				country:   address.country,
142
				state:     address.region,
143
				postcode:  address.postalCode,
144
				city:      address.city,
145
				address:   typeof address.addressLine[0] === 'undefined' ? '' : address.addressLine[0],
146
				address_2: typeof address.addressLine[1] === 'undefined' ? '' : address.addressLine[1]
147
			};
148
149
			$.ajax({
150
				type:    'POST',
151
				data:    data,
152
				url:     self.getAjaxURL( 'get_shipping_options' ),
153
				success: function( response ) {
154
					details.shippingOptions = response;
155
					if ( details.shippingOptions.length == 1 ) {
156
						// The sole shipping option was auto-selected. Update the details
157
						// (including the total).
158
						self.updateShippingDetails(
159
								details, details.shippingOptions[0].id, resolve, reject );
160
					} else {
161
						resolve( details );
162
					}
163
				}
164
			});
165
		},
166
167
		/**
168
		 * Updates the shipping price and the total based on the shipping option.
169
		 *
170
		 * @param {Object}   details        The line items and shipping options.
171
		 * @param {String}   shippingOption User's preferred shipping option to use for shipping price calculations.
172
		 * @param {Function} resolve        The callback to invoke with updated line items and shipping options.
173
		 * @param {Function} reject         The callback to invoke in case of failure.
174
		 */
175
		updateShippingDetails: function( details, shippingOption, resolve, reject ) {
176
			var self     = this;
177
			var selected = null;
178
			var data     = {
179
				security:  wcStripePaymentRequestParams.nonce.update_shipping,
180
				shipping_method: [
181
					shippingOption
182
				]
183
			};
184
185
			$.ajax({
186
				type:    'POST',
187
				data:    data,
188
				url:     self.getAjaxURL( 'update_shipping_method' ),
189
				success: function( response ) {
190
					details.shippingOptions.forEach( function( value, index ) {
191
						if ( value.id === shippingOption ) {
192
							selected = index;
193
							value.selected = true;
194
							details.total.amount.value = parseFloat( response.total );
195
196
							if ( response.items ) {
197
								details.displayItems = response.items;
198
							}
199
						} else {
200
							value.selected = false;
201
						}
202
					});
203
204
					if ( null === selected ) {
205
						reject( wcStripePaymentRequestParams.i18n.unknown_shipping.toString().replace( '[option]', shippingOption ) );
206
					}
207
208
					resolve( details );
209
				}
210
			});
211
		},
212
213
		/**
214
		 * Get order data.
215
		 *
216
		 * @param {PaymentResponse} payment Payment Response instance.
217
		 *
218
		 * @return {Object}
219
		 */
220
		getOrderData: function( payment ) {
221
			var billing  = payment.details.billingAddress;
222
			var shipping = payment.shippingAddress;
223
			var data     = {
224
				_wpnonce:                  wcStripePaymentRequestParams.nonce.checkout,
225
				billing_first_name:        billing.recipient.split( ' ' ).slice( 0, 1 ).join( ' ' ),
226
				billing_last_name:         billing.recipient.split( ' ' ).slice( 1 ).join( ' ' ),
227
				billing_company:           billing.organization,
228
				billing_email:             payment.payerEmail,
229
				billing_phone:             payment.payerPhone,
230
				billing_country:           billing.country,
231
				billing_address_1:         typeof billing.addressLine[0] === 'undefined' ? '' : billing.addressLine[0],
232
				billing_address_2:         typeof billing.addressLine[1] === 'undefined' ? '' : billing.addressLine[1],
233
				billing_city:              billing.city,
234
				billing_state:             billing.region,
235
				billing_postcode:          billing.postalCode,
236
				shipping_first_name:       '',
237
				shipping_last_name:        '',
238
				shipping_company:          '',
239
				shipping_country:          '',
240
				shipping_address_1:        '',
241
				shipping_address_2:        '',
242
				shipping_city:             '',
243
				shipping_state:            '',
244
				shipping_postcode:         '',
245
				shipping_method:           [ payment.shippingOption ],
246
				order_comments:            '',
247
				payment_method:            'stripe',
248
				// 'wc-stripe-payment-token': 'new',
249
				stripe_token:              '',
250
			};
251
252
			if ( shipping ) {
253
				data.shipping_first_name = shipping.recipient.split( ' ' ).slice( 0, 1 ).join( ' ' );
254
				data.shipping_last_name  = shipping.recipient.split( ' ' ).slice( 1 ).join( ' ' );
255
				data.shipping_company    = shipping.organization;
256
				data.shipping_country    = shipping.country;
257
				data.shipping_address_1  = typeof shipping.addressLine[0] === 'undefined' ? '' : shipping.addressLine[0];
258
				data.shipping_address_2  = typeof shipping.addressLine[1] === 'undefined' ? '' : shipping.addressLine[1];
259
				data.shipping_city       = shipping.city;
260
				data.shipping_state      = shipping.region;
261
				data.shipping_postcode   = shipping.postalCode;
262
			}
263
264
			return data;
265
		},
266
267
		/**
268
		 * Get credit card data.
269
		 *
270
		 * @param {PaymentResponse} payment Payment Response instance.
271
		 *
272
		 * @return {Object}
273
		 */
274
		getCardData: function( payment ) {
275
			var billing = payment.details.billingAddress;
276
			var data    = {
277
				number:          payment.details.cardNumber,
278
				cvc:             payment.details.cardSecurityCode,
279
				exp_month:       parseInt( payment.details.expiryMonth, 10 ) || 0,
280
				exp_year:        parseInt( payment.details.expiryYear, 10 ) || 0,
281
				name:            billing.recipient,
282
				address_line1:   typeof billing.addressLine[0] === 'undefined' ? '' : billing.addressLine[0],
283
				address_line2:   typeof billing.addressLine[1] === 'undefined' ? '' : billing.addressLine[1],
284
				address_state:   billing.region,
285
				address_city:    billing.city,
286
				address_zip:     billing.postalCode,
287
				address_country: billing.country
288
			};
289
290
			return data;
291
		},
292
293
		/**
294
		 * Generate error message HTML.
295
		 *
296
		 * @param  {String} message Error message.
297
		 * @return {Object}
298
		 */
299
		getErrorMessageHTML: function( message ) {
300
			return $( '<div class="woocommerce-error" />' ).text( message );
301
		},
302
303
		/**
304
		 * Abort payment and display error messages.
305
		 *
306
		 * @param {PaymentResponse} payment Payment response instance.
307
		 * @param {String}          message Error message to display.
308
		 */
309
		abortPayment: function( payment, message ) {
310
			payment.complete( '' ).then( function() {
311
				var $form = $( '.shop_table.cart' ).closest( 'form' );
312
				$( '.woocommerce-error' ).remove();
313
				$form.before( message );
314
				$( 'html, body' ).animate({
315
					scrollTop: $form.prev( '.woocommerce-error' ).offset().top
316
				}, 600 );
317
			})
318
			.catch( function( err ) {
319
				console.error( err );
320
			});
321
		},
322
323
		/**
324
		 * Complete payment.
325
		 *
326
		 * @param {PaymentResponse} payment Payment response instance.
327
		 * @param {String}          url     Order thank you page URL.
328
		 */
329
		completePayment: function( payment, url ) {
330
			payment.complete( 'success' ).then( function() {
331
				// Success, then redirect to the Thank You page.
332
				window.location = url;
333
			})
334
			.catch( function( err ) {
335
				console.error( err );
336
			});
337
		},
338
339
		/**
340
		 * Process payment.
341
		 *
342
		 * @param {PaymentResponse} payment Payment response instance.
343
		 */
344
		processPayment: function( payment ) {
345
			var self      = this;
346
			var orderData = self.getOrderData( payment );
347
			var cardData  = self.getCardData( payment );
348
349
			Stripe.setPublishableKey( wcStripePaymentRequestParams.stripe.key );
350
			Stripe.createToken( cardData, function( status, response ) {
351
				if ( response.error ) {
352
					self.abortPayment( payment, self.getErrorMessageHTML( response.error.message ) );
353
				} else {
354
					// Check if we allow prepaid cards.
355
					if ( 'no' === wcStripePaymentRequestParams.stripe.allow_prepaid_card && 'prepaid' === response.card.funding ) {
356
						self.abortPayment( payment, self.getErrorMessageHTML( wcStripePaymentRequestParams.i18n.no_prepaid_card ) );
357
					} else {
358
						// Token contains id, last4, and card type.
359
						orderData.stripe_token = response.id;
360
361
						$.ajax({
362
							type:     'POST',
363
							data:     orderData,
364
							dataType: 'json',
365
							url:      self.getAjaxURL( 'create_order' ),
366
							success: function( response ) {
367
								if ( 'success' === response.result ) {
368
									self.completePayment( payment, response.redirect );
369
								} else {
370
									self.abortPayment( payment, response.messages );
371
								}
372
							},
373
							complete: function( jqXHR, textStatus ) {
374
								if ( 'success' !== textStatus ) {
375
									console.error( jqXHR );
376
								}
377
							}
378
						});
379
					}
380
				}
381
			});
382
		}
383
	};
384
385
	wcStripePaymentRequest.init();
386
387
})( jQuery );
388