PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 2.8.1
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v2.8.1
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
432 lines 12.7 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 // validate the finalized request.
171 $finalized = $this->validateFinalizeRequest( $finalized, $request );
172
173 // bail if error.
174 if ( is_wp_error( $finalized ) ) {
175 return $finalized;
176 }
177
178 // return the order.
179 return $finalized;
180 }
181
182 /**
183 * Confirm an order.
184 *
185 * This force-fetches the order from the API, runs any automations
186 * and creates the user account tied to the customer.
187 *
188 * @param \WP_REST_Request $request Rest Request.
189 *
190 * @return \SureCart\Models\Checkout|\WP_Error
191 */
192 public function confirm( \WP_REST_Request $request ) {
193 $checkout = $this->middleware( new $this->class(), $request );
194 if ( is_wp_error( $checkout ) ) {
195 return $checkout;
196 }
197
198 $checkout = $checkout->where(
199 array_merge(
200 $request->get_query_params(),
201 [ 'refresh_status' => true ] // Important: Do not remove. This will force syncing with the processor.
202 )
203 )->with(
204 [
205 'purchases', // Important: we need to make sure we expand the purchase to provide access.
206 'customer', // Important: we need to use this to create the WP User with the same info.
207 'manual_payment_method', // Important: we need to use this to display manual payment instructions.
208 ]
209 )->find( $request['id'] );
210
211 // bail if error.
212 if ( is_wp_error( $checkout ) ) {
213 return $checkout;
214 }
215
216 // Create a user account for the customer.
217 $linked = $this->linkCustomerId( $checkout );
218 if ( is_wp_error( $linked ) ) {
219 return $linked;
220 }
221
222 // purchase created.
223 if ( ! empty( $checkout->purchases->data ) ) {
224 foreach ( $checkout->purchases->data as $purchase ) {
225 if ( empty( $purchase->revoked ) ) {
226 // broadcast the webhook.
227 do_action( 'surecart/purchase_created', $purchase );
228 }
229 }
230 }
231
232 // the order is confirmed.
233 do_action( 'surecart/checkout_confirmed', $checkout, $request );
234
235 // return the order.
236 return $checkout;
237 }
238
239 /**
240 * Link the customer id to the order.
241 *
242 * @param \SureCart\Models\Checkout $checkout Checkout model.
243 * @return \WP_User|\WP_Error
244 */
245 public function linkCustomerId( $checkout ) {
246 // get transient.
247 $password_hash = get_transient( 'sc_checkout_password_hash_' . $checkout->id );
248 // delete transient.
249 delete_transient( 'sc_checkout_password_hash_' . $checkout->id );
250 // link customer.
251 $service = new CustomerLinkService( $checkout, $password_hash );
252 return $service->link();
253 }
254
255 /**
256 * Validate the form.
257 *
258 * @param array $args Arguments.
259 * @param object $request Request.
260 * @return \WP_Error Errors.
261 */
262 public function validate( $args, $request ) {
263 $errors = new \WP_Error();
264
265 // check if they are trying to sign in.
266 // $valid_login = $this->maybeValidateLoginCreds( $request->get_param( 'email' ), $request->get_param( 'password' ) );
267 // if ( is_wp_error( $valid_login ) ) {
268 // $errors->add( $valid_login->get_error_code(), $valid_login->get_error_message() );
269 // }
270
271 // Check if honeypot checkbox checked or not.
272 $metadata = $request->get_param( 'metadata' );
273 if ( $metadata && ! empty( $metadata['get_feedback'] ) ) {
274 $errors->add( 'invalid', __( 'Spam check failed. Please try again.', 'surecart' ) );
275 }
276
277 // check recaptcha.
278 $service = new RecaptchaValidationService();
279 if ( $service->isEnabled() ) {
280 $recaptcha = $service->validate( $request->get_param( 'grecaptcha' ) );
281 if ( is_wp_error( $recaptcha ) ) {
282 $errors->add( $recaptcha->get_error_code(), $recaptcha->get_error_message() );
283 }
284 }
285
286 return apply_filters( 'surecart/checkout/validate', $errors, $args, $request );
287 }
288
289 /**
290 * Check if the user is trying to sign in.
291 * If so, validate credentials before finalizing.
292 *
293 * @param string $email Email.
294 * @param string $password Password.
295 *
296 * @return true|\WP_Error
297 */
298 public function maybeValidateLoginCreds( $email = '', $password = '' ) {
299 // check if the person is signing in using a password and sign them in.
300 if ( $password && $email ) {
301 // user exists, try signing in with password.
302 $user = get_user_by( 'email', $email );
303 // if there's a user, check the username and password before we submit the order.
304 if ( false !== $user ) {
305 return wp_authenticate_username_password( null, $user->user_login, $password );
306 }
307 }
308 return true;
309 }
310
311 /**
312 * Validate the finalized request.
313 * We do this to make sure the form is in "Test" mode
314 * if a test payment is requested. This prevents the spamming of any
315 * forms on your site that are not in test mode or creating access to something
316 * with a fake test payment.
317 *
318 * @param \SureCart\Models\Checkout $finalized Finalized checkout.
319 * @param \WP_REST_Request $request The request.
320 *
321 * @return \WP_Error|\SureCart\Models\Checkout
322 */
323 public function validateFinalizeRequest( $finalized, $request ) {
324 // allow this if the user can edit orders.
325 if ( current_user_can( 'edit_sc_orders' ) ) {
326 return $finalized;
327 }
328
329 // make sure the form id is valid.
330 if ( ! empty( $request['form_id'] ) ) {
331 return $this->validateFormId( $finalized, $request );
332 }
333
334 return $this->validateProductId( $finalized, $request );
335 }
336
337 /**
338 * Validate the product id.
339 *
340 * @param \WP_REST_Request $request The rest request.
341 * @param \SureCart\Models\Order $finalized The finalized order.
342 *
343 * @return \WP_Error|\SureCart\Models\Order
344 */
345 public function validateProductId( $finalized, $request ) {
346 // make sure the product is valid.
347 if ( empty( $request['product_id'] ) ) {
348 return new \WP_Error( 'missing_parameters', 'You must pass a form id or product id in order to make this payment.', [ 'status' => 400 ] );
349 }
350 // make sure the product is valid.
351 $product = Product::find( $request['product_id'] );
352 if ( empty( $product->id ) ) {
353 return new \WP_Error( 'product_id_invalid', esc_html__( 'This product is invalid.', 'surecart' ), [ 'status' => 400 ] );
354 }
355
356 // check to make sure the product buy page is enabled.
357 if ( ! $product->buyLink()->isEnabled() ) {
358 return new \WP_Error( 'product_buy_page_disabled', esc_html__( 'This product is not available for purchase.', 'surecart' ), [ 'status' => 400 ] );
359 }
360
361 // the mode must match.
362 $mode = $product->buyLink()->getMode();
363 // if the request is for test mode, but the form is not test, return an error.
364 if ( false === $finalized->live_mode && 'test' !== $mode ) {
365 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 ] );
366 }
367
368 // At least one line item must be for this product.
369 foreach ( $finalized->line_items->data as $line_item ) {
370 if ( $line_item->price->product->id === $product->id ) {
371 return $finalized;
372 }
373 }
374
375 return new \WP_Error( 'product_buy_page_disabled', esc_html__( 'This product is not available for purchase.', 'surecart' ), [ 'status' => 400 ] );
376 }
377
378 /**
379 * Validate the form id.
380 *
381 * @param \WP_REST_Request $request The rest request.
382 * @param \SureCart\Models\Order $finalized The finalized order.
383 *
384 * @return \WP_Error|\SureCart\Models\Order
385 */
386 public function validateFormId( $finalized, $request ) {
387 // the form's mode must be test.
388 $mode = $this->getFormMode( (int) $request['form_id'] );
389
390 // if the request is for test mode, but the form is not test, return an error.
391 if ( false === $finalized->live_mode && 'test' !== $mode ) {
392 return new \WP_Error( 'invalid_mode', 'The form is set to live mode, but the request is for test mode.', [ 'status' => 400 ] );
393 }
394
395 return $finalized;
396 }
397
398 /**
399 * Create or login the user.
400 *
401 * @param string $user_email Username.
402 * @param string $password User password.
403 * @return \WP_Error|true
404 */
405 protected function maybeLoginUser( $user_email, $password = '' ) {
406 if ( empty( $password ) ) {
407 return;
408 }
409 return wp_signon(
410 [
411 'user_login' => $user_email,
412 'user_password' => $password,
413 ]
414 );
415 }
416
417 /**
418 * Cancel an checkout
419 *
420 * @param \WP_REST_Request $request Rest Request.
421 *
422 * @return \SureCart\Models\Checkout|\WP_Error
423 */
424 public function cancel( \WP_REST_Request $request ) {
425 $order = $this->middleware( new $this->class( $request['id'] ), $request );
426 if ( is_wp_error( $order ) ) {
427 return $order;
428 }
429 return $order->where( $request->get_query_params() )->cancel();
430 }
431 }
432