PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.1
Tutor LMS – eLearning and online course solution v4.0.1
4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / ecommerce / HooksHandler.php
tutor / ecommerce Last commit date
Cart 11 months ago PaymentGateways 1 month ago AdminMenu.php 4 days ago BillingController.php 4 days ago CartController.php 4 days ago CheckoutController.php 4 days ago CouponController.php 4 days ago Ecommerce.php 4 days ago EmailController.php 11 months ago HooksHandler.php 1 day ago OptionKeys.php 1 year ago OrderActivitiesController.php 1 year ago OrderController.php 4 days ago PaymentHandler.php 9 months ago Settings.php 4 days ago Tax.php 9 months ago currency.php 5 months ago
HooksHandler.php
600 lines
1 <?php
2 /**
3 * Handle ecommerce hooks
4 *
5 * @package Tutor\Ecommerce
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 3.0.0
9 */
10
11 namespace Tutor\Ecommerce;
12
13 use TUTOR\Input;
14 use TUTOR\Course;
15 use TUTOR\Earnings;
16 use Tutor\Models\CartModel;
17 use Tutor\Models\OrderModel;
18 use Tutor\Helpers\QueryHelper;
19 use Tutor\Models\EnrollmentModel;
20 use Tutor\Models\OrderMetaModel;
21 use Tutor\Models\OrderActivitiesModel;
22 use TUTOR\User;
23 use TutorPro\CourseBundle\Models\BundleModel;
24 use TutorPro\CourseBundle\CustomPosts\CourseBundle;
25
26 /**
27 * Handle custom hooks
28 */
29 class HooksHandler {
30
31 /**
32 * OrderModel
33 *
34 * @since 3.0.0
35 *
36 * @var OrderModel
37 */
38 private $order_model;
39
40 /**
41 * OrderActivitiesModel
42 *
43 * @since 3.0.0
44 *
45 * @var OrderActivitiesModel
46 */
47 private $order_activities_model;
48
49 /**
50 * Coupon controller instance
51 *
52 * @since 3.5.0
53 *
54 * @var CouponController
55 */
56 private $coupon_ctrl;
57
58 /**
59 * Register hooks & resolve props
60 *
61 * @since 3.0.0
62 */
63 public function __construct() {
64 $this->order_activities_model = new OrderActivitiesModel();
65 $this->order_model = new OrderModel();
66 $this->coupon_ctrl = new CouponController( false );
67
68 // Register hooks.
69 add_filter( 'tutor_course_sell_by', array( $this, 'alter_course_sell_by' ) );
70 add_filter( 'get_tutor_course_price', array( $this, 'alter_course_price' ), 10, 2 );
71
72 // Order hooks.
73 add_action( 'tutor_order_payment_updated', array( $this, 'handle_payment_updated_webhook' ) );
74
75 add_action( 'tutor_order_payment_status_changed', array( $this, 'handle_payment_status_changed' ), 10, 3 );
76
77 add_action( 'tutor_order_placement_success', array( $this, 'handle_order_placement_success' ) );
78
79 /**
80 * Clear order menu badge count
81 *
82 * @since 3.0.0
83 */
84 add_action( 'tutor_order_placed', array( $this, 'clear_order_badge_count' ) );
85 add_action( 'tutor_order_payment_status_changed', array( $this, 'clear_order_badge_count' ) );
86 add_action( 'tutor_before_order_bulk_action', array( $this, 'clear_order_badge_count' ) );
87 add_filter( 'tutor_before_order_create', array( $this, 'update_order_data' ) );
88 add_action( 'tutor_order_placed', array( $this, 'handle_free_checkout' ) );
89 add_filter( 'tutor_redirect_url_after_checkout', array( $this, 'redirect_to_the_course' ), 10, 3 );
90
91 /**
92 * Store customer billing information for each order.
93 *
94 * @since 3.5.0
95 */
96 add_action( 'tutor_order_placed', array( $this, 'store_billing_address_for_order' ) );
97 add_action( 'tutor_order_updated', array( $this, 'store_billing_address_for_order' ) );
98 }
99
100 /**
101 * Clear order menu badge count
102 *
103 * @since 3.0.0
104 *
105 * @return void
106 */
107 public function clear_order_badge_count() {
108 delete_transient( OrderModel::TRANSIENT_ORDER_BADGE_COUNT );
109 }
110
111 /**
112 * Store order activity before bulk action.
113 *
114 * @since 3.0.0
115 *
116 * @param string $bulk_action The bulk action being performed.
117 * @param array $bulk_ids The IDs of the orders being acted upon.
118 *
119 * @return void
120 */
121 public function after_order_bulk_action( $bulk_action, $bulk_ids ) {
122 $order_status = $this->order_model->get_order_status_by_payment_status( $bulk_action );
123
124 $cancel_reason = Input::post( 'cancel_reason', '' );
125 foreach ( $bulk_ids as $order_id ) {
126 try {
127 $this->manage_earnings_and_enrollments( $order_status, $order_id );
128 $data = (object) array(
129 'order_id' => $order_id,
130 'meta_key' => $this->order_activities_model::META_KEY_HISTORY,
131 'meta_value' => "Order mark as {$bulk_action} {$cancel_reason}",
132 );
133 $this->order_activities_model->add_order_meta( $data );
134 } catch ( \Throwable $th ) {
135 // Log message with line & file.
136 error_log( $th->getMessage() . ' in ' . $th->getFile() . ' at line ' . $th->getLine() );
137 }
138 }
139 }
140
141 /**
142 * Alter course sell by value
143 *
144 * @since 3.0.0
145 *
146 * @param mixed $sell_by Default sell by.
147 *
148 * @return mixed
149 */
150 public function alter_course_sell_by( $sell_by ) {
151 if ( tutor_utils()->is_monetize_by_tutor() ) {
152 $sell_by = Ecommerce::MONETIZE_BY;
153 }
154
155 return $sell_by;
156 }
157
158 /**
159 * Alter course price to show price on the course
160 * entry box
161 *
162 * @since 3.0.0
163 *
164 * @param mixed $price Course price.
165 * @param int $course_id Course id.
166 *
167 * @return mixed
168 */
169 public function alter_course_price( $price, $course_id ) {
170 $price_type = tutor_utils()->price_type( $course_id );
171 if ( tutor_utils()->is_monetize_by_tutor() && Course::PRICE_TYPE_PAID === $price_type ) {
172 $price = tutor_get_course_formatted_price_html( $course_id, false );
173 }
174
175 return $price;
176 }
177
178 /**
179 * Handle payment updated webhook
180 *
181 * @since 3.0.0
182 *
183 * @param object $res Response data.
184 * {order_id, transaction_id, payment_status, payment_method, redirectUrl}.
185 *
186 * @return void
187 */
188 public function handle_payment_updated_webhook( $res ) {
189 $order_id = $res->id;
190 $new_payment_status = $res->payment_status;
191 $transaction_id = $res->transaction_id;
192
193 $order_details = $this->order_model->get_order_by_id( $order_id );
194
195 /**
196 * Ignore canceled/failed webhooks for old/failed payment session to avoid unenrolling paid users.
197 *
198 * @since 3.9.7
199 */
200 $is_valid_paid_order = OrderModel::ORDER_COMPLETED === $order_details->order_status && OrderModel::PAYMENT_PAID === $order_details->payment_status;
201
202 if ( $order_details && ! $is_valid_paid_order ) {
203 $prev_payment_status = $order_details->payment_status;
204
205 $order_data = array(
206 'order_status' => $order_details->order_status,
207 'payment_status' => $new_payment_status,
208 'payment_method' => $res->payment_method,
209 'payment_payloads' => $res->payment_payload,
210 'transaction_id' => $transaction_id,
211 'earnings' => $res->earnings,
212 'fees' => $res->fees,
213 'updated_at_gmt' => current_time( 'mysql', true ),
214 );
215
216 switch ( $new_payment_status ) {
217 case $this->order_model::PAYMENT_PAID:
218 $order_data['order_status'] = $this->order_model::ORDER_COMPLETED;
219 break;
220 case $this->order_model::PAYMENT_FAILED:
221 case $this->order_model::PAYMENT_REFUNDED:
222 $order_data['order_status'] = $this->order_model::ORDER_CANCELLED;
223 break;
224 case $this->order_model::PAYMENT_PENDING:
225 $order_data['order_status'] = $this->order_model::ORDER_PENDING;
226 break;
227 }
228
229 $update = $this->order_model->update_order( $order_id, $order_data );
230 if ( $update ) {
231 // Provide hook after update order.
232 do_action( 'tutor_order_payment_status_changed', $order_id, $prev_payment_status, $new_payment_status );
233 }
234 }
235 }
236
237 /**
238 * Update enrollment & earnings based on payment status
239 *
240 * @since 3.0.0
241 *
242 * @param int $order_id Order id.
243 * @param string $prev_payment_status previous payment status.
244 * @param string $new_payment_status new payment status.
245 *
246 * @return void
247 */
248 public function handle_payment_status_changed( $order_id, $prev_payment_status, $new_payment_status ) {
249 $cancel_reason = Input::post( 'cancel_reason' );
250 $order_status = $this->order_model->get_order_status_by_payment_status( $new_payment_status );
251
252 // Store activity.
253 $data = (object) array(
254 'order_id' => $order_id,
255 'meta_key' => $this->order_activities_model::META_KEY_HISTORY,
256 'meta_value' => 'Order marked as ' . $new_payment_status,
257 );
258
259 if ( $cancel_reason ) {
260 $meta_value = array(
261 'message' => 'Order marked as ' . $new_payment_status,
262 'cancel_reason' => $cancel_reason,
263 );
264 $data->meta_value = json_encode( $meta_value );
265 }
266
267 $this->order_activities_model->add_order_meta( $data );
268
269 $this->manage_earnings_and_enrollments( $order_status, $order_id );
270
271 // Store coupon usage.
272 $this->coupon_ctrl->store_coupon_usage( $order_id );
273 }
274
275 /**
276 * Handle new order placement
277 *
278 * Clear cart items, managing enrollment & earnings
279 *
280 * @since 3.0.0
281 *
282 * @param int $order_id Order id.
283 *
284 * @return void
285 */
286 public function handle_order_placement_success( int $order_id ) {
287 $order_data = $this->order_model->get_order_by_id( $order_id );
288 if ( $order_data ) {
289 $user_id = $order_data->student->id;
290
291 ( new CartModel() )->clear_user_cart( $user_id );
292
293 // Manage enrollment & earnings.
294 $order = ( new OrderModel() )->get_order_by_id( $order_id );
295 $payment_status = $order->payment_status;
296
297 $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status );
298
299 $this->manage_earnings_and_enrollments( $order_status, $order_id );
300 }
301 }
302
303 /**
304 * Check if order is bundle order
305 *
306 * @since 3.0.0
307 *
308 * @param object $order order object.
309 * @param int $object_id object id.
310 *
311 * @return boolean
312 */
313 private function is_bundle_order( $order, $object_id ) {
314 return tutor_utils()->is_addon_enabled( 'course-bundle' )
315 && in_array( $order->order_type, array( $this->order_model::TYPE_SINGLE_ORDER, $this->order_model::TYPE_SUBSCRIPTION ), true )
316 && 'course-bundle' === get_post_type( $object_id );
317 }
318
319 /**
320 * Manage earnings after order bulk action
321 *
322 * @since 3.0.0
323 *
324 * @param string $order_status Order status.
325 * @param int $order_id Order ID.
326 *
327 * @return void
328 */
329 public function manage_earnings_and_enrollments( string $order_status, int $order_id ) {
330 $earnings = Earnings::get_instance();
331 $order = $this->order_model->get_order_by_id( $order_id );
332 $student_id = $order->student->id;
333
334 $enrollment_status = ( OrderModel::ORDER_COMPLETED === $order_status ? 'completed' : ( OrderModel::ORDER_INCOMPLETE === $order->order_status ? 'pending' : 'cancel' ) );
335
336 /**
337 * Handles enrollment updates from the admin refund modal.
338 *
339 * - No action is taken if the enrollment is already in the canceled state.
340 * - If the administrator explicitly chooses to remove the enrollment,
341 * the enrollment status is updated to canceled.
342 *
343 * @since 4.0.0
344 */
345 if ( 'backend_refund' === Input::post( 'context' ) && User::is_admin() ) {
346 $has_enrollment = isset( $order->enrollment ) && is_object( $order->enrollment );
347 if ( $has_enrollment ) {
348 if ( EnrollmentModel::STATUS_CANCEL === $order->enrollment->status ) {
349 $enrollment_status = EnrollmentModel::STATUS_CANCEL;
350 }
351
352 if ( EnrollmentModel::STATUS_COMPLETED === $order->enrollment->status && Input::has( 'is_remove_enrolment', Input::POST_REQUEST ) ) {
353 $remove_enrollment = Input::post( 'is_remove_enrolment', false, Input::TYPE_BOOL );
354 $enrollment_status = $remove_enrollment ? EnrollmentModel::STATUS_CANCEL : EnrollmentModel::STATUS_COMPLETED;
355 }
356 }
357 }
358
359 foreach ( $order->items as $item ) {
360 $object_id = $item->id; // It could be course/bundle/plan id.
361 $is_gift_item = apply_filters( 'tutor_is_gift_item', false, $item->primary_id );
362 if ( $is_gift_item ) {
363 continue;
364 }
365
366 if ( $this->order_model::TYPE_SINGLE_ORDER !== $order->order_type ) {
367 /**
368 * Do not process enrollment for membership plan.
369 *
370 * @since 3.2.0
371 */
372 $plan_info = apply_filters( 'tutor_get_plan_info', null, $object_id );
373 if ( $plan_info && isset( $plan_info->is_membership_plan ) && $plan_info->is_membership_plan ) {
374 continue;
375 } else {
376 $object_id = apply_filters( 'tutor_subscription_course_by_plan', $item->id, $order );
377 }
378
379 /**
380 * Do not process enrollment for subscription order refund.
381 * It will be handled by subscription controller's handle_order_refund method.
382 *
383 * @since 3.3.0
384 */
385 if ( Input::has( 'is_cancel_subscription' ) ) {
386 continue;
387 }
388 }
389
390 $has_enrollment = EnrollmentModel::is_enrolled( $object_id, $student_id, false );
391 if ( $has_enrollment ) {
392 // Update enrollment status based on order status.
393 $update = EnrollmentModel::update_enrollments( $enrollment_status, array( $has_enrollment->ID ) );
394 if ( $update ) {
395 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
396 if ( 'completed' === $enrollment_status ) {
397 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
398 } else {
399 BundleModel::disenroll_from_bundle_courses( $object_id, $student_id );
400 }
401 }
402
403 /**
404 * For subscription, renewal no need to update order id.
405 */
406 if ( $this->order_model->is_single_order( $order ) ) {
407 update_post_meta( $has_enrollment->ID, EnrollmentModel::ENROLLMENT_ORDER_ID_META, $order_id );
408 if ( tutor_utils()->is_addon_enabled( 'course-bundle' ) ) {
409 $has_bundle_enrollment_meta = get_post_meta( $has_enrollment->ID, CourseBundle::BUNDLE_ENROLLMENT_META, true );
410 if ( $has_bundle_enrollment_meta ) {
411 delete_post_meta( $has_enrollment->ID, CourseBundle::BUNDLE_ENROLLMENT_META );
412 }
413 }
414 /**
415 * Update enrollment expiry date if it is set in a course.
416 */
417 if ( tutor()->course_post_type === get_post_type( $object_id ) ) {
418 $is_set_enrollment_expiry = (int) get_tutor_course_settings( $object_id, 'enrollment_expiry' );
419 $enrollment_expiry_enabled = (bool) get_tutor_option( 'enrollment_expiry_enabled' );
420 if ( $enrollment_expiry_enabled && $is_set_enrollment_expiry ) {
421 global $wpdb;
422 QueryHelper::update(
423 $wpdb->posts,
424 array(
425 'post_date' => current_time( 'mysql' ),
426 'post_date_gmt' => current_time( 'mysql', true ),
427 ),
428 array(
429 'ID' => $has_enrollment->ID,
430 'post_type' => tutor()->enrollment_post_type,
431 )
432 );
433 }
434 }
435
436 if ( OrderModel::ORDER_COMPLETED === $order_status && OrderModel::PAYMENT_PARTIALLY_REFUNDED !== $order->payment_status ) {
437 do_action( 'tutor_after_enrolled', $object_id, $student_id, $has_enrollment->ID );
438 }
439 }
440
441 if ( 'completed' === $enrollment_status ) {
442 do_action( 'tutor_order_enrolled', $order, $has_enrollment->ID );
443 }
444 }
445 } else {
446 if ( $order->order_status === $this->order_model::ORDER_COMPLETED ) {
447 // Insert enrollment.
448 add_filter( 'tutor_enroll_data', fn( $enroll_data) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
449
450 $enrollment_id = EnrollmentModel::do_enroll( $object_id, $order_id, $student_id );
451 if ( $enrollment_id ) {
452 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
453 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
454 }
455 update_post_meta( $enrollment_id, EnrollmentModel::ENROLLMENT_ORDER_ID_META, $order_id );
456
457 do_action( 'tutor_order_enrolled', $order, $enrollment_id );
458 } else {
459 // Log error message with student id and course id.
460 error_log( "Error updating enrollment for student {$student_id} and course {$object_id}" );
461 }
462 }
463 }
464 }
465
466 // Update earnings.
467 $earnings->prepare_order_earnings( $order_id );
468 $earnings->remove_before_store_earnings();
469 }
470
471 /**
472 * Update order data for the free checkout
473 *
474 * @since 3.4.0
475 *
476 * @param array $order_data Order data.
477 *
478 * @return array
479 */
480 public function update_order_data( array $order_data ) {
481 if ( empty( $order_data['total_price'] ) && OrderModel::TYPE_SINGLE_ORDER === $order_data['order_type'] ) {
482 $order_data['order_status'] = OrderModel::ORDER_COMPLETED;
483 $order_data['payment_status'] = OrderModel::PAYMENT_PAID;
484 $order_data['payment_method'] = 'free';
485 }
486 return $order_data;
487 }
488
489 /**
490 * Enroll user to the course when free checkout
491 *
492 * @since 3.4.0
493 *
494 * @param array $order_data Order data.
495 *
496 * @return array
497 */
498 public function handle_free_checkout( array $order_data ) {
499 if ( empty( $order_data['total_price'] ) && OrderModel::TYPE_SINGLE_ORDER === $order_data['order_type'] ) {
500 $order_id = $order_data['id'];
501 $user_id = $order_data['user_id'];
502 $items = $order_data['items'];
503 foreach ( $items as $item ) {
504 $is_gift_item = apply_filters( 'tutor_is_gift_item', false, $item );
505 if ( $is_gift_item ) {
506 continue;
507 }
508
509 add_filter( 'tutor_enroll_data', fn( $enroll_data ) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
510
511 $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id );
512 if ( $enrolled_id && tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) {
513 BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id );
514 }
515 }
516
517 // Store coupon usage.
518 $this->coupon_ctrl->store_coupon_usage( $order_id );
519 }
520 return $order_data;
521 }
522
523 /**
524 * Redirect user to the course after free checkout when item is 1.
525 * If user checkout multiple items and keep the default behavior.
526 *
527 * @since 3.4.0
528 *
529 * @param string $url Default redirect url.
530 * @param string $status Order placement status.
531 * @param integer $order_id Order id.
532 *
533 * @return string
534 */
535 public function redirect_to_the_course( string $url, string $status, int $order_id ): string {
536 $user_id = get_current_user_id();
537 if ( OrderModel::ORDER_PLACEMENT_SUCCESS === $status ) {
538 $order = $this->order_model->get_order_by_id( $order_id );
539 if ( $order && count( $order->items ) === 1 && empty( $order->total_price ) && OrderModel::TYPE_SINGLE_ORDER === $order->order_type ) {
540
541 // Firing hook to clear cart.
542 do_action( 'tutor_order_placement_success', $order_id );
543
544 // Clear the alert message.
545 delete_transient( CheckoutController::PAY_NOW_ALERT_MSG_TRANSIENT_KEY . $user_id );
546 delete_transient( CheckoutController::PAY_NOW_ERROR_TRANSIENT_KEY . $user_id );
547 $course_id = $order->items[0]->id;
548 $url = get_the_permalink( $course_id );
549 }
550 }
551 return $url;
552 }
553
554 /**
555 * Store billing address for an order when order is placed.
556 *
557 * @since 3.5.0
558 *
559 * @param array $order_data order data.
560 *
561 * @return void
562 */
563 public function store_billing_address_for_order( array $order_data ) {
564 $order_id = $order_data['id'];
565 $user_id = $order_data['user_id'];
566 $billing_info = ( new BillingController( false ) )->get_billing_info( $user_id );
567
568 /**
569 * JSON_UNESCAPED_UNICODE is used to ensure that the billing info is stored in a readable format.
570 * This is important for languages that use non-ASCII characters like ñ, á, é, í, ó, ú, ü, etc.
571 *
572 * @since 3.7.1
573 */
574 $meta_value = '{}';
575 if ( $billing_info ) {
576 $meta_value = wp_json_encode( $billing_info, JSON_UNESCAPED_UNICODE );
577 } else {
578 /**
579 * Store user data as billing info
580 * If user has no billing info during order like manual enrollment from CSV.
581 */
582 $user_data = get_userdata( $user_id );
583 $meta_value = wp_json_encode(
584 array(
585 'billing_first_name' => $user_data->first_name,
586 'billing_last_name' => $user_data->last_name,
587 'billing_email' => $user_data->user_email,
588 ),
589 JSON_UNESCAPED_UNICODE
590 );
591 }
592
593 OrderMetaModel::add_meta(
594 $order_id,
595 OrderModel::META_KEY_BILLING_ADDRESS,
596 $meta_value
597 );
598 }
599 }
600