This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | if ( ! defined( 'ABSPATH' ) ) { |
||
3 | exit; |
||
4 | } |
||
5 | |||
6 | // phpcs:disable WordPress.Files.FileName |
||
7 | |||
8 | /** |
||
9 | * Abstract class that will be inherited by all payment methods. |
||
10 | * |
||
11 | * @extends WC_Payment_Gateway_CC |
||
12 | * |
||
13 | * @since 4.0.0 |
||
14 | */ |
||
15 | abstract class WC_Stripe_Payment_Gateway extends WC_Payment_Gateway_CC { |
||
16 | /** |
||
17 | * Displays the admin settings webhook description. |
||
18 | * |
||
19 | * @since 4.1.0 |
||
20 | * @return mixed |
||
21 | */ |
||
22 | public function display_admin_settings_webhook_description() { |
||
23 | /* translators: 1) webhook url */ |
||
24 | return sprintf( __( 'You must add the following webhook endpoint <strong style="background-color:#ddd;"> %s </strong> to your <a href="https://dashboard.stripe.com/account/webhooks" target="_blank">Stripe account settings</a>. This will enable you to receive notifications on the charge statuses.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::get_webhook_url() ); |
||
25 | } |
||
26 | |||
27 | /** |
||
28 | * Displays the save to account checkbox. |
||
29 | * |
||
30 | * @since 4.1.0 |
||
31 | */ |
||
32 | public function save_payment_method_checkbox() { |
||
33 | printf( |
||
34 | '<p class="form-row woocommerce-SavedPaymentMethods-saveNew"> |
||
35 | <input id="wc-%1$s-new-payment-method" name="wc-%1$s-new-payment-method" type="checkbox" value="true" style="width:auto;" /> |
||
36 | <label for="wc-%1$s-new-payment-method" style="display:inline;">%2$s</label> |
||
37 | </p>', |
||
38 | esc_attr( $this->id ), |
||
39 | esc_html( apply_filters( 'wc_stripe_save_to_account_text', __( 'Save payment information to my account for future purchases.', 'woocommerce-gateway-stripe' ) ) ) |
||
40 | ); |
||
41 | } |
||
42 | |||
43 | /** |
||
44 | * Checks to see if request is invalid and that |
||
45 | * they are worth retrying. |
||
46 | * |
||
47 | * @since 4.0.5 |
||
48 | * @param array $error |
||
49 | */ |
||
50 | public function is_retryable_error( $error ) { |
||
51 | return ( |
||
52 | 'invalid_request_error' === $error->type || |
||
53 | 'idempotency_error' === $error->type || |
||
54 | 'rate_limit_error' === $error->type || |
||
55 | 'api_connection_error' === $error->type || |
||
56 | 'api_error' === $error->type |
||
57 | ); |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * Checks to see if error is of same idempotency key |
||
62 | * error due to retries with different parameters. |
||
63 | * |
||
64 | * @since 4.1.0 |
||
65 | * @param array $error |
||
66 | */ |
||
67 | public function is_same_idempotency_error( $error ) { |
||
68 | return ( |
||
69 | $error && |
||
0 ignored issues
–
show
|
|||
70 | 'idempotency_error' === $error->type && |
||
71 | preg_match( '/Keys for idempotent requests can only be used with the same parameters they were first used with./i', $error->message ) |
||
72 | ); |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Checks to see if error is of invalid request |
||
77 | * error and it is no such customer. |
||
78 | * |
||
79 | * @since 4.1.0 |
||
80 | * @param array $error |
||
81 | */ |
||
82 | public function is_no_such_customer_error( $error ) { |
||
83 | return ( |
||
84 | $error && |
||
0 ignored issues
–
show
The expression
$error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
85 | 'invalid_request_error' === $error->type && |
||
86 | preg_match( '/No such customer/i', $error->message ) |
||
87 | ); |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Checks to see if error is of invalid request |
||
92 | * error and it is no such token. |
||
93 | * |
||
94 | * @since 4.1.0 |
||
95 | * @param array $error |
||
96 | */ |
||
97 | public function is_no_such_token_error( $error ) { |
||
98 | return ( |
||
99 | $error && |
||
0 ignored issues
–
show
The expression
$error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
100 | 'invalid_request_error' === $error->type && |
||
101 | preg_match( '/No such token/i', $error->message ) |
||
102 | ); |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Checks to see if error is of invalid request |
||
107 | * error and it is no such source. |
||
108 | * |
||
109 | * @since 4.1.0 |
||
110 | * @param array $error |
||
111 | */ |
||
112 | public function is_no_such_source_error( $error ) { |
||
113 | return ( |
||
114 | $error && |
||
0 ignored issues
–
show
The expression
$error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
115 | 'invalid_request_error' === $error->type && |
||
116 | preg_match( '/No such source/i', $error->message ) |
||
117 | ); |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Checks to see if error is of invalid request |
||
122 | * error and it is no such source linked to customer. |
||
123 | * |
||
124 | * @since 4.1.0 |
||
125 | * @param array $error |
||
126 | */ |
||
127 | public function is_no_linked_source_error( $error ) { |
||
128 | return ( |
||
129 | $error && |
||
0 ignored issues
–
show
The expression
$error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
130 | 'invalid_request_error' === $error->type && |
||
131 | preg_match( '/does not have a linked source with ID/i', $error->message ) |
||
132 | ); |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Check to see if we need to update the idempotency |
||
137 | * key to be different from previous charge request. |
||
138 | * |
||
139 | * @since 4.1.0 |
||
140 | * @param object $source_object |
||
141 | * @param object $error |
||
142 | * @return bool |
||
143 | */ |
||
144 | public function need_update_idempotency_key( $source_object, $error ) { |
||
145 | return ( |
||
146 | $error && |
||
147 | 1 < $this->retry_interval && |
||
148 | ! empty( $source_object ) && |
||
149 | 'chargeable' === $source_object->status && |
||
150 | self::is_same_idempotency_error( $error ) |
||
151 | ); |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Checks if keys are set and valid. |
||
156 | * |
||
157 | * @since 4.0.6 |
||
158 | * @return bool 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). |
||
159 | */ |
||
160 | View Code Duplication | public function are_keys_set() { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
161 | // NOTE: updates to this function should be added to are_keys_set() |
||
162 | // in includes/payment-methods/class-wc-stripe-payment-request.php |
||
163 | |||
164 | if ( $this->testmode ) { |
||
165 | return preg_match( '/^pk_test_/', $this->publishable_key ) |
||
166 | && preg_match( '/^[rs]k_test_/', $this->secret_key ); |
||
167 | } else { |
||
168 | return preg_match( '/^pk_live_/', $this->publishable_key ) |
||
169 | && preg_match( '/^[rs]k_live_/', $this->secret_key ); |
||
170 | } |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Check if we need to make gateways available. |
||
175 | * |
||
176 | * @since 4.1.3 |
||
177 | */ |
||
178 | public function is_available() { |
||
179 | if ( 'yes' === $this->enabled ) { |
||
180 | return $this->are_keys_set(); |
||
181 | } |
||
182 | |||
183 | return parent::is_available(); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Checks if we need to process pre orders when |
||
188 | * pre orders is in the cart. |
||
189 | * |
||
190 | * @since 4.1.0 |
||
191 | * @param int $order_id |
||
192 | * @return bool |
||
193 | */ |
||
194 | public function maybe_process_pre_orders( $order_id ) { |
||
195 | return ( |
||
196 | WC_Stripe_Helper::is_pre_orders_exists() && |
||
197 | $this->pre_orders->is_pre_order( $order_id ) && |
||
198 | WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) && |
||
199 | ! is_wc_endpoint_url( 'order-pay' ) |
||
200 | ); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * All payment icons that work with Stripe. Some icons references |
||
205 | * WC core icons. |
||
206 | * |
||
207 | * @since 4.0.0 |
||
208 | * @since 4.1.0 Changed to using img with svg (colored) instead of fonts. |
||
209 | * @return array |
||
210 | */ |
||
211 | public function payment_icons() { |
||
212 | return apply_filters( |
||
213 | 'wc_stripe_payment_icons', |
||
214 | array( |
||
215 | 'visa' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg" class="stripe-visa-icon stripe-icon" alt="Visa" />', |
||
216 | 'amex' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg" class="stripe-amex-icon stripe-icon" alt="American Express" />', |
||
217 | 'mastercard' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg" class="stripe-mastercard-icon stripe-icon" alt="Mastercard" />', |
||
218 | 'discover' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg" class="stripe-discover-icon stripe-icon" alt="Discover" />', |
||
219 | 'diners' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg" class="stripe-diners-icon stripe-icon" alt="Diners" />', |
||
220 | 'jcb' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg" class="stripe-jcb-icon stripe-icon" alt="JCB" />', |
||
221 | 'alipay' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/alipay.svg" class="stripe-alipay-icon stripe-icon" alt="Alipay" />', |
||
222 | 'wechat' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/wechat.svg" class="stripe-wechat-icon stripe-icon" alt="Wechat Pay" />', |
||
223 | 'bancontact' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bancontact.svg" class="stripe-bancontact-icon stripe-icon" alt="Bancontact" />', |
||
224 | 'ideal' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/ideal.svg" class="stripe-ideal-icon stripe-icon" alt="iDeal" />', |
||
225 | 'p24' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/p24.svg" class="stripe-p24-icon stripe-icon" alt="P24" />', |
||
226 | 'giropay' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/giropay.svg" class="stripe-giropay-icon stripe-icon" alt="Giropay" />', |
||
227 | 'eps' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/eps.svg" class="stripe-eps-icon stripe-icon" alt="EPS" />', |
||
228 | 'multibanco' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/multibanco.svg" class="stripe-multibanco-icon stripe-icon" alt="Multibanco" />', |
||
229 | 'sofort' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sofort.svg" class="stripe-sofort-icon stripe-icon" alt="SOFORT" />', |
||
230 | 'sepa' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sepa.svg" class="stripe-sepa-icon stripe-icon" alt="SEPA" />', |
||
231 | ) |
||
232 | ); |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Validates that the order meets the minimum order amount |
||
237 | * set by Stripe. |
||
238 | * |
||
239 | * @since 4.0.0 |
||
240 | * @version 4.0.0 |
||
241 | * @param object $order |
||
242 | */ |
||
243 | public function validate_minimum_order_amount( $order ) { |
||
244 | if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) { |
||
245 | /* translators: 1) dollar amount */ |
||
246 | throw new WC_Stripe_Exception( 'Did not meet minimum amount', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) ); |
||
247 | } |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * Gets the transaction URL linked to Stripe dashboard. |
||
252 | * |
||
253 | * @since 4.0.0 |
||
254 | * @version 4.0.0 |
||
255 | */ |
||
256 | public function get_transaction_url( $order ) { |
||
257 | if ( $this->testmode ) { |
||
258 | $this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s'; |
||
259 | } else { |
||
260 | $this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s'; |
||
261 | } |
||
262 | |||
263 | return parent::get_transaction_url( $order ); |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * Gets the saved customer id if exists. |
||
268 | * |
||
269 | * @since 4.0.0 |
||
270 | * @version 4.0.0 |
||
271 | */ |
||
272 | public function get_stripe_customer_id( $order ) { |
||
273 | $customer = get_user_option( '_stripe_customer_id', $order->get_customer_id() ); |
||
274 | |||
275 | if ( empty( $customer ) ) { |
||
276 | // Try to get it via the order. |
||
277 | return $order->get_meta( '_stripe_customer_id', true ); |
||
278 | } else { |
||
279 | return $customer; |
||
280 | } |
||
281 | |||
282 | return false; |
||
0 ignored issues
–
show
return false; does not seem to be reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Builds the return URL from redirects. |
||
287 | * |
||
288 | * @since 4.0.0 |
||
289 | * @version 4.0.0 |
||
290 | * @param object $order |
||
291 | * @param int $id Stripe session id. |
||
292 | */ |
||
293 | public function get_stripe_return_url( $order = null, $id = null ) { |
||
294 | if ( is_object( $order ) ) { |
||
295 | if ( empty( $id ) ) { |
||
296 | $id = uniqid(); |
||
0 ignored issues
–
show
$id is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
297 | } |
||
298 | |||
299 | $order_id = $order->get_id(); |
||
300 | |||
301 | $args = array( |
||
302 | 'utm_nooverride' => '1', |
||
303 | 'order_id' => $order_id, |
||
304 | ); |
||
305 | |||
306 | return wp_sanitize_redirect( esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) ) ); |
||
307 | } |
||
308 | |||
309 | return wp_sanitize_redirect( esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) ) ); |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * Is $order_id a subscription? |
||
314 | * @param int $order_id |
||
315 | * @return boolean |
||
316 | */ |
||
317 | public function has_subscription( $order_id ) { |
||
318 | return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) ); |
||
319 | } |
||
320 | |||
321 | /** |
||
322 | * Generate the request for the payment. |
||
323 | * |
||
324 | * @since 3.1.0 |
||
325 | * @version 4.5.4 |
||
326 | * @param WC_Order $order |
||
327 | * @param object $prepared_source |
||
328 | * @return array() |
||
0 ignored issues
–
show
The doc-type
array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. ![]() |
|||
329 | */ |
||
330 | public function generate_payment_request( $order, $prepared_source ) { |
||
331 | $settings = get_option( 'woocommerce_stripe_settings', array() ); |
||
332 | $statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : ''; |
||
333 | $capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false; |
||
334 | $post_data = array(); |
||
335 | $post_data['currency'] = strtolower( $order->get_currency() ); |
||
336 | $post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] ); |
||
337 | /* translators: 1) blog name 2) order number */ |
||
338 | $post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() ); |
||
339 | $billing_email = $order->get_billing_email(); |
||
340 | $billing_first_name = $order->get_billing_first_name(); |
||
341 | $billing_last_name = $order->get_billing_last_name(); |
||
342 | |||
343 | if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) { |
||
344 | $post_data['receipt_email'] = $billing_email; |
||
345 | } |
||
346 | |||
347 | switch ( $order->get_payment_method() ) { |
||
348 | case 'stripe': |
||
349 | if ( ! empty( $statement_descriptor ) ) { |
||
350 | $post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor ); |
||
351 | } |
||
352 | |||
353 | $post_data['capture'] = $capture ? 'true' : 'false'; |
||
354 | break; |
||
355 | case 'stripe_sepa': |
||
356 | if ( ! empty( $statement_descriptor ) ) { |
||
357 | $post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor ); |
||
358 | } |
||
359 | break; |
||
360 | } |
||
361 | |||
362 | if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) { |
||
363 | $post_data['shipping'] = array( |
||
364 | 'name' => trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() ), |
||
365 | 'address' => array( |
||
366 | 'line1' => $order->get_shipping_address_1(), |
||
367 | 'line2' => $order->get_shipping_address_2(), |
||
368 | 'city' => $order->get_shipping_city(), |
||
369 | 'country' => $order->get_shipping_country(), |
||
370 | 'postal_code' => $order->get_shipping_postcode(), |
||
371 | 'state' => $order->get_shipping_state(), |
||
372 | ) |
||
373 | ); |
||
374 | } |
||
375 | |||
376 | $post_data['expand[]'] = 'balance_transaction'; |
||
377 | |||
378 | $metadata = array( |
||
379 | __( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ), |
||
380 | __( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ), |
||
381 | 'order_id' => $order->get_order_number(), |
||
382 | 'site_url' => esc_url( get_site_url() ), |
||
383 | ); |
||
384 | |||
385 | if ( $this->has_subscription( $order->get_id() ) ) { |
||
386 | $metadata += array( |
||
387 | 'payment_type' => 'recurring', |
||
388 | ); |
||
389 | } |
||
390 | |||
391 | $post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_source ); |
||
392 | |||
393 | if ( $prepared_source->customer ) { |
||
394 | $post_data['customer'] = $prepared_source->customer; |
||
395 | } |
||
396 | |||
397 | if ( $prepared_source->source ) { |
||
398 | $post_data['source'] = $prepared_source->source; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request. |
||
403 | * |
||
404 | * @since 3.1.0 |
||
405 | * @param array $post_data |
||
406 | * @param WC_Order $order |
||
407 | * @param object $source |
||
408 | */ |
||
409 | return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_source ); |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * Store extra meta data for an order from a Stripe Response. |
||
414 | */ |
||
415 | public function process_response( $response, $order ) { |
||
416 | WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) ); |
||
417 | |||
418 | $order_id = $order->get_id(); |
||
419 | $captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no'; |
||
420 | |||
421 | // Store charge data. |
||
422 | $order->update_meta_data( '_stripe_charge_captured', $captured ); |
||
423 | |||
424 | if ( isset( $response->balance_transaction ) ) { |
||
425 | $this->update_fees( $order, is_string( $response->balance_transaction ) ? $response->balance_transaction : $response->balance_transaction->id ); |
||
426 | } |
||
427 | |||
428 | if ( 'yes' === $captured ) { |
||
429 | /** |
||
430 | * Charge can be captured but in a pending state. Payment methods |
||
431 | * that are asynchronous may take couple days to clear. Webhook will |
||
432 | * take care of the status changes. |
||
433 | */ |
||
434 | if ( 'pending' === $response->status ) { |
||
435 | $order_stock_reduced = $order->get_meta( '_order_stock_reduced', true ); |
||
436 | |||
437 | if ( ! $order_stock_reduced ) { |
||
438 | wc_reduce_stock_levels( $order_id ); |
||
439 | } |
||
440 | |||
441 | $order->set_transaction_id( $response->id ); |
||
442 | /* translators: transaction id */ |
||
443 | $order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) ); |
||
444 | } |
||
445 | |||
446 | if ( 'succeeded' === $response->status ) { |
||
447 | $order->payment_complete( $response->id ); |
||
448 | |||
449 | /* translators: transaction id */ |
||
450 | $message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id ); |
||
451 | $order->add_order_note( $message ); |
||
452 | } |
||
453 | |||
454 | if ( 'failed' === $response->status ) { |
||
455 | $localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' ); |
||
456 | $order->add_order_note( $localized_message ); |
||
457 | throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message ); |
||
458 | } |
||
459 | } else { |
||
460 | $order->set_transaction_id( $response->id ); |
||
461 | |||
462 | if ( $order->has_status( array( 'pending', 'failed' ) ) ) { |
||
463 | wc_reduce_stock_levels( $order_id ); |
||
464 | } |
||
465 | |||
466 | /* translators: transaction id */ |
||
467 | $order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization.', 'woocommerce-gateway-stripe' ), $response->id ) ); |
||
468 | } |
||
469 | |||
470 | if ( is_callable( array( $order, 'save' ) ) ) { |
||
471 | $order->save(); |
||
472 | } |
||
473 | |||
474 | do_action( 'wc_gateway_stripe_process_response', $response, $order ); |
||
475 | |||
476 | return $response; |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * Sends the failed order email to admin. |
||
481 | * |
||
482 | * @since 3.1.0 |
||
483 | * @version 4.0.0 |
||
484 | * @param int $order_id |
||
485 | * @return null |
||
486 | */ |
||
487 | public function send_failed_order_email( $order_id ) { |
||
488 | $emails = WC()->mailer()->get_emails(); |
||
489 | if ( ! empty( $emails ) && ! empty( $order_id ) ) { |
||
490 | $emails['WC_Email_Failed_Order']->trigger( $order_id ); |
||
491 | } |
||
492 | } |
||
493 | |||
494 | /** |
||
495 | * Get owner details. |
||
496 | * |
||
497 | * @since 4.0.0 |
||
498 | * @version 4.0.0 |
||
499 | * @param object $order |
||
500 | * @return object $details |
||
501 | */ |
||
502 | public function get_owner_details( $order ) { |
||
503 | $billing_first_name = $order->get_billing_first_name(); |
||
504 | $billing_last_name = $order->get_billing_last_name(); |
||
505 | |||
506 | $details = array(); |
||
507 | |||
508 | $name = $billing_first_name . ' ' . $billing_last_name; |
||
509 | $email = $order->get_billing_email(); |
||
510 | $phone = $order->get_billing_phone(); |
||
511 | |||
512 | if ( ! empty( $phone ) ) { |
||
513 | $details['phone'] = $phone; |
||
514 | } |
||
515 | |||
516 | if ( ! empty( $name ) ) { |
||
517 | $details['name'] = $name; |
||
518 | } |
||
519 | |||
520 | if ( ! empty( $email ) ) { |
||
521 | $details['email'] = $email; |
||
522 | } |
||
523 | |||
524 | $details['address']['line1'] = $order->get_billing_address_1(); |
||
525 | $details['address']['line2'] = $order->get_billing_address_2(); |
||
526 | $details['address']['state'] = $order->get_billing_state(); |
||
527 | $details['address']['city'] = $order->get_billing_city(); |
||
528 | $details['address']['postal_code'] = $order->get_billing_postcode(); |
||
529 | $details['address']['country'] = $order->get_billing_country(); |
||
530 | |||
531 | return (object) apply_filters( 'wc_stripe_owner_details', $details, $order ); |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * Get source object by source id. |
||
536 | * |
||
537 | * @since 4.0.3 |
||
538 | * @param string $source_id The source ID to get source object for. |
||
539 | */ |
||
540 | public function get_source_object( $source_id = '' ) { |
||
541 | if ( empty( $source_id ) ) { |
||
542 | return ''; |
||
543 | } |
||
544 | |||
545 | $source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id ); |
||
546 | |||
547 | if ( ! empty( $source_object->error ) ) { |
||
548 | throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message ); |
||
549 | } |
||
550 | |||
551 | return $source_object; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * Checks if card is a prepaid card. |
||
556 | * |
||
557 | * @since 4.0.6 |
||
558 | * @param object $source_object |
||
559 | * @return bool |
||
560 | */ |
||
561 | public function is_prepaid_card( $source_object ) { |
||
562 | return ( |
||
563 | $source_object |
||
564 | && ( 'token' === $source_object->object || 'source' === $source_object->object ) |
||
565 | && 'prepaid' === $source_object->card->funding |
||
566 | ); |
||
567 | } |
||
568 | |||
569 | /** |
||
570 | * Checks if source is of legacy type card. |
||
571 | * |
||
572 | * @since 4.0.8 |
||
573 | * @param string $source_id |
||
574 | * @return bool |
||
575 | */ |
||
576 | public function is_type_legacy_card( $source_id ) { |
||
577 | return ( preg_match( '/^card_/', $source_id ) ); |
||
578 | } |
||
579 | |||
580 | /** |
||
581 | * Checks if payment is via saved payment source. |
||
582 | * |
||
583 | * @since 4.1.0 |
||
584 | * @return bool |
||
585 | */ |
||
586 | public function is_using_saved_payment_method() { |
||
587 | $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe'; |
||
588 | |||
589 | return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ); |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * Get payment source. This can be a new token/source or existing WC token. |
||
594 | * If user is logged in and/or has WC account, create an account on Stripe. |
||
595 | * This way we can attribute the payment to the user to better fight fraud. |
||
596 | * |
||
597 | * @since 3.1.0 |
||
598 | * @version 4.0.0 |
||
599 | * @param string $user_id |
||
600 | * @param bool $force_save_source Should we force save payment source. |
||
601 | * |
||
602 | * @throws Exception When card was not added or for and invalid card. |
||
603 | * @return object |
||
604 | */ |
||
605 | public function prepare_source( $user_id, $force_save_source = false, $existing_customer_id = null ) { |
||
606 | $customer = new WC_Stripe_Customer( $user_id ); |
||
607 | if ( ! empty( $existing_customer_id ) ) { |
||
608 | $customer->set_id( $existing_customer_id ); |
||
609 | } |
||
610 | |||
611 | $force_save_source = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer ); |
||
612 | $source_object = ''; |
||
613 | $source_id = ''; |
||
614 | $wc_token_id = false; |
||
615 | $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe'; |
||
616 | $is_token = false; |
||
617 | |||
618 | // New CC info was entered and we have a new source to process. |
||
619 | if ( ! empty( $_POST['stripe_source'] ) ) { |
||
620 | $source_object = self::get_source_object( wc_clean( $_POST['stripe_source'] ) ); |
||
621 | $source_id = $source_object->id; |
||
622 | |||
623 | // This checks to see if customer opted to save the payment method to file. |
||
624 | $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); |
||
625 | |||
626 | /** |
||
627 | * This is true if the user wants to store the card to their account. |
||
628 | * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is |
||
629 | * actually reusable. Either that or force_save_source is true. |
||
630 | */ |
||
631 | if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) { |
||
632 | $response = $customer->add_source( $source_object->id ); |
||
633 | |||
634 | View Code Duplication | if ( ! empty( $response->error ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
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. ![]() |
|||
635 | throw new WC_Stripe_Exception( print_r( $response, true ), $this->get_localized_error_message_from_response( $response ) ); |
||
636 | } |
||
637 | } |
||
638 | } elseif ( $this->is_using_saved_payment_method() ) { |
||
639 | // Use an existing token, and then process the payment. |
||
640 | $wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ); |
||
641 | $wc_token = WC_Payment_Tokens::get( $wc_token_id ); |
||
642 | |||
643 | if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) { |
||
644 | WC()->session->set( 'refresh_totals', true ); |
||
645 | throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) ); |
||
646 | } |
||
647 | |||
648 | $source_id = $wc_token->get_token(); |
||
649 | |||
650 | if ( $this->is_type_legacy_card( $source_id ) ) { |
||
651 | $is_token = true; |
||
652 | } |
||
653 | } elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) { |
||
654 | $stripe_token = wc_clean( $_POST['stripe_token'] ); |
||
655 | $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); |
||
656 | |||
657 | // This is true if the user wants to store the card to their account. |
||
658 | if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) { |
||
659 | $response = $customer->add_source( $stripe_token ); |
||
660 | |||
661 | View Code Duplication | if ( ! empty( $response->error ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
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. ![]() |
|||
662 | throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message ); |
||
663 | } |
||
664 | $source_id = $response; |
||
665 | } else { |
||
666 | $source_id = $stripe_token; |
||
667 | $is_token = true; |
||
668 | } |
||
669 | } |
||
670 | |||
671 | $customer_id = $customer->get_id(); |
||
672 | if ( ! $customer_id ) { |
||
673 | $customer->set_id( $customer->create_customer() ); |
||
674 | $customer_id = $customer->get_id(); |
||
675 | } else { |
||
676 | $customer_id = $customer->update_customer(); |
||
677 | } |
||
678 | |||
679 | if ( empty( $source_object ) && ! $is_token ) { |
||
680 | $source_object = self::get_source_object( $source_id ); |
||
681 | } |
||
682 | |||
683 | return (object) array( |
||
684 | 'token_id' => $wc_token_id, |
||
685 | 'customer' => $customer_id, |
||
686 | 'source' => $source_id, |
||
687 | 'source_object' => $source_object, |
||
688 | ); |
||
689 | } |
||
690 | |||
691 | /** |
||
692 | * Get payment source from an order. This could be used in the future for |
||
693 | * a subscription as an example, therefore using the current user ID would |
||
694 | * not work - the customer won't be logged in :) |
||
695 | * |
||
696 | * Not using 2.6 tokens for this part since we need a customer AND a card |
||
697 | * token, and not just one. |
||
698 | * |
||
699 | * @since 3.1.0 |
||
700 | * @version 4.0.0 |
||
701 | * @param object $order |
||
702 | * @return object |
||
703 | */ |
||
704 | public function prepare_order_source( $order = null ) { |
||
705 | $stripe_customer = new WC_Stripe_Customer(); |
||
706 | $stripe_source = false; |
||
707 | $token_id = false; |
||
708 | $source_object = false; |
||
709 | |||
710 | if ( $order ) { |
||
711 | $order_id = $order->get_id(); |
||
712 | |||
713 | $stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true ); |
||
714 | |||
715 | if ( $stripe_customer_id ) { |
||
716 | $stripe_customer->set_id( $stripe_customer_id ); |
||
717 | } |
||
718 | |||
719 | $source_id = $order->get_meta( '_stripe_source_id', true ); |
||
720 | |||
721 | // Since 4.0.0, we changed card to source so we need to account for that. |
||
722 | if ( empty( $source_id ) ) { |
||
723 | $source_id = $order->get_meta( '_stripe_card_id', true ); |
||
724 | |||
725 | // Take this opportunity to update the key name. |
||
726 | $order->update_meta_data( '_stripe_source_id', $source_id ); |
||
727 | |||
728 | if ( is_callable( array( $order, 'save' ) ) ) { |
||
729 | $order->save(); |
||
730 | } |
||
731 | } |
||
732 | |||
733 | if ( $source_id ) { |
||
734 | $stripe_source = $source_id; |
||
735 | $source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id ); |
||
736 | } elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) { |
||
737 | /* |
||
738 | * We can attempt to charge the customer's default source |
||
739 | * by sending empty source id. |
||
740 | */ |
||
741 | $stripe_source = ''; |
||
742 | } |
||
743 | } |
||
744 | |||
745 | return (object) array( |
||
746 | 'token_id' => $token_id, |
||
747 | 'customer' => $stripe_customer ? $stripe_customer->get_id() : false, |
||
748 | 'source' => $stripe_source, |
||
749 | 'source_object' => $source_object, |
||
750 | ); |
||
751 | } |
||
752 | |||
753 | /** |
||
754 | * Save source to order. |
||
755 | * |
||
756 | * @since 3.1.0 |
||
757 | * @version 4.0.0 |
||
758 | * @param WC_Order $order For to which the source applies. |
||
759 | * @param stdClass $source Source information. |
||
760 | */ |
||
761 | public function save_source_to_order( $order, $source ) { |
||
762 | // Store source in the order. |
||
763 | if ( $source->customer ) { |
||
764 | $order->update_meta_data( '_stripe_customer_id', $source->customer ); |
||
765 | } |
||
766 | |||
767 | if ( $source->source ) { |
||
768 | $order->update_meta_data( '_stripe_source_id', $source->source ); |
||
769 | } |
||
770 | |||
771 | if ( is_callable( array( $order, 'save' ) ) ) { |
||
772 | $order->save(); |
||
773 | } |
||
774 | } |
||
775 | |||
776 | /** |
||
777 | * Updates Stripe fees/net. |
||
778 | * e.g usage would be after a refund. |
||
779 | * |
||
780 | * @since 4.0.0 |
||
781 | * @version 4.0.6 |
||
782 | * @param object $order The order object |
||
783 | * @param int $balance_transaction_id |
||
784 | */ |
||
785 | public function update_fees( $order, $balance_transaction_id ) { |
||
786 | $balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id ); |
||
787 | |||
788 | if ( empty( $balance_transaction->error ) ) { |
||
789 | if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) { |
||
790 | // Fees and Net needs to both come from Stripe to be accurate as the returned |
||
791 | // values are in the local currency of the Stripe account, not from WC. |
||
792 | $fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0; |
||
793 | $net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0; |
||
794 | |||
795 | // Current data fee & net. |
||
796 | $fee_current = WC_Stripe_Helper::get_stripe_fee( $order ); |
||
797 | $net_current = WC_Stripe_Helper::get_stripe_net( $order ); |
||
798 | |||
799 | // Calculation. |
||
800 | $fee = (float) $fee_current + (float) $fee_refund; |
||
801 | $net = (float) $net_current + (float) $net_refund; |
||
802 | |||
803 | WC_Stripe_Helper::update_stripe_fee( $order, $fee ); |
||
804 | WC_Stripe_Helper::update_stripe_net( $order, $net ); |
||
805 | |||
806 | $currency = ! empty( $balance_transaction->currency ) ? strtoupper( $balance_transaction->currency ) : null; |
||
807 | WC_Stripe_Helper::update_stripe_currency( $order, $currency ); |
||
808 | |||
809 | if ( is_callable( array( $order, 'save' ) ) ) { |
||
810 | $order->save(); |
||
811 | } |
||
812 | } |
||
813 | } else { |
||
814 | WC_Stripe_Logger::log( 'Unable to update fees/net meta for order: ' . $order->get_id() ); |
||
815 | } |
||
816 | } |
||
817 | |||
818 | /** |
||
819 | * Refund a charge. |
||
820 | * |
||
821 | * @since 3.1.0 |
||
822 | * @version 4.0.0 |
||
823 | * @param int $order_id |
||
824 | * @param float $amount |
||
825 | * @return bool |
||
826 | */ |
||
827 | public function process_refund( $order_id, $amount = null, $reason = '' ) { |
||
828 | $order = wc_get_order( $order_id ); |
||
829 | |||
830 | if ( ! $order ) { |
||
831 | return false; |
||
832 | } |
||
833 | |||
834 | $request = array(); |
||
835 | |||
836 | $order_currency = $order->get_currency(); |
||
837 | $captured = $order->get_meta( '_stripe_charge_captured', true ); |
||
838 | $charge_id = $order->get_transaction_id(); |
||
839 | |||
840 | if ( ! $charge_id ) { |
||
841 | return false; |
||
842 | } |
||
843 | |||
844 | if ( ! is_null( $amount ) ) { |
||
845 | $request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency ); |
||
846 | } |
||
847 | |||
848 | // If order is only authorized, don't pass amount. |
||
849 | if ( 'yes' !== $captured ) { |
||
850 | unset( $request['amount'] ); |
||
851 | } |
||
852 | |||
853 | if ( $reason ) { |
||
854 | $request['metadata'] = array( |
||
855 | 'reason' => $reason, |
||
856 | ); |
||
857 | } |
||
858 | |||
859 | $request['charge'] = $charge_id; |
||
860 | WC_Stripe_Logger::log( "Info: Beginning refund for order {$charge_id} for the amount of {$amount}" ); |
||
861 | |||
862 | $request = apply_filters( 'wc_stripe_refund_request', $request, $order ); |
||
863 | |||
864 | $intent = $this->get_intent_from_order( $order ); |
||
865 | $intent_cancelled = false; |
||
866 | if ( $intent ) { |
||
867 | // If the order has a Payment Intent pending capture, then the Intent itself must be refunded (cancelled), not the Charge |
||
868 | if ( ! empty( $intent->error ) ) { |
||
869 | $response = $intent; |
||
870 | $intent_cancelled = true; |
||
871 | } elseif ( 'requires_capture' === $intent->status ) { |
||
872 | $result = WC_Stripe_API::request( |
||
873 | array(), |
||
874 | 'payment_intents/' . $intent->id . '/cancel' |
||
875 | ); |
||
876 | $intent_cancelled = true; |
||
877 | |||
878 | if ( ! empty( $result->error ) ) { |
||
879 | $response = $result; |
||
880 | } else { |
||
881 | $charge = end( $result->charges->data ); |
||
882 | $response = end( $charge->refunds->data ); |
||
883 | } |
||
884 | } |
||
885 | } |
||
886 | |||
887 | if ( ! $intent_cancelled ) { |
||
888 | $response = WC_Stripe_API::request( $request, 'refunds' ); |
||
889 | } |
||
890 | |||
891 | if ( ! empty( $response->error ) ) { |
||
892 | WC_Stripe_Logger::log( 'Error: ' . $response->error->message ); |
||
0 ignored issues
–
show
The variable
$response does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
893 | |||
894 | return $response; |
||
895 | |||
896 | } elseif ( ! empty( $response->id ) ) { |
||
897 | $order->update_meta_data( '_stripe_refund_id', $response->id ); |
||
898 | |||
899 | $amount = wc_price( $response->amount / 100 ); |
||
900 | |||
901 | if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) { |
||
902 | $amount = wc_price( $response->amount ); |
||
903 | } |
||
904 | |||
905 | if ( isset( $response->balance_transaction ) ) { |
||
906 | $this->update_fees( $order, $response->balance_transaction ); |
||
907 | } |
||
908 | |||
909 | /* translators: 1) dollar amount 2) transaction id 3) refund message */ |
||
910 | $refund_message = ( isset( $captured ) && 'yes' === $captured ) ? sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), $amount, $response->id, $reason ) : __( 'Pre-Authorization Released', 'woocommerce-gateway-stripe' ); |
||
911 | |||
912 | $order->add_order_note( $refund_message ); |
||
913 | WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( wp_strip_all_tags( $refund_message ) ) ); |
||
914 | |||
915 | return true; |
||
916 | } |
||
917 | } |
||
918 | |||
919 | /** |
||
920 | * Add payment method via account screen. |
||
921 | * We don't store the token locally, but to the Stripe API. |
||
922 | * |
||
923 | * @since 3.0.0 |
||
924 | * @version 4.0.0 |
||
925 | */ |
||
926 | public function add_payment_method() { |
||
927 | $error = false; |
||
928 | $error_msg = __( 'There was a problem adding the payment method.', 'woocommerce-gateway-stripe' ); |
||
929 | $source_id = ''; |
||
930 | |||
931 | if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) { |
||
932 | $error = true; |
||
933 | } |
||
934 | |||
935 | $stripe_customer = new WC_Stripe_Customer( get_current_user_id() ); |
||
936 | |||
937 | $source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : ''; |
||
938 | |||
939 | $source_object = WC_Stripe_API::retrieve( 'sources/' . $source ); |
||
940 | |||
941 | if ( isset( $source_object ) ) { |
||
942 | if ( ! empty( $source_object->error ) ) { |
||
943 | $error = true; |
||
944 | } |
||
945 | |||
946 | $source_id = $source_object->id; |
||
947 | } elseif ( isset( $_POST['stripe_token'] ) ) { |
||
948 | $source_id = wc_clean( $_POST['stripe_token'] ); |
||
949 | } |
||
950 | |||
951 | $response = $stripe_customer->add_source( $source_id ); |
||
952 | |||
953 | if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) { |
||
954 | $error = true; |
||
955 | } |
||
956 | |||
957 | if ( $error ) { |
||
958 | wc_add_notice( $error_msg, 'error' ); |
||
959 | WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg ); |
||
960 | return; |
||
961 | } |
||
962 | |||
963 | do_action( 'wc_stripe_add_payment_method_' . $_POST['payment_method'] . '_success', $source_id, $source_object ); |
||
964 | |||
965 | return array( |
||
966 | 'result' => 'success', |
||
967 | 'redirect' => wc_get_endpoint_url( 'payment-methods' ), |
||
968 | ); |
||
969 | } |
||
970 | |||
971 | /** |
||
972 | * Gets the locale with normalization that only Stripe accepts. |
||
973 | * |
||
974 | * @since 4.0.6 |
||
975 | * @return string $locale |
||
976 | */ |
||
977 | public function get_locale() { |
||
978 | $locale = get_locale(); |
||
979 | |||
980 | /* |
||
981 | * Stripe expects Norwegian to only be passed NO. |
||
982 | * But WP has different dialects. |
||
983 | */ |
||
984 | if ( 'NO' === substr( $locale, 3, 2 ) ) { |
||
985 | $locale = 'no'; |
||
986 | } else { |
||
987 | $locale = substr( get_locale(), 0, 2 ); |
||
988 | } |
||
989 | |||
990 | return $locale; |
||
991 | } |
||
992 | |||
993 | /** |
||
994 | * Change the idempotency key so charge can |
||
995 | * process order as a different transaction. |
||
996 | * |
||
997 | * @since 4.0.6 |
||
998 | * @param string $idempotency_key |
||
999 | * @param array $request |
||
1000 | */ |
||
1001 | public function change_idempotency_key( $idempotency_key, $request ) { |
||
1002 | $customer = ! empty( $request['customer'] ) ? $request['customer'] : ''; |
||
1003 | $source = ! empty( $request['source'] ) ? $request['source'] : $customer; |
||
1004 | $count = $this->retry_interval; |
||
1005 | |||
1006 | return $request['metadata']['order_id'] . '-' . $count . '-' . $source; |
||
1007 | } |
||
1008 | |||
1009 | /** |
||
1010 | * Checks if request is the original to prevent double processing |
||
1011 | * on WC side. The original-request header and request-id header |
||
1012 | * needs to be the same to mean its the original request. |
||
1013 | * |
||
1014 | * @since 4.0.6 |
||
1015 | * @param array $headers |
||
1016 | */ |
||
1017 | public function is_original_request( $headers ) { |
||
1018 | if ( $headers['original-request'] === $headers['request-id'] ) { |
||
1019 | return true; |
||
1020 | } |
||
1021 | |||
1022 | return false; |
||
1023 | } |
||
1024 | |||
1025 | /** |
||
1026 | * Generates the request when creating a new payment intent. |
||
1027 | * |
||
1028 | * @param WC_Order $order The order that is being paid for. |
||
1029 | * @param object $prepared_source The source that is used for the payment. |
||
1030 | * @return array The arguments for the request. |
||
1031 | */ |
||
1032 | public function generate_create_intent_request( $order, $prepared_source ) { |
||
1033 | // The request for a charge contains metadata for the intent. |
||
1034 | $full_request = $this->generate_payment_request( $order, $prepared_source ); |
||
1035 | |||
1036 | $request = array( |
||
1037 | 'source' => $prepared_source->source, |
||
1038 | 'amount' => WC_Stripe_Helper::get_stripe_amount( $order->get_total() ), |
||
1039 | 'currency' => strtolower( $order->get_currency() ), |
||
1040 | 'description' => $full_request['description'], |
||
1041 | 'metadata' => $full_request['metadata'], |
||
1042 | 'capture_method' => ( 'true' === $full_request['capture'] ) ? 'automatic' : 'manual', |
||
1043 | 'payment_method_types' => array( |
||
1044 | 'card', |
||
1045 | ), |
||
1046 | ); |
||
1047 | |||
1048 | if ( $prepared_source->customer ) { |
||
1049 | $request['customer'] = $prepared_source->customer; |
||
1050 | } |
||
1051 | |||
1052 | if ( isset( $full_request['statement_descriptor'] ) ) { |
||
1053 | $request['statement_descriptor'] = $full_request['statement_descriptor']; |
||
1054 | } |
||
1055 | |||
1056 | if ( isset( $full_request['shipping'] ) ) { |
||
1057 | $request['shipping'] = $full_request['shipping']; |
||
1058 | } |
||
1059 | |||
1060 | /** |
||
1061 | * Filter the return value of the WC_Payment_Gateway_CC::generate_create_intent_request. |
||
1062 | * |
||
1063 | * @since 3.1.0 |
||
1064 | * @param array $request |
||
1065 | * @param WC_Order $order |
||
1066 | * @param object $source |
||
1067 | */ |
||
1068 | return apply_filters( 'wc_stripe_generate_create_intent_request', $request, $order, $prepared_source ); |
||
1069 | } |
||
1070 | |||
1071 | /** |
||
1072 | * Create the level 3 data array to send to Stripe when making a purchase. |
||
1073 | * |
||
1074 | * @param WC_Order $order The order that is being paid for. |
||
1075 | * @return array The level 3 data to send to Stripe. |
||
1076 | */ |
||
1077 | public function get_level3_data_from_order( $order ) { |
||
1078 | // Get the order items. Don't need their keys, only their values. |
||
1079 | // Order item IDs are used as keys in the original order items array. |
||
1080 | $order_items = array_values( $order->get_items() ); |
||
1081 | $currency = $order->get_currency(); |
||
1082 | |||
1083 | $stripe_line_items = array_map(function( $item ) use ( $currency ) { |
||
1084 | $product_id = $item->get_variation_id() |
||
1085 | ? $item->get_variation_id() |
||
1086 | : $item->get_product_id(); |
||
1087 | $product_description = substr( $item->get_name(), 0, 26 ); |
||
1088 | $quantity = $item->get_quantity(); |
||
1089 | $unit_cost = WC_Stripe_Helper::get_stripe_amount( ( $item->get_subtotal() / $quantity ), $currency ); |
||
1090 | $tax_amount = WC_Stripe_Helper::get_stripe_amount( $item->get_total_tax(), $currency ); |
||
1091 | $discount_amount = WC_Stripe_Helper::get_stripe_amount( $item->get_subtotal() - $item->get_total(), $currency ); |
||
1092 | |||
1093 | return (object) array( |
||
1094 | 'product_code' => (string) $product_id, // Up to 12 characters that uniquely identify the product. |
||
1095 | 'product_description' => $product_description, // Up to 26 characters long describing the product. |
||
1096 | 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer. |
||
1097 | 'quantity' => $quantity, // The number of items of this type sold, as a non-negative integer. |
||
1098 | 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer. |
||
1099 | 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer. |
||
1100 | ); |
||
1101 | }, $order_items); |
||
1102 | |||
1103 | $level3_data = array( |
||
1104 | 'merchant_reference' => $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”. |
||
1105 | 'shipping_amount' => WC_Stripe_Helper::get_stripe_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency), // The shipping cost, in cents, as a non-negative integer. |
||
1106 | 'line_items' => $stripe_line_items, |
||
1107 | ); |
||
1108 | |||
1109 | // The customer’s U.S. shipping ZIP code. |
||
1110 | $shipping_address_zip = $order->get_shipping_postcode(); |
||
1111 | if ( $this->is_valid_us_zip_code( $shipping_address_zip ) ) { |
||
1112 | $level3_data['shipping_address_zip'] = $shipping_address_zip; |
||
1113 | } |
||
1114 | |||
1115 | // The merchant’s U.S. shipping ZIP code. |
||
1116 | $store_postcode = get_option( 'woocommerce_store_postcode' ); |
||
1117 | if ( $this->is_valid_us_zip_code( $store_postcode ) ) { |
||
1118 | $level3_data['shipping_from_zip'] = $store_postcode; |
||
1119 | } |
||
1120 | |||
1121 | return $level3_data; |
||
1122 | } |
||
1123 | |||
1124 | /** |
||
1125 | * Create a new PaymentIntent. |
||
1126 | * |
||
1127 | * @param WC_Order $order The order that is being paid for. |
||
1128 | * @param object $prepared_source The source that is used for the payment. |
||
1129 | * @return object An intent or an error. |
||
1130 | */ |
||
1131 | public function create_intent( $order, $prepared_source ) { |
||
1132 | $request = $this->generate_create_intent_request( $order, $prepared_source ); |
||
1133 | |||
1134 | // Create an intent that awaits an action. |
||
1135 | $intent = WC_Stripe_API::request( $request, 'payment_intents' ); |
||
1136 | if ( ! empty( $intent->error ) ) { |
||
1137 | return $intent; |
||
1138 | } |
||
1139 | |||
1140 | $order_id = $order->get_id(); |
||
1141 | WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id initiated for order $order_id" ); |
||
1142 | |||
1143 | // Save the intent ID to the order. |
||
1144 | $this->save_intent_to_order( $order, $intent ); |
||
1145 | |||
1146 | return $intent; |
||
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * Updates an existing intent with updated amount, source, and customer. |
||
1151 | * |
||
1152 | * @param object $intent The existing intent object. |
||
1153 | * @param WC_Order $order The order. |
||
1154 | * @param object $prepared_source Currently selected source. |
||
1155 | * @return object An updated intent. |
||
1156 | */ |
||
1157 | public function update_existing_intent( $intent, $order, $prepared_source ) { |
||
1158 | $request = array(); |
||
1159 | |||
1160 | if ( $prepared_source->source !== $intent->source ) { |
||
1161 | $request['source'] = $prepared_source->source; |
||
1162 | } |
||
1163 | |||
1164 | $new_amount = WC_Stripe_Helper::get_stripe_amount( $order->get_total() ); |
||
1165 | if ( $intent->amount !== $new_amount ) { |
||
1166 | $request['amount'] = $new_amount; |
||
1167 | } |
||
1168 | |||
1169 | if ( $prepared_source->customer && $intent->customer !== $prepared_source->customer ) { |
||
1170 | $request['customer'] = $prepared_source->customer; |
||
1171 | } |
||
1172 | |||
1173 | if ( $this->has_subscription( $order ) ) { |
||
1174 | // If this is a failed subscription order payment, the intent should be |
||
1175 | // prepared for future usage. |
||
1176 | $request['setup_future_usage'] = 'off_session'; |
||
1177 | } |
||
1178 | |||
1179 | if ( empty( $request ) ) { |
||
1180 | return $intent; |
||
1181 | } |
||
1182 | |||
1183 | $level3_data = $this->get_level3_data_from_order( $order ); |
||
1184 | return WC_Stripe_API::request_with_level3_data( |
||
1185 | $request, |
||
1186 | "payment_intents/$intent->id", |
||
1187 | $level3_data, |
||
1188 | $order |
||
1189 | ); |
||
1190 | } |
||
1191 | |||
1192 | /** |
||
1193 | * Confirms an intent if it is the `requires_confirmation` state. |
||
1194 | * |
||
1195 | * @since 4.2.1 |
||
1196 | * @param object $intent The intent to confirm. |
||
1197 | * @param WC_Order $order The order that the intent is associated with. |
||
1198 | * @param object $prepared_source The source that is being charged. |
||
1199 | * @return object Either an error or the updated intent. |
||
1200 | */ |
||
1201 | public function confirm_intent( $intent, $order, $prepared_source ) { |
||
1202 | if ( 'requires_confirmation' !== $intent->status ) { |
||
1203 | return $intent; |
||
1204 | } |
||
1205 | |||
1206 | // Try to confirm the intent & capture the charge (if 3DS is not required). |
||
1207 | $confirm_request = array( |
||
1208 | 'source' => $prepared_source->source, |
||
1209 | ); |
||
1210 | |||
1211 | $level3_data = $this->get_level3_data_from_order( $order ); |
||
1212 | $confirmed_intent = WC_Stripe_API::request_with_level3_data( |
||
1213 | $confirm_request, |
||
1214 | "payment_intents/$intent->id/confirm", |
||
1215 | $level3_data, |
||
1216 | $order |
||
1217 | ); |
||
1218 | |||
1219 | if ( ! empty( $confirmed_intent->error ) ) { |
||
1220 | return $confirmed_intent; |
||
1221 | } |
||
1222 | |||
1223 | // Save a note about the status of the intent. |
||
1224 | $order_id = $order->get_id(); |
||
1225 | if ( 'succeeded' === $confirmed_intent->status ) { |
||
1226 | WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id succeeded for order $order_id" ); |
||
1227 | } elseif ( 'requires_action' === $confirmed_intent->status ) { |
||
1228 | WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id requires authentication for order $order_id" ); |
||
1229 | } |
||
1230 | |||
1231 | return $confirmed_intent; |
||
1232 | } |
||
1233 | |||
1234 | /** |
||
1235 | * Saves intent to order. |
||
1236 | * |
||
1237 | * @since 3.2.0 |
||
1238 | * @param WC_Order $order For to which the source applies. |
||
1239 | * @param stdClass $intent Payment intent information. |
||
1240 | */ |
||
1241 | public function save_intent_to_order( $order, $intent ) { |
||
1242 | $order->update_meta_data( '_stripe_intent_id', $intent->id ); |
||
1243 | |||
1244 | if ( is_callable( array( $order, 'save' ) ) ) { |
||
1245 | $order->save(); |
||
1246 | } |
||
1247 | } |
||
1248 | |||
1249 | /** |
||
1250 | * Retrieves the payment intent, associated with an order. |
||
1251 | * |
||
1252 | * @since 4.2 |
||
1253 | * @param WC_Order $order The order to retrieve an intent for. |
||
1254 | * @return obect|bool Either the intent object or `false`. |
||
1255 | */ |
||
1256 | public function get_intent_from_order( $order ) { |
||
1257 | $intent_id = $order->get_meta( '_stripe_intent_id' ); |
||
1258 | |||
1259 | if ( $intent_id ) { |
||
1260 | return $this->get_intent( 'payment_intents', $intent_id ); |
||
1261 | } |
||
1262 | |||
1263 | // The order doesn't have a payment intent, but it may have a setup intent. |
||
1264 | $intent_id = $order->get_meta( '_stripe_setup_intent' ); |
||
1265 | |||
1266 | if ( $intent_id ) { |
||
1267 | return $this->get_intent( 'setup_intents', $intent_id ); |
||
1268 | } |
||
1269 | |||
1270 | return false; |
||
1271 | } |
||
1272 | |||
1273 | /** |
||
1274 | * Retrieves intent from Stripe API by intent id. |
||
1275 | * |
||
1276 | * @param string $intent_type Either 'payment_intents' or 'setup_intents'. |
||
1277 | * @param string $intent_id Intent id. |
||
1278 | * @return object|bool Either the intent object or `false`. |
||
1279 | * @throws Exception Throws exception for unknown $intent_type. |
||
1280 | */ |
||
1281 | private function get_intent( $intent_type, $intent_id ) { |
||
1282 | if ( ! in_array( $intent_type, [ 'payment_intents', 'setup_intents' ] ) ) { |
||
1283 | throw new Exception( "Failed to get intent of type $intent_type. Type is not allowed" ); |
||
1284 | } |
||
1285 | |||
1286 | $response = WC_Stripe_API::request( array(), "$intent_type/$intent_id", 'GET' ); |
||
1287 | |||
1288 | if ( $response && isset( $response->{ 'error' } ) ) { |
||
1289 | $error_response_message = print_r( $response, true ); |
||
1290 | WC_Stripe_Logger::log("Failed to get Stripe intent $intent_type/$intent_id."); |
||
1291 | WC_Stripe_Logger::log("Response: $error_response_message"); |
||
1292 | return false; |
||
1293 | } |
||
1294 | |||
1295 | return $response; |
||
1296 | } |
||
1297 | |||
1298 | /** |
||
1299 | * Locks an order for payment intent processing for 5 minutes. |
||
1300 | * |
||
1301 | * @since 4.2 |
||
1302 | * @param WC_Order $order The order that is being paid. |
||
1303 | * @param stdClass $intent The intent that is being processed. |
||
1304 | * @return bool A flag that indicates whether the order is already locked. |
||
1305 | */ |
||
1306 | public function lock_order_payment( $order, $intent = null ) { |
||
1307 | $order_id = $order->get_id(); |
||
1308 | $transient_name = 'wc_stripe_processing_intent_' . $order_id; |
||
1309 | $processing = get_transient( $transient_name ); |
||
1310 | |||
1311 | // Block the process if the same intent is already being handled. |
||
1312 | if ( "-1" === $processing || ( isset( $intent->id ) && $processing === $intent->id ) ) { |
||
1313 | return true; |
||
1314 | } |
||
1315 | |||
1316 | // Save the new intent as a transient, eventually overwriting another one. |
||
1317 | set_transient( $transient_name, empty( $intent ) ? '-1' : $intent->id, 5 * MINUTE_IN_SECONDS ); |
||
1318 | |||
1319 | return false; |
||
1320 | } |
||
1321 | |||
1322 | /** |
||
1323 | * Unlocks an order for processing by payment intents. |
||
1324 | * |
||
1325 | * @since 4.2 |
||
1326 | * @param WC_Order $order The order that is being unlocked. |
||
1327 | */ |
||
1328 | public function unlock_order_payment( $order ) { |
||
1329 | $order_id = $order->get_id(); |
||
1330 | delete_transient( 'wc_stripe_processing_intent_' . $order_id ); |
||
1331 | } |
||
1332 | |||
1333 | /** |
||
1334 | * Given a response from Stripe, check if it's a card error where authentication is required |
||
1335 | * to complete the payment. |
||
1336 | * |
||
1337 | * @param object $response The response from Stripe. |
||
1338 | * @return boolean Whether or not it's a 'authentication_required' error |
||
1339 | */ |
||
1340 | public function is_authentication_required_for_payment( $response ) { |
||
1341 | return ( ! empty( $response->error ) && 'authentication_required' === $response->error->code ) |
||
1342 | || ( ! empty( $response->last_payment_error ) && 'authentication_required' === $response->last_payment_error->code ); |
||
1343 | } |
||
1344 | |||
1345 | /** |
||
1346 | * Creates a SetupIntent for future payments, and saves it to the order. |
||
1347 | * |
||
1348 | * @param WC_Order $order The ID of the (free/pre- order). |
||
1349 | * @param object $prepared_source The source, entered/chosen by the customer. |
||
1350 | * @return string The client secret of the intent, used for confirmation in JS. |
||
1351 | */ |
||
1352 | public function setup_intent( $order, $prepared_source ) { |
||
1353 | $order_id = $order->get_id(); |
||
1354 | $setup_intent = WC_Stripe_API::request( array( |
||
1355 | 'payment_method' => $prepared_source->source, |
||
1356 | 'customer' => $prepared_source->customer, |
||
1357 | 'confirm' => 'true', |
||
1358 | ), 'setup_intents' ); |
||
1359 | |||
1360 | if ( is_wp_error( $setup_intent ) ) { |
||
1361 | WC_Stripe_Logger::log( "Unable to create SetupIntent for Order #$order_id: " . print_r( $setup_intent, true ) ); |
||
1362 | } elseif ( 'requires_action' === $setup_intent->status ) { |
||
1363 | $order->update_meta_data( '_stripe_setup_intent', $setup_intent->id ); |
||
1364 | $order->save(); |
||
1365 | |||
1366 | return $setup_intent->client_secret; |
||
1367 | } |
||
1368 | } |
||
1369 | |||
1370 | /** |
||
1371 | * Create and confirm a new PaymentIntent. |
||
1372 | * |
||
1373 | * @param WC_Order $order The order that is being paid for. |
||
1374 | * @param object $prepared_source The source that is used for the payment. |
||
1375 | * @param float $amount The amount to charge. If not specified, it will be read from the order. |
||
1376 | * @return object An intent or an error. |
||
1377 | */ |
||
1378 | public function create_and_confirm_intent_for_off_session( $order, $prepared_source, $amount = NULL ) { |
||
1379 | // The request for a charge contains metadata for the intent. |
||
1380 | $full_request = $this->generate_payment_request( $order, $prepared_source ); |
||
1381 | |||
1382 | $request = array( |
||
1383 | 'amount' => $amount ? WC_Stripe_Helper::get_stripe_amount( $amount, $full_request['currency'] ) : $full_request['amount'], |
||
1384 | 'currency' => $full_request['currency'], |
||
1385 | 'description' => $full_request['description'], |
||
1386 | 'metadata' => $full_request['metadata'], |
||
1387 | 'payment_method_types' => array( |
||
1388 | 'card', |
||
1389 | ), |
||
1390 | 'off_session' => 'true', |
||
1391 | 'confirm' => 'true', |
||
1392 | 'confirmation_method' => 'automatic', |
||
1393 | ); |
||
1394 | |||
1395 | if ( isset( $full_request['statement_descriptor'] ) ) { |
||
1396 | $request['statement_descriptor'] = $full_request['statement_descriptor']; |
||
1397 | } |
||
1398 | |||
1399 | if ( isset( $full_request['customer'] ) ) { |
||
1400 | $request['customer'] = $full_request['customer']; |
||
1401 | } |
||
1402 | |||
1403 | if ( isset( $full_request['source'] ) ) { |
||
1404 | $is_source = 'src_' === substr( $full_request['source'], 0, 4 ); |
||
1405 | $request[ $is_source ? 'source' : 'payment_method' ] = $full_request['source']; |
||
1406 | } |
||
1407 | |||
1408 | /** |
||
1409 | * Filter the value of the request. |
||
1410 | * |
||
1411 | * @since 4.5.0 |
||
1412 | * @param array $request |
||
1413 | * @param WC_Order $order |
||
1414 | * @param object $source |
||
1415 | */ |
||
1416 | $request = apply_filters('wc_stripe_generate_create_intent_request', $request, $order, $prepared_source ); |
||
1417 | |||
1418 | if ( isset( $full_request['shipping'] ) ) { |
||
1419 | $request['shipping'] = $full_request['shipping']; |
||
1420 | } |
||
1421 | |||
1422 | $level3_data = $this->get_level3_data_from_order( $order ); |
||
1423 | $intent = WC_Stripe_API::request_with_level3_data( |
||
1424 | $request, |
||
1425 | 'payment_intents', |
||
1426 | $level3_data, |
||
1427 | $order |
||
1428 | ); |
||
1429 | $is_authentication_required = $this->is_authentication_required_for_payment( $intent ); |
||
1430 | |||
1431 | if ( ! empty( $intent->error ) && ! $is_authentication_required ) { |
||
1432 | return $intent; |
||
1433 | } |
||
1434 | |||
1435 | $intent_id = ( ! empty( $intent->error ) |
||
1436 | ? $intent->error->payment_intent->id |
||
1437 | : $intent->id |
||
1438 | ); |
||
1439 | $payment_intent = ( ! empty( $intent->error ) |
||
1440 | ? $intent->error->payment_intent |
||
1441 | : $intent |
||
1442 | ); |
||
1443 | $order_id = $order->get_id(); |
||
1444 | WC_Stripe_Logger::log( "Stripe PaymentIntent $intent_id initiated for order $order_id" ); |
||
1445 | |||
1446 | // Save the intent ID to the order. |
||
1447 | $this->save_intent_to_order( $order, $payment_intent ); |
||
1448 | |||
1449 | return $intent; |
||
1450 | } |
||
1451 | |||
1452 | /** |
||
1453 | * Checks if subscription has a Stripe customer ID and adds it if doesn't. |
||
1454 | * |
||
1455 | * Fix renewal for existing subscriptions affected by https://github.com/woocommerce/woocommerce-gateway-stripe/issues/1072. |
||
1456 | * @param int $order_id subscription renewal order id. |
||
1457 | */ |
||
1458 | public function ensure_subscription_has_customer_id( $order_id ) { |
||
1459 | $subscriptions_ids = wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => 'any' ) ); |
||
1460 | foreach( $subscriptions_ids as $subscription_id => $subscription ) { |
||
1461 | if ( ! metadata_exists( 'post', $subscription_id, '_stripe_customer_id' ) ) { |
||
1462 | $stripe_customer = new WC_Stripe_Customer( $subscription->get_user_id() ); |
||
1463 | update_post_meta( $subscription_id, '_stripe_customer_id', $stripe_customer->get_id() ); |
||
1464 | update_post_meta( $order_id, '_stripe_customer_id', $stripe_customer->get_id() ); |
||
1465 | } |
||
1466 | } |
||
1467 | } |
||
1468 | |||
1469 | /** Verifies whether a certain ZIP code is valid for the US, incl. 4-digit extensions. |
||
1470 | * |
||
1471 | * @param string $zip The ZIP code to verify. |
||
1472 | * @return boolean |
||
1473 | */ |
||
1474 | public function is_valid_us_zip_code( $zip ) { |
||
1475 | return ! empty( $zip ) && preg_match( '/^\d{5,5}(-\d{4,4})?$/', $zip ); |
||
1476 | } |
||
1477 | } |
||
1478 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.