1
|
|
|
<?php |
2
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
3
|
|
|
exit; |
4
|
|
|
} |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* WC_Stripe_Intent_Controller class. |
8
|
|
|
* |
9
|
|
|
* Handles in-checkout AJAX calls, related to Payment Intents. |
10
|
|
|
*/ |
11
|
|
|
class WC_Stripe_Intent_Controller { |
12
|
|
|
/** |
13
|
|
|
* Holds an instance of the gateway class. |
14
|
|
|
* |
15
|
|
|
* @since 4.2.0 |
16
|
|
|
* @var WC_Gateway_Stripe |
17
|
|
|
*/ |
18
|
|
|
protected $gateway; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Class constructor, adds the necessary hooks. |
22
|
|
|
* |
23
|
|
|
* @since 4.2.0 |
24
|
|
|
*/ |
25
|
|
|
public function __construct() { |
26
|
|
|
add_action( 'wc_ajax_wc_stripe_verify_intent', array( $this, 'verify_intent' ) ); |
27
|
|
|
add_action( 'wc_ajax_wc_stripe_create_setup_intent', array( $this, 'create_setup_intent' ) ); |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Returns an instantiated gateway. |
32
|
|
|
* |
33
|
|
|
* @since 4.2.0 |
34
|
|
|
* @return WC_Gateway_Stripe |
35
|
|
|
*/ |
36
|
|
|
protected function get_gateway() { |
37
|
|
|
if ( ! isset( $this->gateway ) ) { |
38
|
|
|
if ( class_exists( 'WC_Subscriptions_Order' ) && function_exists( 'wcs_create_renewal_order' ) ) { |
39
|
|
|
$class_name = 'WC_Stripe_Subs_Compat'; |
40
|
|
|
} else { |
41
|
|
|
$class_name = 'WC_Gateway_Stripe'; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$this->gateway = new $class_name(); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
return $this->gateway; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Loads the order from the current request. |
52
|
|
|
* |
53
|
|
|
* @since 4.2.0 |
54
|
|
|
* @throws WC_Stripe_Exception An exception if there is no order ID or the order does not exist. |
55
|
|
|
* @return WC_Order |
56
|
|
|
*/ |
57
|
|
|
protected function get_order_from_request() { |
58
|
|
View Code Duplication |
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), 'wc_stripe_confirm_pi' ) ) { |
|
|
|
|
59
|
|
|
throw new WC_Stripe_Exception( 'missing-nonce', __( 'CSRF verification failed.', 'woocommerce-gateway-stripe' ) ); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
// Load the order ID. |
63
|
|
|
$order_id = null; |
64
|
|
|
if ( isset( $_GET['order'] ) && absint( $_GET['order'] ) ) { |
65
|
|
|
$order_id = absint( $_GET['order'] ); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// Retrieve the order. |
69
|
|
|
$order = wc_get_order( $order_id ); |
70
|
|
|
|
71
|
|
|
if ( ! $order ) { |
72
|
|
|
throw new WC_Stripe_Exception( 'missing-order', __( 'Missing order ID for payment confirmation', 'woocommerce-gateway-stripe' ) ); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return $order; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Handles successful PaymentIntent authentications. |
80
|
|
|
* |
81
|
|
|
* @since 4.2.0 |
82
|
|
|
*/ |
83
|
|
|
public function verify_intent() { |
84
|
|
|
global $woocommerce; |
85
|
|
|
|
86
|
|
|
$gateway = $this->get_gateway(); |
87
|
|
|
|
88
|
|
|
try { |
89
|
|
|
$order = $this->get_order_from_request(); |
90
|
|
|
} catch ( WC_Stripe_Exception $e ) { |
91
|
|
|
/* translators: Error message text */ |
92
|
|
|
$message = sprintf( __( 'Payment verification error: %s', 'woocommerce-gateway-stripe' ), $e->getLocalizedMessage() ); |
93
|
|
|
wc_add_notice( esc_html( $message ), 'error' ); |
94
|
|
|
|
95
|
|
|
$redirect_url = $woocommerce->cart->is_empty() |
96
|
|
|
? get_permalink( wc_get_page_id( 'shop' ) ) |
97
|
|
|
: wc_get_checkout_url(); |
98
|
|
|
|
99
|
|
|
$this->handle_error( $e, $redirect_url ); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
try { |
103
|
|
|
$gateway->verify_intent_after_checkout( $order ); |
104
|
|
|
|
105
|
|
|
if ( ! isset( $_GET['is_ajax'] ) ) { |
106
|
|
|
$redirect_url = isset( $_GET['redirect_to'] ) // wpcs: csrf ok. |
107
|
|
|
? esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) // wpcs: csrf ok. |
108
|
|
|
: $gateway->get_return_url( $order ); |
109
|
|
|
|
110
|
|
|
wp_safe_redirect( $redirect_url ); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
exit; |
114
|
|
|
} catch ( WC_Stripe_Exception $e ) { |
115
|
|
|
$this->handle_error( $e, $gateway->get_return_url( $order ) ); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Handles exceptions during intent verification. |
121
|
|
|
* |
122
|
|
|
* @since 4.2.0 |
123
|
|
|
* @param WC_Stripe_Exception $e The exception that was thrown. |
124
|
|
|
* @param string $redirect_url An URL to use if a redirect is needed. |
125
|
|
|
*/ |
126
|
|
|
protected function handle_error( $e, $redirect_url ) { |
127
|
|
|
// Log the exception before redirecting. |
128
|
|
|
$message = sprintf( 'PaymentIntent verification exception: %s', $e->getLocalizedMessage() ); |
129
|
|
|
WC_Stripe_Logger::log( $message ); |
130
|
|
|
|
131
|
|
|
// `is_ajax` is only used for PI error reporting, a response is not expected. |
132
|
|
|
if ( isset( $_GET['is_ajax'] ) ) { |
133
|
|
|
exit; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
wp_safe_redirect( $redirect_url ); |
137
|
|
|
exit; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Creates a Setup Intent through AJAX while adding cards. |
142
|
|
|
*/ |
143
|
|
|
public function create_setup_intent() { |
144
|
|
|
if ( |
145
|
|
|
! is_user_logged_in() |
146
|
|
|
|| ! isset( $_POST['stripe_source_id'] ) |
147
|
|
|
|| ! isset( $_POST['nonce'] ) |
148
|
|
|
) { |
149
|
|
|
return; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
try { |
153
|
|
|
$source_id = wc_clean( $_POST['stripe_source_id'] ); |
154
|
|
|
|
155
|
|
|
// 1. Verify. |
156
|
|
View Code Duplication |
if ( |
|
|
|
|
157
|
|
|
! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wc_stripe_create_si' ) |
158
|
|
|
|| ! preg_match( '/^src_.*$/', $source_id ) |
159
|
|
|
) { |
160
|
|
|
throw new Exception( __( 'Unable to verify your request. Please reload the page and try again.', 'woocommerce-gateway-stripe' ) ); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
|
164
|
|
|
// 2. Load the customer ID (and create a customer eventually). |
165
|
|
|
$customer = new WC_Stripe_Customer( wp_get_current_user()->ID ); |
166
|
|
|
|
167
|
|
|
// 3. Attach the source to the customer (Setup Intents require that). |
168
|
|
|
$source_object = $customer->attach_source( $source_id ); |
169
|
|
|
if ( is_wp_error( $source_object ) ) { |
170
|
|
|
throw new Exception( $source_object->get_error_message() ); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
// 4. Generate the setup intent |
174
|
|
|
$setup_intent = WC_Stripe_API::request( |
175
|
|
|
[ |
176
|
|
|
'customer' => $customer->get_id(), |
177
|
|
|
'confirm' => 'true', |
178
|
|
|
'payment_method' => $source_id, |
179
|
|
|
], |
180
|
|
|
'setup_intents' |
181
|
|
|
); |
182
|
|
|
|
183
|
|
|
if ( $setup_intent->error ) { |
184
|
|
|
$error_response_message = print_r( $setup_intent, true ); |
185
|
|
|
WC_Stripe_Logger::log("Failed create Setup Intent while saving a card."); |
186
|
|
|
WC_Stripe_Logger::log("Response: $error_response_message"); |
187
|
|
|
throw new Exception( __( 'Your card could not be set up for future usage.', 'woocommerce-gateway-stripe' ) ); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// 5. Respond. |
191
|
|
|
if ( 'requires_action' === $setup_intent->status ) { |
192
|
|
|
$response = [ |
193
|
|
|
'status' => 'requires_action', |
194
|
|
|
'client_secret' => $setup_intent->client_secret, |
195
|
|
|
]; |
196
|
|
|
} elseif ( 'requires_payment_method' === $setup_intent->status |
197
|
|
|
|| 'requires_confirmation' === $setup_intent->status |
198
|
|
|
|| 'canceled' === $setup_intent->status ) { |
199
|
|
|
// These statuses should not be possible, as such we return an error. |
200
|
|
|
$response = [ |
201
|
|
|
'status' => 'error', |
202
|
|
|
'error' => [ |
203
|
|
|
'type' => 'setup_intent_error', |
204
|
|
|
'message' => __( 'Failed to save payment method.', 'woocommerce-gateway-stripe' ), |
205
|
|
|
], |
206
|
|
|
]; |
207
|
|
|
} else { |
208
|
|
|
// This should only be reached when status is `processing` or `succeeded`, which are |
209
|
|
|
// the only statuses that we haven't explicitly handled. |
210
|
|
|
$response = [ |
211
|
|
|
'status' => 'success', |
212
|
|
|
]; |
213
|
|
|
} |
214
|
|
|
} catch ( Exception $e ) { |
215
|
|
|
$response = [ |
216
|
|
|
'status' => 'error', |
217
|
|
|
'error' => array( |
218
|
|
|
'type' => 'setup_intent_error', |
219
|
|
|
'message' => $e->getMessage(), |
220
|
|
|
), |
221
|
|
|
]; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
echo wp_json_encode( $response ); |
225
|
|
|
exit; |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
new WC_Stripe_Intent_Controller(); |
230
|
|
|
|
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.