PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 2.31.2
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v2.31.2
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 All 281 releases
surecart / app / src / Controllers / Rest / CheckoutsController.php
CheckoutsController.php
476 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SureCart\Controllers\Rest;
4
5 use SureCart\Models\Checkout;
6 use SureCart\Models\Form;
7 use SureCart\Models\Product;
8 use SureCart\Models\User;
9 use SureCart\WordPress\Users\CustomerLinkService;
10 use SureCart\WordPress\RecaptchaValidationService;
11
12 /**
13 * Handle price requests through the REST API
14 */
15 class CheckoutsController extends RestController {
16 /**
17 * Class to make the requests.
18 *
19 * @var string
20 */
21 protected $class = Checkout::class;
22
23 /**
24 * Middleware before we make the request.
25 *
26 * @param \SureCart\Models\Model $class Model class instance.
27 * @param \WP_REST_Request $request Request object.
28 *
29 * @return \SureCart\Models\Model|\WP_Error
30 */
31 protected function middleware( $class, \WP_REST_Request $request ) {
32 // if abandoned checkout is enabled, set the return url.
33 $request->set_param( 'abandoned_checkout_return_url', ! empty( $request->get_param( 'abandoned_checkout_enabled' ) ) ? esc_url_raw( get_home_url( null, 'surecart/redirect' ) ) : null );
34
35 return $this->maybeSetUser( $class, $request );
36 }
37
38 /**
39 * Edit model.
40 *
41 * @param \WP_REST_Request $request Rest Request.
42 *
43 * @return \WP_REST_Response|\WP_Error
44 */
45 public function edit( \WP_REST_Request $request ) {
46 // if we have a password, hash it and set it in a transient.
47 // we need to do this because some processors will redirect and we will lose this form data.
48 if ( ! empty( $request->get_param( 'password' ) ) ) {
49 set_transient( 'sc_checkout_password_hash_' . $request['id'], wp_hash_password( $request->get_param( 'password' ) ), DAY_IN_SECONDS );
50 }
51
52 // edit the checkout.
53 $response = parent::edit( $request );
54
55 // check if the email exists and set on record.
56 if ( apply_filters( 'surecart/checkout/finduser', true ) ) {
57 if ( ! empty( $response->email ) ) {
58 $response->email_exists = (bool) email_exists( $response->email );
59 }
60 }
61
62 return $response;
63 }
64
65 /**
66 * Let's set the customer's email and name if they are already logged in.
67 *
68 * @param \SureCart\Models\Model $class Model class instance.
69 * @param \WP_REST_Request $request Request object.
70 *
71 * @return \SureCart\Models\Model|\WP_Error
72 */
73 protected function maybeSetUser( \SureCart\Models\Model $class, \WP_REST_Request $request ) {
74 // get current user.
75 $user = User::current();
76
77 // must be logged in.
78 if ( ! $user ) {
79 return $class;
80 }
81
82 // set the email.
83 $class['email'] = $user->user_email;
84
85 // force the customer id, if it exists.
86 $customer_id = $user->customerId( ! empty( $request['live_mode'] ) ? 'live' : 'test' );
87 if ( ! empty( $customer_id ) ) {
88 $class['customer'] = $customer_id;
89 }
90
91 // if this is a new session, populate the name and phone from the user data.
92 if ( $request->get_method() === 'POST' ) {
93 $class['name'] = $user->display_name;
94 $class['first_name'] = $user->first_name;
95 $class['last_name'] = $user->last_name;
96 $class['phone'] = $user->phone;
97 }
98
99 return $class;
100 }
101
102 /**
103 * Get the form mode
104 *
105 * @param integer $id ID of the form.
106 * @return string Mode of the form.
107 */
108 protected function getFormMode( $id ) {
109 return Form::getMode( (int) $id );
110 }
111
112 /**
113 * Manually pay an order.
114 *
115 * @param \WP_REST_Request $request Rest Request.
116 *
117 * @return \SureCart\Models\Checkout|\WP_Error
118 */
119 public function manuallyPay( \WP_REST_Request $request ) {
120 $checkout = $this->middleware( new $this->class( $request['id'] ), $request );
121 if ( is_wp_error( $checkout ) ) {
122 return $checkout;
123 }
124
125 if ( ! empty( $this->with ) ) {
126 $checkout = $checkout->with( $this->with );
127 }
128
129 $paid = $checkout->where( $request->get_query_params() )->with(
130 [
131 'purchases', // Important: we need to make sure we expand the purchase to provide access.
132 ]
133 )->manuallyPay();
134
135 // purchase created.
136 if ( ! empty( $paid->purchases->data ) ) {
137 foreach ( $paid->purchases->data as $purchase ) {
138 if ( empty( $purchase->revoked ) ) {
139 // broadcast the webhook.
140 do_action( 'surecart/purchase_created', $purchase );
141 }
142 }
143 }
144 return $paid;
145 }
146
147 /**
148 * Finalize an order.
149 *
150 * @param \WP_REST_Request $request Rest Request.
151 *
152 * @return \SureCart\Models\Checkout|\WP_Error
153 */
154 public function finalize( \WP_REST_Request $request ) {
155 $args = $request->get_params();
156
157 // validate form fields and password input.
158 $errors = $this->validate( $args, $request );
159
160 // return early if errors.
161 if ( $errors->has_errors() ) {
162 return $errors;
163 }
164
165 // finalize the order.
166 $checkout = new $this->class( [ 'id' => $request['id'] ] );
167 $finalized = $checkout->where( $request->get_query_params() )
168 ->finalize( $request->get_body_params() );
169
170 // bail if error.
171 if ( is_wp_error( $finalized ) ) {
172 return $finalized;
173 }
174
175 // validate the finalized request.
176 $finalized = $this->validateFinalizeRequest( $finalized, $request );
177
178 // bail if error.
179 if ( is_wp_error( $finalized ) ) {
180 return $finalized;
181 }
182
183 // return the order.
184 return $finalized;
185 }
186
187 /**
188 * Confirm an order.
189 *
190 * This force-fetches the order from the API, runs any automations
191 * and creates the user account tied to the customer.
192 *
193 * @param \WP_REST_Request $request Rest Request.
194 *
195 * @return \SureCart\Models\Checkout|\WP_Error
196 */
197 public function confirm( \WP_REST_Request $request ) {
198 $checkout = $this->middleware( new $this->class(), $request );
199 if ( is_wp_error( $checkout ) ) {
200 return $checkout;
201 }
202
203 $checkout = $checkout->where(
204 array_merge(
205 $request->get_query_params(),
206 [ 'refresh_status' => true ] // Important: Do not remove. This will force syncing with the processor.
207 )
208 )->with(
209 [
210 'purchases', // Important: we need to make sure we expand the purchase to provide access.
211 'customer', // Important: we need to use this to create the WP User with the same info.
212 'manual_payment_method', // Important: we need to use this to display manual payment instructions.
213 ]
214 )->find( $request['id'] );
215
216 // bail if error.
217 if ( is_wp_error( $checkout ) ) {
218 return $checkout;
219 }
220
221 // Create a user account for the customer.
222 $linked = $this->linkCustomerId( $checkout );
223 if ( is_wp_error( $linked ) ) {
224 return $linked;
225 }
226
227 // purchase created.
228 if ( ! empty( $checkout->purchases->data ) ) {
229 foreach ( $checkout->purchases->data as $purchase ) {
230 if ( empty( $purchase->revoked ) ) {
231 // broadcast the webhook.
232 do_action( 'surecart/purchase_created', $purchase );
233 }
234 }
235 }
236
237 // the order is confirmed.
238 do_action( 'surecart/checkout_confirmed', $checkout, $request );
239
240 // return the order.
241 return $checkout;
242 }
243
244 /**
245 * Link the customer id to the order.
246 *
247 * @param \SureCart\Models\Checkout $checkout Checkout model.
248 * @return \WP_User|\WP_Error
249 */
250 public function linkCustomerId( $checkout ) {
251 // get transient.
252 $password_hash = get_transient( 'sc_checkout_password_hash_' . $checkout->id );
253 // delete transient.
254 delete_transient( 'sc_checkout_password_hash_' . $checkout->id );
255 // link customer.
256 $service = new CustomerLinkService( $checkout, $password_hash );
257 return $service->link();
258 }
259
260 /**
261 * Validate the form.
262 *
263 * @param array $args Arguments.
264 * @param object $request Request.
265 * @return \WP_Error Errors.
266 */
267 public function validate( $args, $request ) {
268 $errors = new \WP_Error();
269
270 // Check if honeypot checkbox checked or not.
271 $metadata = $request->get_param( 'metadata' );
272 if ( $metadata && ! empty( $metadata['get_feedback'] ) ) {
273 $errors->add( 'invalid', __( 'Spam check failed. Please try again.', 'surecart' ) );
274 }
275
276 // check recaptcha.
277 $service = new RecaptchaValidationService();
278 if ( $service->isEnabled() ) {
279 $recaptcha = $service->validate( $request->get_param( 'grecaptcha' ) );
280 if ( is_wp_error( $recaptcha ) ) {
281 $errors->add( $recaptcha->get_error_code(), $recaptcha->get_error_message() );
282 }
283 }
284
285 return apply_filters( 'surecart/checkout/validate', $errors, $args, $request );
286 }
287
288 /**
289 * Cancel an checkout
290 *
291 * @param \WP_REST_Request $request Rest Request.
292 *
293 * @return \SureCart\Models\Checkout|\WP_Error
294 */
295 public function cancel( \WP_REST_Request $request ) {
296 $order = $this->middleware( new $this->class( $request['id'] ), $request );
297 if ( is_wp_error( $order ) ) {
298 return $order;
299 }
300 return $order->where( $request->get_query_params() )->cancel();
301 }
302
303 /**
304 * Offer the bump (used for analytics).
305 *
306 * @param \WP_REST_Request $request Rest Request.
307 *
308 * @return \SureCart\Models\Checkout|\WP_Error
309 */
310 public function offerBump( \WP_REST_Request $request ) {
311 $order = $this->middleware( new $this->class( $request['id'] ), $request );
312 if ( is_wp_error( $order ) ) {
313 return $order;
314 }
315 return $order->where( $request->get_query_params() )->offerBump( $request['bump_id'] );
316 }
317
318 /**
319 * Offer the bump (used for analytics).
320 *
321 * @param \WP_REST_Request $request Rest Request.
322 *
323 * @return \SureCart\Models\Checkout|\WP_Error
324 */
325 public function offerUpsell( \WP_REST_Request $request ) {
326 $order = $this->middleware( new $this->class( $request['id'] ), $request );
327 if ( is_wp_error( $order ) ) {
328 return $order;
329 }
330 return $order->where( $request->get_query_params() )->offerUpsell( $request['upsell_id'] );
331 }
332
333 /**
334 * Offer the bump (used for analytics).
335 *
336 * @param \WP_REST_Request $request Rest Request.
337 *
338 * @return \SureCart\Models\Checkout|\WP_Error
339 */
340 public function declineUpsell( \WP_REST_Request $request ) {
341 $order = $this->middleware( new $this->class( $request['id'] ), $request );
342 if ( is_wp_error( $order ) ) {
343 return $order;
344 }
345 return $order->where( $request->get_query_params() )->declineUpsell( $request['upsell_id'] );
346 }
347
348 /**
349 * Check if the user is trying to sign in.
350 * If so, validate credentials before finalizing.
351 *
352 * @param string $email Email.
353 * @param string $password Password.
354 *
355 * @return true|\WP_Error
356 */
357 public function maybeValidateLoginCreds( $email = '', $password = '' ) {
358 // check if the person is signing in using a password and sign them in.
359 if ( $password && $email ) {
360 // user exists, try signing in with password.
361 $user = get_user_by( 'email', $email );
362 // if there's a user, check the username and password before we submit the order.
363 if ( false !== $user ) {
364 return wp_authenticate_username_password( null, $user->user_login, $password );
365 }
366 }
367 return true;
368 }
369
370 /**
371 * Validate the finalized request.
372 * We do this to make sure the form is in "Test" mode
373 * if a test payment is requested. This prevents the spamming of any
374 * forms on your site that are not in test mode or creating access to something
375 * with a fake test payment.
376 *
377 * @param \SureCart\Models\Checkout $finalized Finalized checkout.
378 * @param \WP_REST_Request $request The request.
379 *
380 * @return \WP_Error|\SureCart\Models\Checkout
381 */
382 public function validateFinalizeRequest( $finalized, $request ) {
383 // allow this if the user can edit orders.
384 if ( current_user_can( 'edit_sc_orders' ) ) {
385 return $finalized;
386 }
387
388 // make sure the form id is valid.
389 if ( ! empty( $request['form_id'] ) ) {
390 return $this->validateFormId( $finalized, $request );
391 }
392
393 return $this->validateProductId( $finalized, $request );
394 }
395
396 /**
397 * Validate the product id.
398 *
399 * @param \WP_REST_Request $request The rest request.
400 * @param \SureCart\Models\Order $finalized The finalized order.
401 *
402 * @return \WP_Error|\SureCart\Models\Order
403 */
404 public function validateProductId( $finalized, $request ) {
405 // make sure the product is valid.
406 if ( empty( $request['product_id'] ) ) {
407 return new \WP_Error( 'missing_parameters', 'You must pass a form id or product id in order to make this payment.', [ 'status' => 400 ] );
408 }
409 // make sure the product is valid.
410 $product = Product::find( $request['product_id'] );
411 if ( empty( $product->id ) ) {
412 return new \WP_Error( 'product_id_invalid', esc_html__( 'This product is invalid.', 'surecart' ), [ 'status' => 400 ] );
413 }
414
415 // check to make sure the product buy page is enabled.
416 if ( ! $product->buyLink()->isEnabled() ) {
417 return new \WP_Error( 'product_buy_page_disabled', esc_html__( 'This product is not available for purchase.', 'surecart' ), [ 'status' => 400 ] );
418 }
419
420 // the mode must match.
421 $mode = $product->buyLink()->getMode();
422 // if the request is for test mode, but the form is not test, return an error.
423 if ( false === $finalized->live_mode && 'test' !== $mode ) {
424 return new \WP_Error( 'invalid_mode', 'This page is set to live mode, but the request is for test mode. Please clear any site caching and try again.', [ 'status' => 400 ] );
425 }
426
427 // At least one line item must be for this product.
428 foreach ( $finalized->line_items->data as $line_item ) {
429 if ( $line_item->price->product->id === $product->id ) {
430 return $finalized;
431 }
432 }
433
434 return new \WP_Error( 'product_buy_page_disabled', esc_html__( 'This product is not available for purchase.', 'surecart' ), [ 'status' => 400 ] );
435 }
436
437 /**
438 * Validate the form id.
439 *
440 * @param \WP_REST_Request $request The rest request.
441 * @param \SureCart\Models\Order $finalized The finalized order.
442 *
443 * @return \WP_Error|\SureCart\Models\Order
444 */
445 public function validateFormId( $finalized, $request ) {
446 // the form's mode must be test.
447 $mode = $this->getFormMode( (int) $request['form_id'] );
448
449 // if the request is for test mode, but the form is not test, return an error.
450 if ( false === $finalized->live_mode && 'test' !== $mode ) {
451 return new \WP_Error( 'invalid_mode', 'The form is set to live mode, but the request is for test mode.', [ 'status' => 400 ] );
452 }
453
454 return $finalized;
455 }
456
457 /**
458 * Create or login the user.
459 *
460 * @param string $user_email Username.
461 * @param string $password User password.
462 * @return \WP_Error|true
463 */
464 protected function maybeLoginUser( $user_email, $password = '' ) {
465 if ( empty( $password ) ) {
466 return;
467 }
468 return wp_signon(
469 [
470 'user_login' => $user_email,
471 'user_password' => $password,
472 ]
473 );
474 }
475 }
476