1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Stripe Payment Request API |
4
|
|
|
* Adds support for Apple Pay and Chrome Payment Request API buttons. |
5
|
|
|
* Utilizes the Stripe Payment Request Button to support checkout from the product detail and cart pages. |
6
|
|
|
* |
7
|
|
|
* @package WooCommerce_Stripe/Classes/Payment_Request |
8
|
|
|
* @since 4.0.0 |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
12
|
|
|
exit; |
13
|
|
|
} |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* WC_Stripe_Payment_Request class. |
17
|
|
|
*/ |
18
|
|
|
class WC_Stripe_Payment_Request { |
19
|
|
|
/** |
20
|
|
|
* Enabled. |
21
|
|
|
* |
22
|
|
|
* @var |
23
|
|
|
*/ |
24
|
|
|
public $stripe_settings; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Total label |
28
|
|
|
* |
29
|
|
|
* @var |
30
|
|
|
*/ |
31
|
|
|
public $total_label; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Key |
35
|
|
|
* |
36
|
|
|
* @var |
37
|
|
|
*/ |
38
|
|
|
public $publishable_key; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Key |
42
|
|
|
* |
43
|
|
|
* @var |
44
|
|
|
*/ |
45
|
|
|
public $secret_key; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Is test mode active? |
49
|
|
|
* |
50
|
|
|
* @var bool |
51
|
|
|
*/ |
52
|
|
|
public $testmode; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* This Instance. |
56
|
|
|
* |
57
|
|
|
* @var |
58
|
|
|
*/ |
59
|
|
|
private static $_this; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Initialize class actions. |
63
|
|
|
* |
64
|
|
|
* @since 3.0.0 |
65
|
|
|
* @version 4.0.0 |
66
|
|
|
*/ |
67
|
|
|
public function __construct() { |
68
|
|
|
self::$_this = $this; |
69
|
|
|
$this->stripe_settings = get_option( 'woocommerce_stripe_settings', array() ); |
70
|
|
|
$this->testmode = ( ! empty( $this->stripe_settings['testmode'] ) && 'yes' === $this->stripe_settings['testmode'] ) ? true : false; |
71
|
|
|
$this->publishable_key = ! empty( $this->stripe_settings['publishable_key'] ) ? $this->stripe_settings['publishable_key'] : ''; |
72
|
|
|
$this->secret_key = ! empty( $this->stripe_settings['secret_key'] ) ? $this->stripe_settings['secret_key'] : ''; |
73
|
|
|
$this->total_label = ! empty( $this->stripe_settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->stripe_settings['statement_descriptor'] ) : ''; |
74
|
|
|
|
75
|
|
|
if ( $this->testmode ) { |
76
|
|
|
$this->publishable_key = ! empty( $this->stripe_settings['test_publishable_key'] ) ? $this->stripe_settings['test_publishable_key'] : ''; |
77
|
|
|
$this->secret_key = ! empty( $this->stripe_settings['test_secret_key'] ) ? $this->stripe_settings['test_secret_key'] : ''; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
$this->total_label = str_replace( "'", '', $this->total_label ) . apply_filters( 'wc_stripe_payment_request_total_label_suffix', ' (via WooCommerce)' ); |
81
|
|
|
|
82
|
|
|
// Checks if Stripe Gateway is enabled. |
83
|
|
|
if ( empty( $this->stripe_settings ) || ( isset( $this->stripe_settings['enabled'] ) && 'yes' !== $this->stripe_settings['enabled'] ) ) { |
84
|
|
|
return; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Checks if Payment Request is enabled. |
88
|
|
|
if ( ! isset( $this->stripe_settings['payment_request'] ) || 'yes' !== $this->stripe_settings['payment_request'] ) { |
89
|
|
|
return; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// Don't load for change payment method page. |
93
|
|
|
if ( isset( $_GET['change_payment_method'] ) ) { |
94
|
|
|
return; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
add_action( 'template_redirect', array( $this, 'set_session' ) ); |
98
|
|
|
$this->init(); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Checks if keys are set and valid. |
103
|
|
|
* |
104
|
|
|
* @since 4.0.6 |
105
|
|
|
* @return boolean True if the keys are set *and* valid, false otherwise (for example, if keys are empty or the secret key was pasted as publishable key). |
106
|
|
|
*/ |
107
|
|
View Code Duplication |
public function are_keys_set() { |
|
|
|
|
108
|
|
|
// NOTE: updates to this function should be added to are_keys_set() |
109
|
|
|
// in includes/abstracts/abstract-wc-stripe-payment-gateway.php |
110
|
|
|
if ( $this->testmode ) { |
111
|
|
|
return preg_match( '/^pk_test_/', $this->publishable_key ) |
112
|
|
|
&& preg_match( '/^[rs]k_test_/', $this->secret_key ); |
113
|
|
|
} else { |
114
|
|
|
return preg_match( '/^pk_live_/', $this->publishable_key ) |
115
|
|
|
&& preg_match( '/^[rs]k_live_/', $this->secret_key ); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Get this instance. |
121
|
|
|
* |
122
|
|
|
* @since 4.0.6 |
123
|
|
|
* @return class |
124
|
|
|
*/ |
125
|
|
|
public static function instance() { |
126
|
|
|
return self::$_this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Sets the WC customer session if one is not set. |
131
|
|
|
* This is needed so nonces can be verified by AJAX Request. |
132
|
|
|
* |
133
|
|
|
* @since 4.0.0 |
134
|
|
|
* @return void |
135
|
|
|
*/ |
136
|
|
|
public function set_session() { |
137
|
|
|
if ( ! is_product() || ( isset( WC()->session ) && WC()->session->has_session() ) ) { |
138
|
|
|
return; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
WC()->session->set_customer_session_cookie( true ); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Initialize hooks. |
146
|
|
|
* |
147
|
|
|
* @since 4.0.0 |
148
|
|
|
* @version 4.0.0 |
149
|
|
|
* @return void |
150
|
|
|
*/ |
151
|
|
|
public function init() { |
152
|
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'scripts' ) ); |
153
|
|
|
|
154
|
|
|
add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_html' ), 1 ); |
155
|
|
|
add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_separator_html' ), 2 ); |
156
|
|
|
|
157
|
|
|
add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_html' ), 1 ); |
158
|
|
|
add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_separator_html' ), 2 ); |
159
|
|
|
|
160
|
|
|
add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_html' ), 1 ); |
161
|
|
|
add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_separator_html' ), 2 ); |
162
|
|
|
|
163
|
|
|
add_action( 'wc_ajax_wc_stripe_get_cart_details', array( $this, 'ajax_get_cart_details' ) ); |
164
|
|
|
add_action( 'wc_ajax_wc_stripe_get_shipping_options', array( $this, 'ajax_get_shipping_options' ) ); |
165
|
|
|
add_action( 'wc_ajax_wc_stripe_update_shipping_method', array( $this, 'ajax_update_shipping_method' ) ); |
166
|
|
|
add_action( 'wc_ajax_wc_stripe_create_order', array( $this, 'ajax_create_order' ) ); |
167
|
|
|
add_action( 'wc_ajax_wc_stripe_add_to_cart', array( $this, 'ajax_add_to_cart' ) ); |
168
|
|
|
add_action( 'wc_ajax_wc_stripe_get_selected_product_data', array( $this, 'ajax_get_selected_product_data' ) ); |
169
|
|
|
add_action( 'wc_ajax_wc_stripe_clear_cart', array( $this, 'ajax_clear_cart' ) ); |
170
|
|
|
add_action( 'wc_ajax_wc_stripe_log_errors', array( $this, 'ajax_log_errors' ) ); |
171
|
|
|
|
172
|
|
|
add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 ); |
173
|
|
|
add_filter( 'woocommerce_validate_postcode', array( $this, 'postal_code_validation' ), 10, 3 ); |
174
|
|
|
|
175
|
|
|
add_action( 'woocommerce_checkout_order_processed', array( $this, 'add_order_meta' ), 10, 2 ); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Gets the button type. |
180
|
|
|
* |
181
|
|
|
* @since 4.0.0 |
182
|
|
|
* @version 4.0.0 |
183
|
|
|
* @return string |
184
|
|
|
*/ |
185
|
|
|
public function get_button_type() { |
186
|
|
|
return isset( $this->stripe_settings['payment_request_button_type'] ) ? $this->stripe_settings['payment_request_button_type'] : 'default'; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Gets the button theme. |
191
|
|
|
* |
192
|
|
|
* @since 4.0.0 |
193
|
|
|
* @version 4.0.0 |
194
|
|
|
* @return string |
195
|
|
|
*/ |
196
|
|
|
public function get_button_theme() { |
197
|
|
|
return isset( $this->stripe_settings['payment_request_button_theme'] ) ? $this->stripe_settings['payment_request_button_theme'] : 'dark'; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Gets the button height. |
202
|
|
|
* |
203
|
|
|
* @since 4.0.0 |
204
|
|
|
* @version 4.0.0 |
205
|
|
|
* @return string |
206
|
|
|
*/ |
207
|
|
|
public function get_button_height() { |
208
|
|
|
return isset( $this->stripe_settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->stripe_settings['payment_request_button_height'] ) : '64'; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Checks if the button is branded. |
213
|
|
|
* |
214
|
|
|
* @since 4.4.0 |
215
|
|
|
* @version 4.4.0 |
216
|
|
|
* @return boolean |
217
|
|
|
*/ |
218
|
|
|
public function is_branded_button() { |
219
|
|
|
return 'branded' === $this->get_button_type(); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Gets the branded button type. |
224
|
|
|
* |
225
|
|
|
* @since 4.4.0 |
226
|
|
|
* @version 4.4.0 |
227
|
|
|
* @return string |
228
|
|
|
*/ |
229
|
|
|
public function get_button_branded_type() { |
230
|
|
|
return isset( $this->stripe_settings['payment_request_button_branded_type'] ) ? $this->stripe_settings['payment_request_button_branded_type'] : 'default'; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Checks if the button is custom. |
235
|
|
|
* |
236
|
|
|
* @since 4.4.0 |
237
|
|
|
* @version 4.4.0 |
238
|
|
|
* @return boolean |
239
|
|
|
*/ |
240
|
|
|
public function is_custom_button() { |
241
|
|
|
return 'custom' === $this->get_button_type(); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Returns custom button css selector. |
246
|
|
|
* |
247
|
|
|
* @since 4.4.0 |
248
|
|
|
* @version 4.4.0 |
249
|
|
|
* @return string |
250
|
|
|
*/ |
251
|
|
|
public function custom_button_selector() { |
252
|
|
|
return $this->is_custom_button() ? '#wc-stripe-custom-button' : ''; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Gets the custom button label. |
257
|
|
|
* |
258
|
|
|
* @since 4.4.0 |
259
|
|
|
* @version 4.4.0 |
260
|
|
|
* @return string |
261
|
|
|
*/ |
262
|
|
|
public function get_button_label() { |
263
|
|
|
return isset( $this->stripe_settings['payment_request_button_label'] ) ? $this->stripe_settings['payment_request_button_label'] : 'Buy now'; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Gets the product data for the currently viewed page |
268
|
|
|
* |
269
|
|
|
* @since 4.0.0 |
270
|
|
|
* @version 4.0.0 |
271
|
|
|
* @return mixed Returns false if not on a product page, the product information otherwise. |
272
|
|
|
*/ |
273
|
|
|
public function get_product_data() { |
274
|
|
|
if ( ! is_product() ) { |
275
|
|
|
return false; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
global $post; |
279
|
|
|
|
280
|
|
|
$product = wc_get_product( $post->ID ); |
281
|
|
|
|
282
|
|
|
if ( 'variable' === $product->get_type() ) { |
283
|
|
|
$attributes = wc_clean( wp_unslash( $_GET ) ); |
284
|
|
|
|
285
|
|
|
$data_store = WC_Data_Store::load( 'product' ); |
286
|
|
|
$variation_id = $data_store->find_matching_product_variation( $product, $attributes ); |
287
|
|
|
|
288
|
|
|
if ( ! empty( $variation_id ) ) { |
289
|
|
|
$product = wc_get_product( $variation_id ); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
$data = array(); |
294
|
|
|
$items = array(); |
295
|
|
|
|
296
|
|
|
$items[] = array( |
297
|
|
|
'label' => $product->get_name(), |
298
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $product->get_price() ), |
299
|
|
|
); |
300
|
|
|
|
301
|
|
View Code Duplication |
if ( wc_tax_enabled() ) { |
|
|
|
|
302
|
|
|
$items[] = array( |
303
|
|
|
'label' => __( 'Tax', 'woocommerce-gateway-stripe' ), |
304
|
|
|
'amount' => 0, |
305
|
|
|
'pending' => true, |
306
|
|
|
); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
View Code Duplication |
if ( wc_shipping_enabled() && $product->needs_shipping() ) { |
|
|
|
|
310
|
|
|
$items[] = array( |
311
|
|
|
'label' => __( 'Shipping', 'woocommerce-gateway-stripe' ), |
312
|
|
|
'amount' => 0, |
313
|
|
|
'pending' => true, |
314
|
|
|
); |
315
|
|
|
|
316
|
|
|
$data['shippingOptions'] = array( |
317
|
|
|
'id' => 'pending', |
318
|
|
|
'label' => __( 'Pending', 'woocommerce-gateway-stripe' ), |
319
|
|
|
'detail' => '', |
320
|
|
|
'amount' => 0, |
321
|
|
|
); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
$data['displayItems'] = $items; |
325
|
|
|
$data['total'] = array( |
326
|
|
|
'label' => apply_filters( 'wc_stripe_payment_request_total_label', $this->total_label ), |
327
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $product->get_price() ), |
328
|
|
|
'pending' => true, |
329
|
|
|
); |
330
|
|
|
|
331
|
|
|
$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() ); |
332
|
|
|
$data['currency'] = strtolower( get_woocommerce_currency() ); |
333
|
|
|
$data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 ); |
334
|
|
|
|
335
|
|
|
return apply_filters( 'wc_stripe_payment_request_product_data', $data, $product ); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Filters the gateway title to reflect Payment Request type |
340
|
|
|
*/ |
341
|
|
|
public function filter_gateway_title( $title, $id ) { |
342
|
|
|
global $post; |
343
|
|
|
|
344
|
|
|
if ( ! is_object( $post ) ) { |
345
|
|
|
return $title; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
$order = wc_get_order( $post->ID ); |
349
|
|
|
$method_title = is_object( $order ) ? $order->get_payment_method_title() : ''; |
350
|
|
|
|
351
|
|
|
if ( 'stripe' === $id && ! empty( $method_title ) && 'Apple Pay (Stripe)' === $method_title ) { |
352
|
|
|
return $method_title; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
if ( 'stripe' === $id && ! empty( $method_title ) && 'Chrome Payment Request (Stripe)' === $method_title ) { |
356
|
|
|
return $method_title; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $title; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Removes postal code validation from WC. |
364
|
|
|
* |
365
|
|
|
* @since 3.1.4 |
366
|
|
|
* @version 4.0.0 |
367
|
|
|
*/ |
368
|
|
|
public function postal_code_validation( $valid, $postcode, $country ) { |
369
|
|
|
$gateways = WC()->payment_gateways->get_available_payment_gateways(); |
370
|
|
|
|
371
|
|
|
if ( ! isset( $gateways['stripe'] ) ) { |
372
|
|
|
return $valid; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$payment_request_type = isset( $_POST['payment_request_type'] ) ? wc_clean( $_POST['payment_request_type'] ) : ''; |
376
|
|
|
|
377
|
|
|
if ( 'apple_pay' !== $payment_request_type ) { |
378
|
|
|
return $valid; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Currently Apple Pay truncates postal codes from UK and Canada to first 3 characters |
383
|
|
|
* when passing it back from the shippingcontactselected object. This causes WC to invalidate |
384
|
|
|
* the order and not let it go through. The remedy for now is just to remove this validation. |
385
|
|
|
* Note that this only works with shipping providers that don't validate full postal codes. |
386
|
|
|
*/ |
387
|
|
|
if ( 'GB' === $country || 'CA' === $country ) { |
388
|
|
|
return true; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
return $valid; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Add needed order meta |
396
|
|
|
* |
397
|
|
|
* @param integer $order_id The order ID. |
398
|
|
|
* @param array $posted_data The posted data from checkout form. |
399
|
|
|
* |
400
|
|
|
* @since 4.0.0 |
401
|
|
|
* @version 4.0.0 |
402
|
|
|
* @return void |
403
|
|
|
*/ |
404
|
|
|
public function add_order_meta( $order_id, $posted_data ) { |
|
|
|
|
405
|
|
|
if ( empty( $_POST['payment_request_type'] ) ) { |
406
|
|
|
return; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
$order = wc_get_order( $order_id ); |
410
|
|
|
|
411
|
|
|
$payment_request_type = wc_clean( $_POST['payment_request_type'] ); |
412
|
|
|
|
413
|
|
|
if ( 'apple_pay' === $payment_request_type ) { |
414
|
|
|
$order->set_payment_method_title( 'Apple Pay (Stripe)' ); |
415
|
|
|
$order->save(); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
if ( 'payment_request_api' === $payment_request_type ) { |
419
|
|
|
$order->set_payment_method_title( 'Chrome Payment Request (Stripe)' ); |
420
|
|
|
$order->save(); |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Checks to make sure product type is supported. |
426
|
|
|
* |
427
|
|
|
* @since 3.1.0 |
428
|
|
|
* @version 4.0.0 |
429
|
|
|
* @return array |
430
|
|
|
*/ |
431
|
|
|
public function supported_product_types() { |
432
|
|
|
return apply_filters( |
433
|
|
|
'wc_stripe_payment_request_supported_types', |
434
|
|
|
array( |
435
|
|
|
'simple', |
436
|
|
|
'variable', |
437
|
|
|
'variation', |
438
|
|
|
'subscription', |
439
|
|
|
'variable-subscription', |
440
|
|
|
'subscription_variation', |
441
|
|
|
'booking', |
442
|
|
|
'bundle', |
443
|
|
|
'composite', |
444
|
|
|
'mix-and-match', |
445
|
|
|
) |
446
|
|
|
); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Checks the cart to see if all items are allowed to used. |
451
|
|
|
* |
452
|
|
|
* @since 3.1.4 |
453
|
|
|
* @version 4.0.0 |
454
|
|
|
* @return boolean |
455
|
|
|
*/ |
456
|
|
|
public function allowed_items_in_cart() { |
457
|
|
|
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { |
458
|
|
|
$_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); |
459
|
|
|
|
460
|
|
|
if ( ! in_array( $_product->get_type(), $this->supported_product_types() ) ) { |
461
|
|
|
return false; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
// Trial subscriptions with shipping are not supported |
465
|
|
|
if ( class_exists( 'WC_Subscriptions_Order' ) && WC_Subscriptions_Cart::cart_contains_subscription() && $_product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $_product ) > 0 ) { |
466
|
|
|
return false; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
// Pre Orders compatbility where we don't support charge upon release. |
470
|
|
|
if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Cart::cart_contains_pre_order() && WC_Pre_Orders_Product::product_is_charged_upon_release( WC_Pre_Orders_Cart::get_pre_order_product() ) ) { |
471
|
|
|
return false; |
472
|
|
|
} |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
return true; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Load public scripts and styles. |
480
|
|
|
* |
481
|
|
|
* @since 3.1.0 |
482
|
|
|
* @version 4.0.0 |
483
|
|
|
*/ |
484
|
|
|
public function scripts() { |
485
|
|
|
// If keys are not set bail. |
486
|
|
|
if ( ! $this->are_keys_set() ) { |
487
|
|
|
WC_Stripe_Logger::log( 'Keys are not set correctly.' ); |
488
|
|
|
return; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
// If no SSL bail. |
492
|
|
|
if ( ! $this->testmode && ! is_ssl() ) { |
493
|
|
|
WC_Stripe_Logger::log( 'Stripe Payment Request live mode requires SSL.' ); |
494
|
|
|
return; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
if ( ! is_product() && ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) { |
498
|
|
|
return; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
if ( is_product() && ! $this->should_show_payment_button_on_product_page() ) { |
502
|
|
|
return; |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; |
506
|
|
|
|
507
|
|
|
$stripe_params = array( |
508
|
|
|
'ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ), |
509
|
|
|
'stripe' => array( |
510
|
|
|
'key' => $this->publishable_key, |
511
|
|
|
'allow_prepaid_card' => apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no', |
512
|
|
|
), |
513
|
|
|
'nonce' => array( |
514
|
|
|
'payment' => wp_create_nonce( 'wc-stripe-payment-request' ), |
515
|
|
|
'shipping' => wp_create_nonce( 'wc-stripe-payment-request-shipping' ), |
516
|
|
|
'update_shipping' => wp_create_nonce( 'wc-stripe-update-shipping-method' ), |
517
|
|
|
'checkout' => wp_create_nonce( 'woocommerce-process_checkout' ), |
518
|
|
|
'add_to_cart' => wp_create_nonce( 'wc-stripe-add-to-cart' ), |
519
|
|
|
'get_selected_product_data' => wp_create_nonce( 'wc-stripe-get-selected-product-data' ), |
520
|
|
|
'log_errors' => wp_create_nonce( 'wc-stripe-log-errors' ), |
521
|
|
|
'clear_cart' => wp_create_nonce( 'wc-stripe-clear-cart' ), |
522
|
|
|
), |
523
|
|
|
'i18n' => array( |
524
|
|
|
'no_prepaid_card' => __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' ), |
525
|
|
|
/* translators: Do not translate the [option] placeholder */ |
526
|
|
|
'unknown_shipping' => __( 'Unknown shipping option "[option]".', 'woocommerce-gateway-stripe' ), |
527
|
|
|
), |
528
|
|
|
'checkout' => array( |
529
|
|
|
'url' => wc_get_checkout_url(), |
530
|
|
|
'currency_code' => strtolower( get_woocommerce_currency() ), |
531
|
|
|
'country_code' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ), |
532
|
|
|
'needs_shipping' => WC()->cart->needs_shipping() ? 'yes' : 'no', |
533
|
|
|
// Defaults to 'required' to match how core initializes this option. |
534
|
|
|
'needs_payer_phone' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ), |
535
|
|
|
), |
536
|
|
|
'button' => array( |
537
|
|
|
'type' => $this->get_button_type(), |
538
|
|
|
'theme' => $this->get_button_theme(), |
539
|
|
|
'height' => $this->get_button_height(), |
540
|
|
|
'locale' => apply_filters( 'wc_stripe_payment_request_button_locale', substr( get_locale(), 0, 2 ) ), // Default format is en_US. |
541
|
|
|
'is_custom' => $this->is_custom_button(), |
542
|
|
|
'is_branded' => $this->is_branded_button(), |
543
|
|
|
'css_selector' => $this->custom_button_selector(), |
544
|
|
|
'branded_type' => $this->get_button_branded_type(), |
545
|
|
|
), |
546
|
|
|
'is_product_page' => is_product(), |
547
|
|
|
'product' => $this->get_product_data(), |
548
|
|
|
); |
549
|
|
|
|
550
|
|
|
wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true ); |
551
|
|
|
wp_register_script( 'wc_stripe_payment_request', plugins_url( 'assets/js/stripe-payment-request' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery', 'stripe' ), WC_STRIPE_VERSION, true ); |
552
|
|
|
|
553
|
|
|
wp_localize_script( 'wc_stripe_payment_request', 'wc_stripe_payment_request_params', apply_filters( 'wc_stripe_payment_request_params', $stripe_params ) ); |
554
|
|
|
|
555
|
|
|
wp_enqueue_script( 'wc_stripe_payment_request' ); |
556
|
|
|
|
557
|
|
|
$gateways = WC()->payment_gateways->get_available_payment_gateways(); |
558
|
|
|
if ( isset( $gateways['stripe'] ) ) { |
559
|
|
|
$gateways['stripe']->payment_scripts(); |
560
|
|
|
} |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Display the payment request button. |
565
|
|
|
* |
566
|
|
|
* @since 4.0.0 |
567
|
|
|
* @version 4.0.0 |
568
|
|
|
*/ |
569
|
|
|
public function display_payment_request_button_html() { |
570
|
|
|
global $post; |
571
|
|
|
|
572
|
|
|
$gateways = WC()->payment_gateways->get_available_payment_gateways(); |
573
|
|
|
|
574
|
|
|
if ( ! isset( $gateways['stripe'] ) ) { |
575
|
|
|
return; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) { |
579
|
|
|
return; |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
if ( is_checkout() && ! apply_filters( 'wc_stripe_show_payment_request_on_checkout', false, $post ) ) { |
583
|
|
|
return; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
if ( is_product() && ! $this->should_show_payment_button_on_product_page() ) { |
587
|
|
|
return; |
588
|
|
|
} else if ( ! $this->should_show_payment_button_on_cart() ) { |
589
|
|
|
return; |
590
|
|
|
} |
591
|
|
|
?> |
592
|
|
|
<div id="wc-stripe-payment-request-wrapper" style="clear:both;padding-top:1.5em;display:none;"> |
593
|
|
|
<div id="wc-stripe-payment-request-button"> |
594
|
|
|
<?php |
595
|
|
|
if ( $this->is_custom_button() ) { |
596
|
|
|
$label = esc_html( $this->get_button_label() ); |
597
|
|
|
$class_name = esc_attr( 'button ' . $this->get_button_theme() ); |
598
|
|
|
$style = esc_attr( 'height:' . $this->get_button_height() . 'px;' ); |
599
|
|
|
echo "<button id=\"wc-stripe-custom-button\" class=\"$class_name\" style=\"$style\"> $label </button>"; |
600
|
|
|
} |
601
|
|
|
?> |
602
|
|
|
<!-- A Stripe Element will be inserted here. --> |
603
|
|
|
</div> |
604
|
|
|
</div> |
605
|
|
|
<?php |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* Display payment request button separator. |
610
|
|
|
* |
611
|
|
|
* @since 4.0.0 |
612
|
|
|
* @version 4.0.0 |
613
|
|
|
*/ |
614
|
|
|
public function display_payment_request_button_separator_html() { |
615
|
|
|
global $post; |
616
|
|
|
|
617
|
|
|
$gateways = WC()->payment_gateways->get_available_payment_gateways(); |
618
|
|
|
|
619
|
|
|
if ( ! isset( $gateways['stripe'] ) ) { |
620
|
|
|
return; |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) { |
624
|
|
|
return; |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
if ( is_checkout() && ! apply_filters( 'wc_stripe_show_payment_request_on_checkout', false, $post ) ) { |
628
|
|
|
return; |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
if ( is_product() && ! $this->should_show_payment_button_on_product_page() ) { |
632
|
|
|
return; |
633
|
|
|
} else if ( ! $this->should_show_payment_button_on_cart() ) { |
634
|
|
|
return; |
635
|
|
|
} |
636
|
|
|
?> |
637
|
|
|
<p id="wc-stripe-payment-request-button-separator" style="margin-top:1.5em;text-align:center;display:none;">— <?php esc_html_e( 'OR', 'woocommerce-gateway-stripe' ); ?> —</p> |
638
|
|
|
<?php |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/** |
642
|
|
|
* Whether payment button html should be rendered on the Cart |
643
|
|
|
* |
644
|
|
|
* @since 4.4.1 |
645
|
|
|
* @return boolean |
646
|
|
|
*/ |
647
|
|
|
private function should_show_payment_button_on_cart() { |
648
|
|
|
if ( ! apply_filters( 'wc_stripe_show_payment_request_on_cart', true ) ) { |
649
|
|
|
return false; |
650
|
|
|
} |
651
|
|
|
if ( ! $this->allowed_items_in_cart() ) { |
652
|
|
|
WC_Stripe_Logger::log( 'Items in the cart has unsupported product type ( Payment Request button disabled )' ); |
653
|
|
|
return false; |
654
|
|
|
} |
655
|
|
|
return true; |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
/** |
659
|
|
|
* Whether payment button html should be rendered |
660
|
|
|
* |
661
|
|
|
* @since 4.3.2 |
662
|
|
|
* @return boolean |
663
|
|
|
*/ |
664
|
|
|
private function should_show_payment_button_on_product_page() { |
665
|
|
|
global $post; |
666
|
|
|
|
667
|
|
|
$product = wc_get_product( $post->ID ); |
668
|
|
|
|
669
|
|
|
if ( apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false, $post ) ) { |
670
|
|
|
return false; |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
if ( ! is_object( $product ) || ! in_array( $product->get_type(), $this->supported_product_types() ) ) { |
674
|
|
|
return false; |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
// Trial subscriptions with shipping are not supported |
678
|
|
|
if ( class_exists( 'WC_Subscriptions_Order' ) && $product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) { |
679
|
|
|
return false; |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
// Pre Orders charge upon release not supported. |
683
|
|
|
if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) { |
684
|
|
|
WC_Stripe_Logger::log( 'Pre Order charge upon release is not supported. ( Payment Request button disabled )' ); |
685
|
|
|
return false; |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
// File upload addon not supported |
689
|
|
|
if ( class_exists( 'WC_Product_Addons_Helper' ) ) { |
690
|
|
|
$product_addons = WC_Product_Addons_Helper::get_product_addons( $product->get_id() ); |
691
|
|
|
foreach ( $product_addons as $addon ) { |
692
|
|
|
if ( 'file_upload' === $addon['type'] ) { |
693
|
|
|
return false; |
694
|
|
|
} |
695
|
|
|
} |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
return true; |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
/** |
702
|
|
|
* Log errors coming from Payment Request |
703
|
|
|
* |
704
|
|
|
* @since 3.1.4 |
705
|
|
|
* @version 4.0.0 |
706
|
|
|
*/ |
707
|
|
|
public function ajax_log_errors() { |
708
|
|
|
check_ajax_referer( 'wc-stripe-log-errors', 'security' ); |
709
|
|
|
|
710
|
|
|
$errors = wc_clean( stripslashes( $_POST['errors'] ) ); |
711
|
|
|
|
712
|
|
|
WC_Stripe_Logger::log( $errors ); |
713
|
|
|
|
714
|
|
|
exit; |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
/** |
718
|
|
|
* Clears cart. |
719
|
|
|
* |
720
|
|
|
* @since 3.1.4 |
721
|
|
|
* @version 4.0.0 |
722
|
|
|
*/ |
723
|
|
|
public function ajax_clear_cart() { |
724
|
|
|
check_ajax_referer( 'wc-stripe-clear-cart', 'security' ); |
725
|
|
|
|
726
|
|
|
WC()->cart->empty_cart(); |
727
|
|
|
exit; |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Get cart details. |
732
|
|
|
*/ |
733
|
|
|
public function ajax_get_cart_details() { |
734
|
|
|
check_ajax_referer( 'wc-stripe-payment-request', 'security' ); |
735
|
|
|
|
736
|
|
|
if ( ! defined( 'WOOCOMMERCE_CART' ) ) { |
737
|
|
|
define( 'WOOCOMMERCE_CART', true ); |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
WC()->cart->calculate_totals(); |
741
|
|
|
|
742
|
|
|
$currency = get_woocommerce_currency(); |
743
|
|
|
|
744
|
|
|
// Set mandatory payment details. |
745
|
|
|
$data = array( |
746
|
|
|
'shipping_required' => WC()->cart->needs_shipping(), |
747
|
|
|
'order_data' => array( |
748
|
|
|
'currency' => strtolower( $currency ), |
749
|
|
|
'country_code' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ), |
750
|
|
|
), |
751
|
|
|
); |
752
|
|
|
|
753
|
|
|
$data['order_data'] += $this->build_display_items(); |
754
|
|
|
|
755
|
|
|
wp_send_json( $data ); |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
/** |
759
|
|
|
* Get shipping options. |
760
|
|
|
* |
761
|
|
|
* @see WC_Cart::get_shipping_packages(). |
762
|
|
|
* @see WC_Shipping::calculate_shipping(). |
763
|
|
|
* @see WC_Shipping::get_packages(). |
764
|
|
|
*/ |
765
|
|
|
public function ajax_get_shipping_options() { |
766
|
|
|
check_ajax_referer( 'wc-stripe-payment-request-shipping', 'security' ); |
767
|
|
|
|
768
|
|
|
$shipping_address = filter_input_array( |
769
|
|
|
INPUT_POST, |
770
|
|
|
array( |
771
|
|
|
'country' => FILTER_SANITIZE_STRING, |
772
|
|
|
'state' => FILTER_SANITIZE_STRING, |
773
|
|
|
'postcode' => FILTER_SANITIZE_STRING, |
774
|
|
|
'city' => FILTER_SANITIZE_STRING, |
775
|
|
|
'address' => FILTER_SANITIZE_STRING, |
776
|
|
|
'address_2' => FILTER_SANITIZE_STRING, |
777
|
|
|
) |
778
|
|
|
); |
779
|
|
|
$product_view_options = filter_input_array( INPUT_POST, [ 'is_product_page' => FILTER_SANITIZE_STRING ] ); |
780
|
|
|
$should_show_itemized_view = ! isset( $product_view_options['is_product_page'] ) ?: filter_var( $product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN ); |
781
|
|
|
|
782
|
|
|
$data = $this->get_shipping_options( $shipping_address, $should_show_itemized_view ); |
783
|
|
|
wp_send_json( $data ); |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
/** |
787
|
|
|
* Gets shipping options available for specified shipping address |
788
|
|
|
* |
789
|
|
|
* @param array $shipping_address Shipping address. |
790
|
|
|
* @param boolean $itemized_display_items Indicates whether to show subtotals or itemized views. |
791
|
|
|
* |
792
|
|
|
* @return array Shipping options data. |
793
|
|
|
* phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag |
794
|
|
|
*/ |
795
|
|
|
public function get_shipping_options( $shipping_address, $itemized_display_items = false ) { |
796
|
|
|
try { |
797
|
|
|
// Set the shipping options. |
798
|
|
|
$data = array(); |
799
|
|
|
|
800
|
|
|
// Remember current shipping method before resetting. |
801
|
|
|
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); |
802
|
|
|
$this->calculate_shipping( apply_filters( 'wc_stripe_payment_request_shipping_posted_values', $shipping_address ) ); |
803
|
|
|
|
804
|
|
|
$packages = WC()->shipping->get_packages(); |
805
|
|
|
|
806
|
|
|
if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) { |
807
|
|
|
foreach ( $packages as $package_key => $package ) { |
808
|
|
|
if ( empty( $package['rates'] ) ) { |
809
|
|
|
throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) ); |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
foreach ( $package['rates'] as $key => $rate ) { |
813
|
|
|
$data['shipping_options'][] = array( |
814
|
|
|
'id' => $rate->id, |
815
|
|
|
'label' => $rate->label, |
816
|
|
|
'detail' => '', |
817
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $rate->cost ), |
818
|
|
|
); |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
} else { |
822
|
|
|
throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) ); |
823
|
|
|
} |
824
|
|
|
|
825
|
|
|
// The first shipping option is automatically applied on the client. |
826
|
|
|
// Keep chosen shipping method by sorting shipping options if the method still available for new address. |
827
|
|
|
// Fallback to the first available shipping method. |
828
|
|
|
if ( isset( $data['shipping_options'][0] ) ) { |
829
|
|
|
if ( isset( $chosen_shipping_methods[0] ) ) { |
830
|
|
|
$chosen_method_id = $chosen_shipping_methods[0]; |
831
|
|
|
$compare_shipping_options = function ( $a, $b ) use ( $chosen_method_id ) { |
832
|
|
|
if ( $a['id'] === $chosen_method_id ) { |
833
|
|
|
return -1; |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
if ( $b['id'] === $chosen_method_id ) { |
837
|
|
|
return 1; |
838
|
|
|
} |
839
|
|
|
|
840
|
|
|
return 0; |
841
|
|
|
}; |
842
|
|
|
usort( $data['shipping_options'], $compare_shipping_options ); |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
$first_shipping_method_id = $data['shipping_options'][0]['id']; |
846
|
|
|
$this->update_shipping_method( [ $first_shipping_method_id ] ); |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
WC()->cart->calculate_totals(); |
850
|
|
|
|
851
|
|
|
$data += $this->build_display_items( $itemized_display_items ); |
852
|
|
|
$data['result'] = 'success'; |
853
|
|
|
} catch ( Exception $e ) { |
854
|
|
|
$data += $this->build_display_items( $itemized_display_items ); |
855
|
|
|
$data['result'] = 'invalid_shipping_address'; |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
return $data; |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
/** |
862
|
|
|
* Update shipping method. |
863
|
|
|
*/ |
864
|
|
|
public function ajax_update_shipping_method() { |
865
|
|
|
check_ajax_referer( 'wc-stripe-update-shipping-method', 'security' ); |
866
|
|
|
|
867
|
|
|
if ( ! defined( 'WOOCOMMERCE_CART' ) ) { |
868
|
|
|
define( 'WOOCOMMERCE_CART', true ); |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
$shipping_methods = filter_input( INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); |
872
|
|
|
$this->update_shipping_method( $shipping_methods ); |
873
|
|
|
|
874
|
|
|
WC()->cart->calculate_totals(); |
875
|
|
|
|
876
|
|
|
$product_view_options = filter_input_array( INPUT_POST, [ 'is_product_page' => FILTER_SANITIZE_STRING ] ); |
877
|
|
|
$should_show_itemized_view = ! isset( $product_view_options['is_product_page'] ) ?: filter_var( $product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN ); |
878
|
|
|
|
879
|
|
|
$data = array(); |
880
|
|
|
$data += $this->build_display_items( $should_show_itemized_view ); |
881
|
|
|
$data['result'] = 'success'; |
882
|
|
|
|
883
|
|
|
wp_send_json( $data ); |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
/** |
887
|
|
|
* Updates shipping method in WC session |
888
|
|
|
* |
889
|
|
|
* @param array $shipping_methods Array of selected shipping methods ids. |
890
|
|
|
*/ |
891
|
|
|
public function update_shipping_method( $shipping_methods ) { |
892
|
|
|
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); |
893
|
|
|
|
894
|
|
|
if ( is_array( $shipping_methods ) ) { |
895
|
|
|
foreach ( $shipping_methods as $i => $value ) { |
896
|
|
|
$chosen_shipping_methods[ $i ] = wc_clean( $value ); |
897
|
|
|
} |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Gets the selected product data. |
905
|
|
|
* |
906
|
|
|
* @since 4.0.0 |
907
|
|
|
* @version 4.0.0 |
908
|
|
|
* @return array $data |
909
|
|
|
*/ |
910
|
|
|
public function ajax_get_selected_product_data() { |
911
|
|
|
check_ajax_referer( 'wc-stripe-get-selected-product-data', 'security' ); |
912
|
|
|
|
913
|
|
|
try { |
914
|
|
|
$product_id = absint( $_POST['product_id'] ); |
915
|
|
|
$qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id ); |
916
|
|
|
$addon_value = isset( $_POST['addon_value'] ) ? max( floatval( $_POST['addon_value'] ), 0 ) : 0; |
917
|
|
|
$product = wc_get_product( $product_id ); |
918
|
|
|
$variation_id = null; |
919
|
|
|
|
920
|
|
|
if ( ! is_a( $product, 'WC_Product' ) ) { |
921
|
|
|
throw new Exception( sprintf( __( 'Product with the ID (%d) cannot be found.', 'woocommerce-gateway-stripe' ), $product_id ) ); |
922
|
|
|
} |
923
|
|
|
|
924
|
|
|
if ( 'variable' === $product->get_type() && isset( $_POST['attributes'] ) ) { |
925
|
|
|
$attributes = wc_clean( wp_unslash( $_POST['attributes'] ) ); |
926
|
|
|
|
927
|
|
|
$data_store = WC_Data_Store::load( 'product' ); |
928
|
|
|
$variation_id = $data_store->find_matching_product_variation( $product, $attributes ); |
929
|
|
|
|
930
|
|
|
if ( ! empty( $variation_id ) ) { |
931
|
|
|
$product = wc_get_product( $variation_id ); |
932
|
|
|
} |
933
|
|
|
} |
934
|
|
|
|
935
|
|
|
// Force quantity to 1 if sold individually and check for existing item in cart. |
936
|
|
|
if ( $product->is_sold_individually() ) { |
937
|
|
|
$qty = apply_filters( 'wc_stripe_payment_request_add_to_cart_sold_individually_quantity', 1, $qty, $product_id, $variation_id ); |
938
|
|
|
} |
939
|
|
|
|
940
|
|
|
if ( ! $product->has_enough_stock( $qty ) ) { |
941
|
|
|
/* translators: 1: product name 2: quantity in stock */ |
942
|
|
|
throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-gateway-stripe' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) ); |
943
|
|
|
} |
944
|
|
|
|
945
|
|
|
$total = $qty * $product->get_price() + $addon_value; |
946
|
|
|
|
947
|
|
|
$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : ''; |
948
|
|
|
|
949
|
|
|
$data = array(); |
950
|
|
|
$items = array(); |
951
|
|
|
|
952
|
|
|
$items[] = array( |
953
|
|
|
'label' => $product->get_name() . $quantity_label, |
954
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $total ), |
955
|
|
|
); |
956
|
|
|
|
957
|
|
View Code Duplication |
if ( wc_tax_enabled() ) { |
|
|
|
|
958
|
|
|
$items[] = array( |
959
|
|
|
'label' => __( 'Tax', 'woocommerce-gateway-stripe' ), |
960
|
|
|
'amount' => 0, |
961
|
|
|
'pending' => true, |
962
|
|
|
); |
963
|
|
|
} |
964
|
|
|
|
965
|
|
View Code Duplication |
if ( wc_shipping_enabled() && $product->needs_shipping() ) { |
|
|
|
|
966
|
|
|
$items[] = array( |
967
|
|
|
'label' => __( 'Shipping', 'woocommerce-gateway-stripe' ), |
968
|
|
|
'amount' => 0, |
969
|
|
|
'pending' => true, |
970
|
|
|
); |
971
|
|
|
|
972
|
|
|
$data['shippingOptions'] = array( |
973
|
|
|
'id' => 'pending', |
974
|
|
|
'label' => __( 'Pending', 'woocommerce-gateway-stripe' ), |
975
|
|
|
'detail' => '', |
976
|
|
|
'amount' => 0, |
977
|
|
|
); |
978
|
|
|
} |
979
|
|
|
|
980
|
|
|
$data['displayItems'] = $items; |
981
|
|
|
$data['total'] = array( |
982
|
|
|
'label' => $this->total_label, |
983
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $total ), |
984
|
|
|
'pending' => true, |
985
|
|
|
); |
986
|
|
|
|
987
|
|
|
$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() ); |
988
|
|
|
$data['currency'] = strtolower( get_woocommerce_currency() ); |
989
|
|
|
$data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 ); |
990
|
|
|
|
991
|
|
|
wp_send_json( $data ); |
992
|
|
|
} catch ( Exception $e ) { |
993
|
|
|
wp_send_json( array( 'error' => wp_strip_all_tags( $e->getMessage() ) ) ); |
994
|
|
|
} |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
/** |
998
|
|
|
* Adds the current product to the cart. Used on product detail page. |
999
|
|
|
* |
1000
|
|
|
* @since 4.0.0 |
1001
|
|
|
* @version 4.0.0 |
1002
|
|
|
* @return array $data |
1003
|
|
|
*/ |
1004
|
|
|
public function ajax_add_to_cart() { |
1005
|
|
|
check_ajax_referer( 'wc-stripe-add-to-cart', 'security' ); |
1006
|
|
|
|
1007
|
|
|
if ( ! defined( 'WOOCOMMERCE_CART' ) ) { |
1008
|
|
|
define( 'WOOCOMMERCE_CART', true ); |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
WC()->shipping->reset_shipping(); |
1012
|
|
|
|
1013
|
|
|
$product_id = absint( $_POST['product_id'] ); |
1014
|
|
|
$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] ); |
1015
|
|
|
$product = wc_get_product( $product_id ); |
1016
|
|
|
$product_type = $product->get_type(); |
1017
|
|
|
|
1018
|
|
|
// First empty the cart to prevent wrong calculation. |
1019
|
|
|
WC()->cart->empty_cart(); |
1020
|
|
|
|
1021
|
|
|
if ( ( 'variable' === $product_type || 'variable-subscription' === $product_type ) && isset( $_POST['attributes'] ) ) { |
1022
|
|
|
$attributes = wc_clean( wp_unslash( $_POST['attributes'] ) ); |
1023
|
|
|
|
1024
|
|
|
$data_store = WC_Data_Store::load( 'product' ); |
1025
|
|
|
$variation_id = $data_store->find_matching_product_variation( $product, $attributes ); |
1026
|
|
|
|
1027
|
|
|
WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes ); |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
if ( 'simple' === $product_type || 'subscription' === $product_type ) { |
1031
|
|
|
WC()->cart->add_to_cart( $product->get_id(), $qty ); |
1032
|
|
|
} |
1033
|
|
|
|
1034
|
|
|
WC()->cart->calculate_totals(); |
1035
|
|
|
|
1036
|
|
|
$data = array(); |
1037
|
|
|
$data += $this->build_display_items(); |
1038
|
|
|
$data['result'] = 'success'; |
1039
|
|
|
|
1040
|
|
|
wp_send_json( $data ); |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
|
|
/** |
1044
|
|
|
* Normalizes the state/county field because in some |
1045
|
|
|
* cases, the state/county field is formatted differently from |
1046
|
|
|
* what WC is expecting and throws an error. An example |
1047
|
|
|
* for Ireland the county dropdown in Chrome shows "Co. Clare" format |
1048
|
|
|
* |
1049
|
|
|
* @since 4.0.0 |
1050
|
|
|
* @version 4.0.0 |
1051
|
|
|
*/ |
1052
|
|
|
public function normalize_state() { |
1053
|
|
|
$billing_country = ! empty( $_POST['billing_country'] ) ? wc_clean( $_POST['billing_country'] ) : ''; |
1054
|
|
|
$shipping_country = ! empty( $_POST['shipping_country'] ) ? wc_clean( $_POST['shipping_country'] ) : ''; |
1055
|
|
|
$billing_state = ! empty( $_POST['billing_state'] ) ? wc_clean( $_POST['billing_state'] ) : ''; |
1056
|
|
|
$shipping_state = ! empty( $_POST['shipping_state'] ) ? wc_clean( $_POST['shipping_state'] ) : ''; |
1057
|
|
|
|
1058
|
|
View Code Duplication |
if ( $billing_state && $billing_country ) { |
|
|
|
|
1059
|
|
|
$valid_states = WC()->countries->get_states( $billing_country ); |
1060
|
|
|
|
1061
|
|
|
// Valid states found for country. |
1062
|
|
|
if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) { |
1063
|
|
|
foreach ( $valid_states as $state_abbr => $state ) { |
1064
|
|
|
if ( preg_match( '/' . preg_quote( $state ) . '/i', $billing_state ) ) { |
1065
|
|
|
$_POST['billing_state'] = $state_abbr; |
1066
|
|
|
} |
1067
|
|
|
} |
1068
|
|
|
} |
1069
|
|
|
} |
1070
|
|
|
|
1071
|
|
View Code Duplication |
if ( $shipping_state && $shipping_country ) { |
|
|
|
|
1072
|
|
|
$valid_states = WC()->countries->get_states( $shipping_country ); |
1073
|
|
|
|
1074
|
|
|
// Valid states found for country. |
1075
|
|
|
if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) { |
1076
|
|
|
foreach ( $valid_states as $state_abbr => $state ) { |
1077
|
|
|
if ( preg_match( '/' . preg_quote( $state ) . '/i', $shipping_state ) ) { |
1078
|
|
|
$_POST['shipping_state'] = $state_abbr; |
1079
|
|
|
} |
1080
|
|
|
} |
1081
|
|
|
} |
1082
|
|
|
} |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
|
|
/** |
1086
|
|
|
* Create order. Security is handled by WC. |
1087
|
|
|
* |
1088
|
|
|
* @since 3.1.0 |
1089
|
|
|
* @version 4.0.0 |
1090
|
|
|
*/ |
1091
|
|
|
public function ajax_create_order() { |
1092
|
|
|
if ( WC()->cart->is_empty() ) { |
1093
|
|
|
wp_send_json_error( __( 'Empty cart', 'woocommerce-gateway-stripe' ) ); |
1094
|
|
|
} |
1095
|
|
|
|
1096
|
|
|
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { |
1097
|
|
|
define( 'WOOCOMMERCE_CHECKOUT', true ); |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
$this->normalize_state(); |
1101
|
|
|
|
1102
|
|
|
WC()->checkout()->process_checkout(); |
1103
|
|
|
|
1104
|
|
|
die( 0 ); |
1105
|
|
|
} |
1106
|
|
|
|
1107
|
|
|
/** |
1108
|
|
|
* Calculate and set shipping method. |
1109
|
|
|
* |
1110
|
|
|
* @param array $address Shipping address. |
1111
|
|
|
* |
1112
|
|
|
* @since 3.1.0 |
1113
|
|
|
* @version 4.0.0 |
1114
|
|
|
*/ |
1115
|
|
|
protected function calculate_shipping( $address = array() ) { |
1116
|
|
|
$country = $address['country']; |
1117
|
|
|
$state = $address['state']; |
1118
|
|
|
$postcode = $address['postcode']; |
1119
|
|
|
$city = $address['city']; |
1120
|
|
|
$address_1 = $address['address']; |
1121
|
|
|
$address_2 = $address['address_2']; |
1122
|
|
|
$wc_states = WC()->countries->get_states( $country ); |
1123
|
|
|
|
1124
|
|
|
/** |
1125
|
|
|
* In some versions of Chrome, state can be a full name. So we need |
1126
|
|
|
* to convert that to abbreviation as WC is expecting that. |
1127
|
|
|
*/ |
1128
|
|
|
if ( 2 < strlen( $state ) && ! empty( $wc_states ) && ! isset( $wc_states[ $state ] ) ) { |
1129
|
|
|
$state = array_search( ucwords( strtolower( $state ) ), $wc_states, true ); |
1130
|
|
|
} |
1131
|
|
|
|
1132
|
|
|
WC()->shipping->reset_shipping(); |
1133
|
|
|
|
1134
|
|
|
if ( $postcode && WC_Validation::is_postcode( $postcode, $country ) ) { |
1135
|
|
|
$postcode = wc_format_postcode( $postcode, $country ); |
1136
|
|
|
} |
1137
|
|
|
|
1138
|
|
|
if ( $country ) { |
1139
|
|
|
WC()->customer->set_location( $country, $state, $postcode, $city ); |
1140
|
|
|
WC()->customer->set_shipping_location( $country, $state, $postcode, $city ); |
1141
|
|
|
} else { |
1142
|
|
|
WC()->customer->set_billing_address_to_base(); |
1143
|
|
|
WC()->customer->set_shipping_address_to_base(); |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
WC()->customer->set_calculated_shipping( true ); |
1147
|
|
|
WC()->customer->save(); |
1148
|
|
|
|
1149
|
|
|
$packages = array(); |
1150
|
|
|
|
1151
|
|
|
$packages[0]['contents'] = WC()->cart->get_cart(); |
1152
|
|
|
$packages[0]['contents_cost'] = 0; |
1153
|
|
|
$packages[0]['applied_coupons'] = WC()->cart->applied_coupons; |
1154
|
|
|
$packages[0]['user']['ID'] = get_current_user_id(); |
1155
|
|
|
$packages[0]['destination']['country'] = $country; |
1156
|
|
|
$packages[0]['destination']['state'] = $state; |
1157
|
|
|
$packages[0]['destination']['postcode'] = $postcode; |
1158
|
|
|
$packages[0]['destination']['city'] = $city; |
1159
|
|
|
$packages[0]['destination']['address'] = $address_1; |
1160
|
|
|
$packages[0]['destination']['address_2'] = $address_2; |
1161
|
|
|
|
1162
|
|
|
foreach ( WC()->cart->get_cart() as $item ) { |
1163
|
|
|
if ( $item['data']->needs_shipping() ) { |
1164
|
|
|
if ( isset( $item['line_total'] ) ) { |
1165
|
|
|
$packages[0]['contents_cost'] += $item['line_total']; |
1166
|
|
|
} |
1167
|
|
|
} |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages ); |
1171
|
|
|
|
1172
|
|
|
WC()->shipping->calculate_shipping( $packages ); |
1173
|
|
|
} |
1174
|
|
|
|
1175
|
|
|
/** |
1176
|
|
|
* Builds the shippings methods to pass to Payment Request |
1177
|
|
|
* |
1178
|
|
|
* @since 3.1.0 |
1179
|
|
|
* @version 4.0.0 |
1180
|
|
|
*/ |
1181
|
|
|
protected function build_shipping_methods( $shipping_methods ) { |
1182
|
|
|
if ( empty( $shipping_methods ) ) { |
1183
|
|
|
return array(); |
1184
|
|
|
} |
1185
|
|
|
|
1186
|
|
|
$shipping = array(); |
1187
|
|
|
|
1188
|
|
|
foreach ( $shipping_methods as $method ) { |
1189
|
|
|
$shipping[] = array( |
1190
|
|
|
'id' => $method['id'], |
1191
|
|
|
'label' => $method['label'], |
1192
|
|
|
'detail' => '', |
1193
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $method['amount']['value'] ), |
1194
|
|
|
); |
1195
|
|
|
} |
1196
|
|
|
|
1197
|
|
|
return $shipping; |
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
/** |
1201
|
|
|
* Builds the line items to pass to Payment Request |
1202
|
|
|
* |
1203
|
|
|
* @since 3.1.0 |
1204
|
|
|
* @version 4.0.0 |
1205
|
|
|
*/ |
1206
|
|
|
protected function build_display_items( $itemized_display_items = false ) { |
1207
|
|
|
if ( ! defined( 'WOOCOMMERCE_CART' ) ) { |
1208
|
|
|
define( 'WOOCOMMERCE_CART', true ); |
1209
|
|
|
} |
1210
|
|
|
|
1211
|
|
|
$items = array(); |
1212
|
|
|
$subtotal = 0; |
1213
|
|
|
$discounts = 0; |
1214
|
|
|
|
1215
|
|
|
// Default show only subtotal instead of itemization. |
1216
|
|
|
if ( ! apply_filters( 'wc_stripe_payment_request_hide_itemization', true ) || $itemized_display_items ) { |
1217
|
|
|
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { |
1218
|
|
|
$amount = $cart_item['line_subtotal']; |
1219
|
|
|
$subtotal += $cart_item['line_subtotal']; |
1220
|
|
|
$quantity_label = 1 < $cart_item['quantity'] ? ' (x' . $cart_item['quantity'] . ')' : ''; |
1221
|
|
|
|
1222
|
|
|
$product_name = $cart_item['data']->get_name(); |
1223
|
|
|
|
1224
|
|
|
$item = array( |
1225
|
|
|
'label' => $product_name . $quantity_label, |
1226
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $amount ), |
1227
|
|
|
); |
1228
|
|
|
|
1229
|
|
|
$items[] = $item; |
1230
|
|
|
} |
1231
|
|
|
} |
1232
|
|
|
|
1233
|
|
|
if ( version_compare( WC_VERSION, '3.2', '<' ) ) { |
1234
|
|
|
$discounts = wc_format_decimal( WC()->cart->get_cart_discount_total(), WC()->cart->dp ); |
1235
|
|
|
} else { |
1236
|
|
|
$applied_coupons = array_values( WC()->cart->get_coupon_discount_totals() ); |
1237
|
|
|
|
1238
|
|
|
foreach ( $applied_coupons as $amount ) { |
1239
|
|
|
$discounts += (float) $amount; |
1240
|
|
|
} |
1241
|
|
|
} |
1242
|
|
|
|
1243
|
|
|
$discounts = wc_format_decimal( $discounts, WC()->cart->dp ); |
1244
|
|
|
$tax = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ); |
1245
|
|
|
$shipping = wc_format_decimal( WC()->cart->shipping_total, WC()->cart->dp ); |
1246
|
|
|
$items_total = wc_format_decimal( WC()->cart->cart_contents_total, WC()->cart->dp ) + $discounts; |
1247
|
|
|
$order_total = version_compare( WC_VERSION, '3.2', '<' ) ? wc_format_decimal( $items_total + $tax + $shipping - $discounts, WC()->cart->dp ) : WC()->cart->get_total( false ); |
1248
|
|
|
|
1249
|
|
|
if ( wc_tax_enabled() ) { |
1250
|
|
|
$items[] = array( |
1251
|
|
|
'label' => esc_html( __( 'Tax', 'woocommerce-gateway-stripe' ) ), |
1252
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $tax ), |
1253
|
|
|
); |
1254
|
|
|
} |
1255
|
|
|
|
1256
|
|
View Code Duplication |
if ( WC()->cart->needs_shipping() ) { |
|
|
|
|
1257
|
|
|
$items[] = array( |
1258
|
|
|
'label' => esc_html( __( 'Shipping', 'woocommerce-gateway-stripe' ) ), |
1259
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $shipping ), |
1260
|
|
|
); |
1261
|
|
|
} |
1262
|
|
|
|
1263
|
|
View Code Duplication |
if ( WC()->cart->has_discount() ) { |
|
|
|
|
1264
|
|
|
$items[] = array( |
1265
|
|
|
'label' => esc_html( __( 'Discount', 'woocommerce-gateway-stripe' ) ), |
1266
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $discounts ), |
1267
|
|
|
); |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
if ( version_compare( WC_VERSION, '3.2', '<' ) ) { |
1271
|
|
|
$cart_fees = WC()->cart->fees; |
1272
|
|
|
} else { |
1273
|
|
|
$cart_fees = WC()->cart->get_fees(); |
1274
|
|
|
} |
1275
|
|
|
|
1276
|
|
|
// Include fees and taxes as display items. |
1277
|
|
|
foreach ( $cart_fees as $key => $fee ) { |
1278
|
|
|
$items[] = array( |
1279
|
|
|
'label' => $fee->name, |
1280
|
|
|
'amount' => WC_Stripe_Helper::get_stripe_amount( $fee->amount ), |
1281
|
|
|
); |
1282
|
|
|
} |
1283
|
|
|
|
1284
|
|
|
return array( |
1285
|
|
|
'displayItems' => $items, |
1286
|
|
|
'total' => array( |
1287
|
|
|
'label' => $this->total_label, |
1288
|
|
|
'amount' => max( 0, apply_filters( 'woocommerce_stripe_calculated_total', WC_Stripe_Helper::get_stripe_amount( $order_total ), $order_total, WC()->cart ) ), |
1289
|
|
|
'pending' => false, |
1290
|
|
|
), |
1291
|
|
|
); |
1292
|
|
|
} |
1293
|
|
|
} |
1294
|
|
|
|
1295
|
|
|
new WC_Stripe_Payment_Request(); |
1296
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.