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