blob: 75ecbe03366b59949954b730510feb7b90d69f8a [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4}
5
6/**
7 * Class that handles SEPA payment method.
8 *
9 * @extends WC_Gateway_Stripe
10 *
11 * @since 4.0.0
12 */
13class WC_Gateway_Stripe_Sepa extends WC_Stripe_Payment_Gateway {
14
15 const ID = 'stripe_sepa';
16
17 /**
18 * Notices (array)
19 *
20 * @var array
21 */
22 public $notices = [];
23
24 /**
25 * Is test mode active?
26 *
27 * @var bool
28 */
29 public $testmode;
30
31 /**
32 * Alternate credit card statement name
33 *
34 * @var bool
35 */
36 public $statement_descriptor;
37
38 /**
39 * API access secret key
40 *
41 * @var string
42 */
43 public $secret_key;
44
45 /**
46 * Api access publishable key
47 *
48 * @var string
49 */
50 public $publishable_key;
51
52 /**
53 * Should we store the users credit cards?
54 *
55 * @var bool
56 */
57 public $saved_cards;
58
59 /**
60 * Constructor
61 */
62 public function __construct() {
63 $this->id = self::ID;
64 $this->method_title = __( 'Stripe SEPA Direct Debit', 'woocommerce-gateway-stripe' );
65 $this->method_description = sprintf(
66 /* translators: 1) HTML anchor open tag 2) HTML anchor closing tag */
67 __( 'All other general Stripe settings can be adjusted %1$shere%2$s.', 'woocommerce-gateway-stripe' ),
68 '<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout&section=stripe' ) ) . '">',
69 '</a>'
70 );
71 $this->has_fields = true;
72 $this->supports = [
73 'products',
74 'refunds',
75 'tokenization',
76 'add_payment_method',
77 ];
78
79 // Load the form fields.
80 $this->init_form_fields();
81
82 // Load the settings.
83 $this->init_settings();
84
85 // Check if subscriptions are enabled and add support for them.
86 $this->maybe_init_subscriptions();
87
88 // Check if pre-orders are enabled and add support for them.
89 $this->maybe_init_pre_orders();
90
91 $main_settings = get_option( 'woocommerce_stripe_settings' );
92 $this->title = $this->get_option( 'title' );
93 $this->description = $this->get_option( 'description' );
94 $this->enabled = $this->get_option( 'enabled' );
95 $this->testmode = ( ! empty( $main_settings['testmode'] ) && 'yes' === $main_settings['testmode'] ) ? true : false;
96 $this->saved_cards = ( ! empty( $main_settings['saved_cards'] ) && 'yes' === $main_settings['saved_cards'] ) ? true : false;
97 $this->publishable_key = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
98 $this->secret_key = ! empty( $main_settings['secret_key'] ) ? $main_settings['secret_key'] : '';
99 $this->statement_descriptor = ! empty( $main_settings['statement_descriptor'] ) ? $main_settings['statement_descriptor'] : '';
100
101 if ( $this->testmode ) {
102 $this->publishable_key = ! empty( $main_settings['test_publishable_key'] ) ? $main_settings['test_publishable_key'] : '';
103 $this->secret_key = ! empty( $main_settings['test_secret_key'] ) ? $main_settings['test_secret_key'] : '';
104 }
105
106 add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
107 add_action( 'wp_enqueue_scripts', [ $this, 'payment_scripts' ] );
108 }
109
110 /**
111 * Returns all supported currencies for this payment method.
112 *
113 * @since 4.0.0
114 * @version 4.0.0
115 * @return array
116 */
117 public function get_supported_currency() {
118 return apply_filters(
119 'wc_stripe_sepa_supported_currencies',
120 [
121 'EUR',
122 ]
123 );
124 }
125
126 /**
127 * Checks to see if all criteria is met before showing payment method.
128 *
129 * @since 4.0.0
130 * @version 4.0.0
131 * @return bool
132 */
133 public function is_available() {
134 if ( ! in_array( get_woocommerce_currency(), $this->get_supported_currency() ) ) {
135 return false;
136 }
137
138 if ( is_add_payment_method_page() && ! $this->saved_cards ) {
139 return false;
140 }
141
142 return parent::is_available();
143 }
144
145 /**
146 * Get_icon function.
147 *
148 * @since 1.0.0
149 * @version 4.0.0
150 * @return string
151 */
152 public function get_icon() {
153 $icons = $this->payment_icons();
154
155 $icons_str = '';
156
157 $icons_str .= isset( $icons['sepa'] ) ? $icons['sepa'] : '';
158
159 return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id );
160 }
161
162 /**
163 * Outputs scripts used for stripe payment
164 */
165 public function payment_scripts() {
166 if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() ) {
167 return;
168 }
169
170 wp_enqueue_style( 'stripe_styles' );
171 wp_enqueue_script( 'woocommerce_stripe' );
172 }
173
174 /**
175 * Initialize Gateway Settings Form Fields.
176 */
177 public function init_form_fields() {
178 $this->form_fields = require WC_STRIPE_PLUGIN_PATH . '/includes/admin/stripe-sepa-settings.php';
179 }
180
181 /**
182 * Displays the mandate acceptance notice to customer.
183 *
184 * @since 4.0.0
185 * @version 4.0.0
186 * @return string
187 */
188 public function mandate_display() {
189 /* translators: statement descriptor */
190 printf( __( 'By providing your IBAN and confirming this payment, you are authorizing %s and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::clean_statement_descriptor( $this->statement_descriptor ) );
191 }
192
193 /**
194 * Renders the Stripe elements form.
195 *
196 * @since 4.0.0
197 * @version 4.0.0
198 */
199 public function form() {
200 ?>
201 <fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-form" class="wc-payment-form">
202 <?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
203 <p class="wc-stripe-sepa-mandate" style="margin-bottom:40px;"><?php $this->mandate_display(); ?></p>
204 <p class="form-row form-row-wide">
205 <label for="stripe-iban-element">
206 <?php esc_html_e( 'IBAN.', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span>
207 </label>
208 <div id="stripe-iban-element" class="wc-stripe-iban-element-field">
209 <!-- A Stripe Element will be inserted here. -->
210 </div>
211 </p>
212
213 <!-- Used to display form errors -->
214 <div class="stripe-source-errors" role="alert"></div>
215 <br />
216 <?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
217 <div class="clear"></div>
218 </fieldset>
219 <?php
220 }
221
222 /**
223 * Payment form on checkout page
224 */
225 public function payment_fields() {
226 global $wp;
227 $total = WC()->cart->total;
228 $display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
229 $description = $this->get_description();
230 $description = ! empty( $description ) ? $description : '';
231
232 // If paying from order, we need to get total from order not cart.
233 if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) {
234 $order = wc_get_order( wc_clean( $wp->query_vars['order-pay'] ) );
235 $total = $order->get_total();
236 }
237
238 if ( is_add_payment_method_page() ) {
239 $total = '';
240 }
241
242 echo '<div
243 id="stripe-sepa_debit-payment-data"
244 data-amount="' . esc_attr( WC_Stripe_Helper::get_stripe_amount( $total ) ) . '"
245 data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '">';
246
247 if ( $this->testmode ) {
248 $description .= ' ' . __( 'TEST MODE ENABLED. In test mode, you can use IBAN number DE89370400440532013000.', 'woocommerce-gateway-stripe' );
249 }
250
251 $description = trim( $description );
252
253 echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $description ) ), $this->id );
254
255 if ( $display_tokenization ) {
256 $this->tokenization_script();
257 $this->saved_payment_methods();
258 }
259
260 $this->form();
261
262 if ( apply_filters( 'wc_stripe_display_save_payment_method_checkbox', $display_tokenization ) && ! is_add_payment_method_page() && ! isset( $_GET['change_payment_method'] ) ) {
263 $this->save_payment_method_checkbox();
264 }
265
266 do_action( 'wc_stripe_payment_fields_stripe_sepa', $this->id );
267
268 echo '</div>';
269 }
270
271 /**
272 * Process the payment
273 *
274 * @param int $order_id Reference.
275 * @param bool $retry Should we retry on fail.
276 * @param bool $force_save_source Force save the payment source.
277 *
278 * @throws Exception If payment will not be accepted.
279 *
280 * @return array|void
281 */
282 public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
283 try {
284 $order = wc_get_order( $order_id );
285
286 if ( $this->has_subscription( $order_id ) ) {
287 $force_save_source = true;
288 }
289
290 if ( $this->maybe_change_subscription_payment_method( $order_id ) ) {
291 return $this->process_change_subscription_payment_method( $order_id );
292 }
293
294 if ( $this->maybe_process_pre_orders( $order_id ) ) {
295 return $this->process_pre_order( $order_id );
296 }
297
298 // This comes from the create account checkbox in the checkout page.
299 $create_account = ! empty( $_POST['createaccount'] ) ? true : false;
300
301 if ( $create_account ) {
302 $new_customer_id = $order->get_customer_id();
303 $new_stripe_customer = new WC_Stripe_Customer( $new_customer_id );
304 $new_stripe_customer->create_customer();
305 }
306
307 $prepared_source = $this->prepare_source( get_current_user_id(), $force_save_source );
308
309 $this->save_source_to_order( $order, $prepared_source );
310
311 // Result from Stripe API request.
312 $response = null;
313
314 if ( $order->get_total() > 0 ) {
315 // This will throw exception if not valid.
316 $this->validate_minimum_order_amount( $order );
317
318 WC_Stripe_Logger::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
319
320 // Make the request.
321 $response = WC_Stripe_API::request( $this->generate_payment_request( $order, $prepared_source ) );
322
323 if ( ! empty( $response->error ) ) {
324 // Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
325 if ( $this->is_no_such_customer_error( $response->error ) ) {
326 delete_user_option( $order->get_customer_id(), '_stripe_customer_id' );
327 $order->delete_meta_data( '_stripe_customer_id' );
328 $order->save();
329 }
330
331 if ( $this->is_no_such_token_error( $response->error ) && $prepared_source->token_id ) {
332 // Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
333 $wc_token = WC_Payment_Tokens::get( $prepared_source->token_id );
334 $wc_token->delete();
335 $localized_message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
336 $order->add_order_note( $localized_message );
337 throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
338 }
339
340 // We want to retry.
341 if ( $this->is_retryable_error( $response->error ) ) {
342 if ( $retry ) {
343 // Don't do anymore retries after this.
344 if ( 5 <= $this->retry_interval ) {
345
346 return $this->process_payment( $order_id, false, $force_save_source );
347 }
348
349 sleep( $this->retry_interval );
350
351 $this->retry_interval++;
352
353 return $this->process_payment( $order_id, true, $force_save_source );
354 } else {
355 $localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
356 $order->add_order_note( $localized_message );
357 throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
358 }
359 }
360
361 $localized_messages = WC_Stripe_Helper::get_localized_messages();
362
363 if ( 'card_error' === $response->error->type ) {
364 $localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
365 } else {
366 $localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
367 }
368
369 $order->add_order_note( $localized_message );
370
371 throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
372 }
373
374 do_action( 'wc_gateway_stripe_process_payment', $response, $order );
375
376 // Process valid response.
377 $this->process_response( $response, $order );
378 } else {
379 $order->payment_complete();
380 }
381
382 // Remove cart.
383 WC()->cart->empty_cart();
384
385 // Return thank you page redirect.
386 return [
387 'result' => 'success',
388 'redirect' => $this->get_return_url( $order ),
389 ];
390
391 } catch ( WC_Stripe_Exception $e ) {
392 wc_add_notice( $e->getLocalizedMessage(), 'error' );
393 WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
394
395 do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
396
397 if ( $order->has_status(
398 apply_filters(
399 'wc_stripe_allowed_payment_processing_statuses',
400 [ 'pending', 'failed' ],
401 $order
402 )
403 ) ) {
404 $this->send_failed_order_email( $order_id );
405 }
406
407 return [
408 'result' => 'fail',
409 'redirect' => '',
410 ];
411 }
412 }
413}