1
|
|
|
<?php |
2
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
3
|
|
|
exit; |
4
|
|
|
} |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Abstract class that will be inherited by all payment methods. |
8
|
|
|
* |
9
|
|
|
* @extends WC_Payment_Gateway_CC |
10
|
|
|
* |
11
|
|
|
* @since 4.0.0 |
12
|
|
|
*/ |
13
|
|
|
abstract class WC_Stripe_Payment_Gateway extends WC_Payment_Gateway_CC { |
14
|
|
|
const META_NAME_FEE = 'Stripe Fee'; |
15
|
|
|
const META_NAME_NET = 'Net Revenue From Stripe'; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Checks to see if request is invalid and that |
19
|
|
|
* they are worth retrying. |
20
|
|
|
* |
21
|
|
|
* @since 4.0.5 |
22
|
|
|
* @param array $error |
23
|
|
|
*/ |
24
|
|
|
public function is_retryable_error( $error ) { |
25
|
|
|
return ( |
26
|
|
|
'invalid_request_error' === $error->type || |
27
|
|
|
'idempotency_error' === $error->type || |
28
|
|
|
'rate_limit_error' === $error->type |
29
|
|
|
); |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Check if this gateway is enabled |
34
|
|
|
*/ |
35
|
|
|
public function is_available() { |
36
|
|
|
if ( 'yes' === $this->enabled ) { |
37
|
|
|
if ( ! $this->testmode && is_checkout() && ! is_ssl() ) { |
38
|
|
|
return false; |
39
|
|
|
} |
40
|
|
|
if ( ! $this->secret_key || ! $this->publishable_key ) { |
41
|
|
|
return false; |
42
|
|
|
} |
43
|
|
|
return true; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
return parent::is_available(); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Allow this class and other classes to add slug keyed notices (to avoid duplication). |
51
|
|
|
* |
52
|
|
|
* @since 4.0.0 |
53
|
|
|
* @version 4.0.0 |
54
|
|
|
*/ |
55
|
|
|
public function add_admin_notice( $slug, $class, $message ) { |
56
|
|
|
$this->notices[ $slug ] = array( |
57
|
|
|
'class' => $class, |
58
|
|
|
'message' => $message, |
59
|
|
|
); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Remove admin notice. |
64
|
|
|
* |
65
|
|
|
* @since 4.0.0 |
66
|
|
|
* @version 4.0.0 |
67
|
|
|
*/ |
68
|
|
|
public function remove_admin_notice() { |
69
|
|
|
if ( did_action( 'woocommerce_update_options' ) ) { |
70
|
|
|
remove_action( 'admin_notices', array( $this, 'check_environment' ) ); |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* All payment icons that work with Stripe. |
76
|
|
|
* |
77
|
|
|
* @since 4.0.0 |
78
|
|
|
* @version 4.0.0 |
79
|
|
|
* @return array |
80
|
|
|
*/ |
81
|
|
|
public function payment_icons() { |
82
|
|
|
return apply_filters( 'wc_stripe_payment_icons', array( |
83
|
|
|
'visa' => '<i class="stripe-pf stripe-pf-visa stripe-pf-right" alt="Visa" aria-hidden="true"></i>', |
84
|
|
|
'amex' => '<i class="stripe-pf stripe-pf-american-express stripe-pf-right" alt="Amex" aria-hidden="true"></i>', |
85
|
|
|
'mastercard' => '<i class="stripe-pf stripe-pf-mastercard stripe-pf-right" alt="Mastercard" aria-hidden="true"></i>', |
86
|
|
|
'discover' => '<i class="stripe-pf stripe-pf-discover stripe-pf-right" alt="Discover" aria-hidden="true"></i>', |
87
|
|
|
'diners' => '<i class="stripe-pf stripe-pf-diners stripe-pf-right" alt="Diners" aria-hidden="true"></i>', |
88
|
|
|
'jcb' => '<i class="stripe-pf stripe-pf-jcb stripe-pf-right" alt="JCB" aria-hidden="true"></i>', |
89
|
|
|
'alipay' => '<i class="stripe-pf stripe-pf-alipay stripe-pf-right" alt="Alipay" aria-hidden="true"></i>', |
90
|
|
|
'wechat' => '<i class="stripe-pf stripe-pf-wechat-pay stripe-pf-right" alt="Wechat Pay" aria-hidden="true"></i>', |
91
|
|
|
'bitcoin' => '<i class="stripe-pf stripe-pf-bitcoin stripe-pf-right" alt="Bitcoin" aria-hidden="true"></i>', |
92
|
|
|
'bancontact' => '<i class="stripe-pf stripe-pf-bancontact-mister-cash stripe-pf-right" alt="Bancontact" aria-hidden="true"></i>', |
93
|
|
|
'ideal' => '<i class="stripe-pf stripe-pf-ideal stripe-pf-right" alt="iDeal" aria-hidden="true"></i>', |
94
|
|
|
'p24' => '<i class="stripe-pf stripe-pf-p24 stripe-pf-right" alt="P24" aria-hidden="true"></i>', |
95
|
|
|
'giropay' => '<i class="stripe-pf stripe-pf-giropay stripe-pf-right" alt="Giropay" aria-hidden="true"></i>', |
96
|
|
|
'eps' => '<i class="stripe-pf stripe-pf-eps stripe-pf-right" alt="EPS" aria-hidden="true"></i>', |
97
|
|
|
'sofort' => '<i class="stripe-pf stripe-pf-sofort stripe-pf-right" alt="SOFORT" aria-hidden="true"></i>', |
98
|
|
|
'sepa' => '<i class="stripe-pf stripe-pf-sepa stripe-pf-right" alt="SEPA" aria-hidden="true"></i>', |
99
|
|
|
) ); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Validates that the order meets the minimum order amount |
104
|
|
|
* set by Stripe. |
105
|
|
|
* |
106
|
|
|
* @since 4.0.0 |
107
|
|
|
* @version 4.0.0 |
108
|
|
|
* @param object $order |
109
|
|
|
*/ |
110
|
|
|
public function validate_minimum_order_amount( $order ) { |
111
|
|
|
if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) { |
112
|
|
|
/* translators: 1) dollar amount */ |
113
|
|
|
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 ) ) ); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Gets the transaction URL linked to Stripe dashboard. |
119
|
|
|
* |
120
|
|
|
* @since 4.0.0 |
121
|
|
|
* @version 4.0.0 |
122
|
|
|
*/ |
123
|
|
|
public function get_transaction_url( $order ) { |
124
|
|
|
if ( $this->testmode ) { |
125
|
|
|
$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s'; |
126
|
|
|
} else { |
127
|
|
|
$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s'; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return parent::get_transaction_url( $order ); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Gets the saved customer id if exists. |
135
|
|
|
* |
136
|
|
|
* @since 4.0.0 |
137
|
|
|
* @version 4.0.0 |
138
|
|
|
*/ |
139
|
|
|
public function get_stripe_customer_id( $order ) { |
140
|
|
|
$customer = get_user_meta( WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id(), '_stripe_customer_id', true ); |
141
|
|
|
|
142
|
|
|
if ( empty( $customer ) ) { |
143
|
|
|
// Try to get it via the order. |
144
|
|
|
if ( WC_Stripe_Helper::is_pre_30() ) { |
145
|
|
|
return get_post_meta( $order->id, '_stripe_customer_id', true ); |
146
|
|
|
} else { |
147
|
|
|
return $order->get_meta( '_stripe_customer_id', true ); |
148
|
|
|
} |
149
|
|
|
} else { |
150
|
|
|
return $customer; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return false; |
|
|
|
|
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Builds the return URL from redirects. |
158
|
|
|
* |
159
|
|
|
* @since 4.0.0 |
160
|
|
|
* @version 4.0.0 |
161
|
|
|
* @param object $order |
162
|
|
|
* @param int $id Stripe session id. |
163
|
|
|
*/ |
164
|
|
|
public function get_stripe_return_url( $order = null, $id = null ) { |
165
|
|
|
if ( is_object( $order ) ) { |
166
|
|
|
if ( empty( $id ) ) { |
167
|
|
|
$id = uniqid(); |
|
|
|
|
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
171
|
|
|
|
172
|
|
|
$args = array( |
173
|
|
|
'utm_nooverride' => '1', |
174
|
|
|
'order_id' => $order_id, |
175
|
|
|
); |
176
|
|
|
|
177
|
|
|
return esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) ); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) ); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Generate the request for the payment. |
185
|
|
|
* |
186
|
|
|
* @since 3.1.0 |
187
|
|
|
* @version 4.0.0 |
188
|
|
|
* @param WC_Order $order |
189
|
|
|
* @param object $source |
190
|
|
|
* @return array() |
|
|
|
|
191
|
|
|
*/ |
192
|
|
|
public function generate_payment_request( $order, $source ) { |
193
|
|
|
$settings = get_option( 'woocommerce_stripe_settings', array() ); |
194
|
|
|
$statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : ''; |
195
|
|
|
$capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false; |
196
|
|
|
$post_data = array(); |
197
|
|
|
$post_data['currency'] = strtolower( WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency() ); |
198
|
|
|
$post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] ); |
199
|
|
|
/* translators: 1) blog name 2) order number */ |
200
|
|
|
$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() ); |
201
|
|
|
$billing_email = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email(); |
202
|
|
|
$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name(); |
203
|
|
|
$billing_last_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name(); |
204
|
|
|
|
205
|
|
|
if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) { |
206
|
|
|
$post_data['receipt_email'] = $billing_email; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
switch ( WC_Stripe_Helper::is_pre_30() ? $order->payment_method : $order->get_payment_method() ) { |
210
|
|
|
case 'stripe': |
211
|
|
|
if ( ! empty( $statement_descriptor ) ) { |
212
|
|
|
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor ); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$post_data['capture'] = $capture ? 'true' : 'false'; |
216
|
|
|
break; |
217
|
|
|
case 'stripe_sepa': |
218
|
|
|
if ( ! empty( $statement_descriptor ) ) { |
219
|
|
|
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor ); |
220
|
|
|
} |
221
|
|
|
break; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$post_data['expand[]'] = 'balance_transaction'; |
225
|
|
|
|
226
|
|
|
$metadata = array( |
227
|
|
|
__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ), |
228
|
|
|
__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ), |
229
|
|
|
'order_id' => WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(), |
230
|
|
|
); |
231
|
|
|
|
232
|
|
|
$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source ); |
233
|
|
|
|
234
|
|
|
if ( $source->customer ) { |
235
|
|
|
$post_data['customer'] = $source->customer; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
if ( $source->source ) { |
239
|
|
|
$post_data['source'] = $source->source; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request. |
244
|
|
|
* |
245
|
|
|
* @since 3.1.0 |
246
|
|
|
* @param array $post_data |
247
|
|
|
* @param WC_Order $order |
248
|
|
|
* @param object $source |
249
|
|
|
*/ |
250
|
|
|
return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source ); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Store extra meta data for an order from a Stripe Response. |
255
|
|
|
*/ |
256
|
|
|
public function process_response( $response, $order ) { |
257
|
|
|
WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) ); |
258
|
|
|
|
259
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
260
|
|
|
|
261
|
|
|
$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no'; |
262
|
|
|
|
263
|
|
|
// Store charge data |
264
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_charge_captured', $captured ) : $order->update_meta_data( '_stripe_charge_captured', $captured ); |
265
|
|
|
|
266
|
|
|
// Store other data such as fees |
267
|
|
View Code Duplication |
if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->fee ) ) { |
|
|
|
|
268
|
|
|
// Fees and Net needs to both come from Stripe to be accurate as the returned |
269
|
|
|
// values are in the local currency of the Stripe account, not from WC. |
270
|
|
|
$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0; |
271
|
|
|
$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0; |
272
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_FEE, $fee ) : $order->update_meta_data( self::META_NAME_FEE, $fee ); |
273
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_NET, $net ) : $order->update_meta_data( self::META_NAME_NET, $net ); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
if ( 'yes' === $captured ) { |
277
|
|
|
/** |
278
|
|
|
* Charge can be captured but in a pending state. Payment methods |
279
|
|
|
* that are asynchronous may take couple days to clear. Webhook will |
280
|
|
|
* take care of the status changes. |
281
|
|
|
*/ |
282
|
|
|
if ( 'pending' === $response->status ) { |
283
|
|
|
if ( ! wc_string_to_bool( get_post_meta( $order_id, '_order_stock_reduced', true ) ) ) { |
284
|
|
|
WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id ); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id ); |
288
|
|
|
/* translators: transaction id */ |
289
|
|
|
$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) ); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
if ( 'succeeded' === $response->status ) { |
293
|
|
|
$order->payment_complete( $response->id ); |
294
|
|
|
|
295
|
|
|
/* translators: transaction id */ |
296
|
|
|
$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id ); |
297
|
|
|
$order->add_order_note( $message ); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
if ( 'failed' === $response->status ) { |
301
|
|
|
$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' ); |
302
|
|
|
$order->add_order_note( $localized_message ); |
303
|
|
|
throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message ); |
304
|
|
|
} |
305
|
|
|
} else { |
306
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id ); |
307
|
|
|
|
308
|
|
|
if ( $order->has_status( array( 'pending', 'failed' ) ) ) { |
309
|
|
|
WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id ); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/* translators: transaction id */ |
313
|
|
|
$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 ) ); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
if ( is_callable( array( $order, 'save' ) ) ) { |
317
|
|
|
$order->save(); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
do_action( 'wc_gateway_stripe_process_response', $response, $order ); |
321
|
|
|
|
322
|
|
|
return $response; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Sends the failed order email to admin. |
327
|
|
|
* |
328
|
|
|
* @since 3.1.0 |
329
|
|
|
* @version 4.0.0 |
330
|
|
|
* @param int $order_id |
331
|
|
|
* @return null |
332
|
|
|
*/ |
333
|
|
|
public function send_failed_order_email( $order_id ) { |
334
|
|
|
$emails = WC()->mailer()->get_emails(); |
335
|
|
|
if ( ! empty( $emails ) && ! empty( $order_id ) ) { |
336
|
|
|
$emails['WC_Email_Failed_Order']->trigger( $order_id ); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Get owner details. |
342
|
|
|
* |
343
|
|
|
* @since 4.0.0 |
344
|
|
|
* @version 4.0.0 |
345
|
|
|
* @param object $order |
346
|
|
|
* @return object $details |
347
|
|
|
*/ |
348
|
|
|
public function get_owner_details( $order ) { |
349
|
|
|
$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name(); |
350
|
|
|
$billing_last_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name(); |
351
|
|
|
|
352
|
|
|
$details = array(); |
353
|
|
|
|
354
|
|
|
$details['name'] = $billing_first_name . ' ' . $billing_last_name; |
355
|
|
|
$details['email'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email(); |
356
|
|
|
|
357
|
|
|
$phone = WC_Stripe_Helper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone(); |
358
|
|
|
|
359
|
|
|
if ( ! empty( $phone ) ) { |
360
|
|
|
$details['phone'] = $phone; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
$details['address']['line1'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1(); |
364
|
|
|
$details['address']['line2'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2(); |
365
|
|
|
$details['address']['state'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state(); |
366
|
|
|
$details['address']['city'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city(); |
367
|
|
|
$details['address']['postal_code'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode(); |
368
|
|
|
$details['address']['country'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country(); |
369
|
|
|
|
370
|
|
|
return (object) apply_filters( 'wc_stripe_owner_details', $details, $order ); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* Create source object by source id. |
375
|
|
|
* |
376
|
|
|
* @since 4.0.3 |
377
|
|
|
*/ |
378
|
|
|
public function get_source_object() { |
379
|
|
|
$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : ''; |
380
|
|
|
|
381
|
|
|
if ( empty( $source ) ) { |
382
|
|
|
return ''; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
$source_object = WC_Stripe_API::retrieve( 'sources/' . $source ); |
386
|
|
|
|
387
|
|
|
if ( ! empty( $source_object->error ) ) { |
388
|
|
|
throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message ); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
return $source_object; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Checks if 3DS is required. |
396
|
|
|
* |
397
|
|
|
* @since 4.0.4 |
398
|
|
|
* @param object $source_object |
399
|
|
|
* @return bool |
400
|
|
|
*/ |
401
|
|
|
public function is_3ds_required( $source_object ) { |
402
|
|
|
return ( |
403
|
|
|
$source_object && ! empty( $source_object->card ) ) && |
404
|
|
|
( 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure || |
405
|
|
|
( $this->three_d_secure && 'optional' === $source_object->card->three_d_secure ) |
406
|
|
|
); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Checks if card is 3DS. |
411
|
|
|
* |
412
|
|
|
* @since 4.0.4 |
413
|
|
|
* @param object $source_object |
414
|
|
|
* @return bool |
415
|
|
|
*/ |
416
|
|
|
public function is_3ds_card( $source_object ) { |
417
|
|
|
return ( $source_object && 'three_d_secure' === $source_object->type ); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Creates the 3DS source for charge. |
422
|
|
|
* |
423
|
|
|
* @since 4.0.0 |
424
|
|
|
* @since 4.0.4 Add $return_url |
425
|
|
|
* @param object $order |
426
|
|
|
* @param object $source_object |
427
|
|
|
* @param string $return_url |
428
|
|
|
* @return mixed |
429
|
|
|
*/ |
430
|
|
|
public function create_3ds_source( $order, $source_object, $return_url = '' ) { |
431
|
|
|
$currency = WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency(); |
432
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
|
|
|
|
433
|
|
|
$return_url = empty( $return_url ) ? $this->get_stripe_return_url( $order ) : $return_url; |
434
|
|
|
|
435
|
|
|
$post_data = array(); |
436
|
|
|
$post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency ); |
437
|
|
|
$post_data['currency'] = strtolower( $currency ); |
438
|
|
|
$post_data['type'] = 'three_d_secure'; |
439
|
|
|
$post_data['owner'] = $this->get_owner_details( $order ); |
440
|
|
|
$post_data['three_d_secure'] = array( 'card' => $source_object->id ); |
441
|
|
|
$post_data['redirect'] = array( 'return_url' => $return_url ); |
442
|
|
|
|
443
|
|
|
WC_Stripe_Logger::log( 'Info: Begin creating 3DS source...' ); |
444
|
|
|
|
445
|
|
|
return WC_Stripe_API::request( apply_filters( 'wc_stripe_3ds_source', $post_data, $order ), 'sources' ); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Get payment source. This can be a new token/source or existing WC token. |
450
|
|
|
* If user is logged in and/or has WC account, create an account on Stripe. |
451
|
|
|
* This way we can attribute the payment to the user to better fight fraud. |
452
|
|
|
* |
453
|
|
|
* @since 3.1.0 |
454
|
|
|
* @version 4.0.0 |
455
|
|
|
* @param object $source_object |
456
|
|
|
* @param string $user_id |
457
|
|
|
* @param bool $force_save_source Should we force save payment source. |
458
|
|
|
* |
459
|
|
|
* @throws Exception When card was not added or for and invalid card. |
460
|
|
|
* @return object |
461
|
|
|
*/ |
462
|
|
|
public function prepare_source( $source_object = '', $user_id, $force_save_source = false ) { |
463
|
|
|
$customer = new WC_Stripe_Customer( $user_id ); |
464
|
|
|
$set_customer = true; |
465
|
|
|
$force_save_source = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer ); |
466
|
|
|
$source_id = ''; |
467
|
|
|
$wc_token_id = false; |
468
|
|
|
$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe'; |
469
|
|
|
|
470
|
|
|
// New CC info was entered and we have a new source to process. |
471
|
|
|
if ( ! empty( $source_object ) ) { |
472
|
|
|
$source_id = $source_object->id; |
473
|
|
|
|
474
|
|
|
// This checks to see if customer opted to save the payment method to file. |
475
|
|
|
$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* This is true if the user wants to store the card to their account. |
479
|
|
|
* Criteria to save to file is they are logged in, they opted to save or product requirements and the source is |
480
|
|
|
* actually reusable. Either that or force_save_source is true. |
481
|
|
|
*/ |
482
|
|
|
if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) { |
483
|
|
|
$response = $customer->add_source( $source_object->id ); |
484
|
|
|
|
485
|
|
|
if ( ! empty( $response->error ) ) { |
486
|
|
|
throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message ); |
487
|
|
|
} |
488
|
|
|
} |
489
|
|
|
} elseif ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) { |
490
|
|
|
// Use an existing token, and then process the payment |
491
|
|
|
$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ); |
492
|
|
|
$wc_token = WC_Payment_Tokens::get( $wc_token_id ); |
493
|
|
|
|
494
|
|
|
if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) { |
495
|
|
|
WC()->session->set( 'refresh_totals', true ); |
496
|
|
|
throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) ); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
$source_id = $wc_token->get_token(); |
500
|
|
|
} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) { |
501
|
|
|
$stripe_token = wc_clean( $_POST['stripe_token'] ); |
502
|
|
|
$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); |
503
|
|
|
|
504
|
|
|
// This is true if the user wants to store the card to their account. |
505
|
|
|
if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) { |
506
|
|
|
$response = $customer->add_source( $stripe_token ); |
507
|
|
|
|
508
|
|
|
if ( ! empty( $response->error ) ) { |
509
|
|
|
throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message ); |
510
|
|
|
} |
511
|
|
|
} else { |
512
|
|
|
$set_customer = false; |
513
|
|
|
$source_id = $stripe_token; |
514
|
|
|
} |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
if ( ! $set_customer ) { |
518
|
|
|
$customer_id = false; |
519
|
|
|
} else { |
520
|
|
|
$customer_id = $customer->get_id() ? $customer->get_id() : false; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
return (object) array( |
524
|
|
|
'token_id' => $wc_token_id, |
525
|
|
|
'customer' => $customer_id, |
526
|
|
|
'source' => $source_id, |
527
|
|
|
); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Get payment source from an order. This could be used in the future for |
532
|
|
|
* a subscription as an example, therefore using the current user ID would |
533
|
|
|
* not work - the customer won't be logged in :) |
534
|
|
|
* |
535
|
|
|
* Not using 2.6 tokens for this part since we need a customer AND a card |
536
|
|
|
* token, and not just one. |
537
|
|
|
* |
538
|
|
|
* @since 3.1.0 |
539
|
|
|
* @version 4.0.0 |
540
|
|
|
* @param object $order |
541
|
|
|
* @return object |
542
|
|
|
*/ |
543
|
|
|
public function prepare_order_source( $order = null ) { |
544
|
|
|
$stripe_customer = new WC_Stripe_Customer(); |
545
|
|
|
$stripe_source = false; |
546
|
|
|
$token_id = false; |
547
|
|
|
|
548
|
|
|
if ( $order ) { |
549
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
550
|
|
|
|
551
|
|
|
$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true ); |
552
|
|
|
|
553
|
|
|
if ( $stripe_customer_id ) { |
554
|
|
|
$stripe_customer->set_id( $stripe_customer_id ); |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true ); |
558
|
|
|
|
559
|
|
|
// Since 4.0.0, we changed card to source so we need to account for that. |
560
|
|
|
if ( empty( $source_id ) ) { |
561
|
|
|
$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true ); |
562
|
|
|
|
563
|
|
|
// Take this opportunity to update the key name. |
564
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_source_id', $source_id ) : $order->update_meta_data( '_stripe_source_id', $source_id ); |
565
|
|
|
|
566
|
|
|
if ( is_callable( array( $order, 'save' ) ) ) { |
567
|
|
|
$order->save(); |
568
|
|
|
} |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
if ( $source_id ) { |
572
|
|
|
$stripe_source = $source_id; |
573
|
|
|
} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) { |
574
|
|
|
/* |
575
|
|
|
* We can attempt to charge the customer's default source |
576
|
|
|
* by sending empty source id. |
577
|
|
|
*/ |
578
|
|
|
$stripe_source = ''; |
579
|
|
|
} |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
return (object) array( |
583
|
|
|
'token_id' => $token_id, |
584
|
|
|
'customer' => $stripe_customer ? $stripe_customer->get_id() : false, |
585
|
|
|
'source' => $stripe_source, |
586
|
|
|
); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Save source to order. |
591
|
|
|
* |
592
|
|
|
* @since 3.1.0 |
593
|
|
|
* @version 4.0.0 |
594
|
|
|
* @param WC_Order $order For to which the source applies. |
595
|
|
|
* @param stdClass $source Source information. |
596
|
|
|
*/ |
597
|
|
|
public function save_source_to_order( $order, $source ) { |
598
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
599
|
|
|
|
600
|
|
|
// Store source in the order. |
601
|
|
|
if ( $source->customer ) { |
602
|
|
|
if ( WC_Stripe_Helper::is_pre_30() ) { |
603
|
|
|
update_post_meta( $order_id, '_stripe_customer_id', $source->customer ); |
604
|
|
|
} else { |
605
|
|
|
$order->update_meta_data( '_stripe_customer_id', $source->customer ); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
if ( $source->source ) { |
610
|
|
|
if ( WC_Stripe_Helper::is_pre_30() ) { |
611
|
|
|
update_post_meta( $order_id, '_stripe_source_id', $source->source ); |
612
|
|
|
} else { |
613
|
|
|
$order->update_meta_data( '_stripe_source_id', $source->source ); |
614
|
|
|
} |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
if ( is_callable( array( $order, 'save' ) ) ) { |
618
|
|
|
$order->save(); |
619
|
|
|
} |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Updates Stripe fees/net. |
624
|
|
|
* e.g usage would be after a refund. |
625
|
|
|
* |
626
|
|
|
* @since 4.0.0 |
627
|
|
|
* @version 4.0.0 |
628
|
|
|
* @param object $order The order object |
629
|
|
|
* @param int $balance_transaction_id |
630
|
|
|
*/ |
631
|
|
|
public function update_fees( $order, $balance_transaction_id ) { |
632
|
|
|
$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(); |
633
|
|
|
|
634
|
|
|
$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id ); |
635
|
|
|
|
636
|
|
|
if ( empty( $balance_transaction->error ) ) { |
637
|
|
|
if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) { |
638
|
|
|
// Fees and Net needs to both come from Stripe to be accurate as the returned |
639
|
|
|
// values are in the local currency of the Stripe account, not from WC. |
640
|
|
|
$fee = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0; |
641
|
|
|
$net = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0; |
642
|
|
|
|
643
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_FEE, $fee ) : $order->update_meta_data( self::META_NAME_FEE, $fee ); |
644
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_NET, $net ) : $order->update_meta_data( self::META_NAME_NET, $net ); |
645
|
|
|
|
646
|
|
|
if ( is_callable( array( $order, 'save' ) ) ) { |
647
|
|
|
$order->save(); |
648
|
|
|
} |
649
|
|
|
} |
650
|
|
|
} else { |
651
|
|
|
WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" ); |
652
|
|
|
} |
653
|
|
|
} |
654
|
|
|
|
655
|
|
|
/** |
656
|
|
|
* Refund a charge. |
657
|
|
|
* |
658
|
|
|
* @since 3.1.0 |
659
|
|
|
* @version 4.0.0 |
660
|
|
|
* @param int $order_id |
661
|
|
|
* @param float $amount |
662
|
|
|
* @return bool |
663
|
|
|
*/ |
664
|
|
|
public function process_refund( $order_id, $amount = null, $reason = '' ) { |
665
|
|
|
$order = wc_get_order( $order_id ); |
666
|
|
|
|
667
|
|
|
if ( ! $order || ! $order->get_transaction_id() ) { |
668
|
|
|
return false; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
$request = array(); |
672
|
|
|
|
673
|
|
|
if ( WC_Stripe_Helper::is_pre_30() ) { |
674
|
|
|
$order_currency = get_post_meta( $order_id, '_order_currency', true ); |
675
|
|
|
$captured = get_post_meta( $order_id, '_stripe_charge_captured', true ); |
676
|
|
|
} else { |
677
|
|
|
$order_currency = $order->get_currency(); |
678
|
|
|
$captured = $order->get_meta( '_stripe_charge_captured', true ); |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
if ( ! is_null( $amount ) ) { |
682
|
|
|
$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency ); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
// If order is only authorized, don't pass amount. |
686
|
|
|
if ( 'yes' !== $captured ) { |
687
|
|
|
unset( $request['amount'] ); |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
if ( $reason ) { |
691
|
|
|
$request['metadata'] = array( |
692
|
|
|
'reason' => $reason, |
693
|
|
|
); |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
$request['charge'] = $order->get_transaction_id(); |
697
|
|
|
|
698
|
|
|
WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" ); |
699
|
|
|
|
700
|
|
|
$response = WC_Stripe_API::request( $request, 'refunds' ); |
701
|
|
|
|
702
|
|
|
if ( ! empty( $response->error ) ) { |
703
|
|
|
WC_Stripe_Logger::log( 'Error: ' . $response->error->message ); |
704
|
|
|
|
705
|
|
|
return $response; |
706
|
|
|
|
707
|
|
|
} elseif ( ! empty( $response->id ) ) { |
708
|
|
|
WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_refund_id', $response->id ) : $order->update_meta_data( '_stripe_refund_id', $response->id ); |
709
|
|
|
|
710
|
|
|
$amount = wc_price( $response->amount / 100 ); |
711
|
|
|
|
712
|
|
|
if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) { |
713
|
|
|
$amount = wc_price( $response->amount ); |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
if ( isset( $response->balance_transaction ) ) { |
717
|
|
|
$this->update_fees( $order, $response->balance_transaction ); |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
/* translators: 1) dollar amount 2) transaction id 3) refund message */ |
721
|
|
|
$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' ); |
722
|
|
|
|
723
|
|
|
$order->add_order_note( $refund_message ); |
724
|
|
|
WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) ); |
725
|
|
|
|
726
|
|
|
return true; |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Add payment method via account screen. |
732
|
|
|
* We don't store the token locally, but to the Stripe API. |
733
|
|
|
* |
734
|
|
|
* @since 3.0.0 |
735
|
|
|
* @version 4.0.0 |
736
|
|
|
*/ |
737
|
|
|
public function add_payment_method() { |
738
|
|
|
$error = false; |
739
|
|
|
$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ); |
740
|
|
|
$source_id = ''; |
741
|
|
|
|
742
|
|
|
if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) { |
743
|
|
|
$error = true; |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
$stripe_customer = new WC_Stripe_Customer( get_current_user_id() ); |
747
|
|
|
|
748
|
|
|
$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : ''; |
749
|
|
|
|
750
|
|
|
$source_object = WC_Stripe_API::retrieve( 'sources/' . $source ); |
751
|
|
|
|
752
|
|
|
if ( isset( $source_object ) ) { |
753
|
|
|
if ( ! empty( $source_object->error ) ) { |
754
|
|
|
$error = true; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
$source_id = $source_object->id; |
758
|
|
|
} elseif ( isset( $_POST['stripe_token'] ) ) { |
759
|
|
|
$source_id = wc_clean( $_POST['stripe_token'] ); |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
$response = $stripe_customer->add_source( $source_id ); |
763
|
|
|
|
764
|
|
|
if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) { |
765
|
|
|
$error = true; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
if ( $error ) { |
769
|
|
|
wc_add_notice( $error_msg, 'error' ); |
770
|
|
|
WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg ); |
771
|
|
|
return; |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
return array( |
775
|
|
|
'result' => 'success', |
776
|
|
|
'redirect' => wc_get_endpoint_url( 'payment-methods' ), |
777
|
|
|
); |
778
|
|
|
} |
779
|
|
|
} |
780
|
|
|
|
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
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.