PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
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 10 months ago PaymentGateways 4 weeks ago AdminMenu.php 1 day ago BillingController.php 1 day ago CartController.php 1 day ago CheckoutController.php 1 day ago CouponController.php 1 day ago Ecommerce.php 1 day ago EmailController.php 11 months ago HooksHandler.php 1 day ago OptionKeys.php 1 year ago OrderActivitiesController.php 1 year ago OrderController.php 1 day ago PaymentHandler.php 9 months ago Settings.php 1 day ago Tax.php 9 months ago currency.php 5 months ago
HooksHandler.php
598 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 $has_bundle_enrollment_meta = get_post_meta( $has_enrollment->ID, CourseBundle::BUNDLE_ENROLLMENT_META, true );
409 if ( $has_bundle_enrollment_meta ) {
410 delete_post_meta( $has_enrollment->ID, CourseBundle::BUNDLE_ENROLLMENT_META );
411 }
412 /**
413 * Update enrollment expiry date if it is set in a course.
414 */
415 if ( tutor()->course_post_type === get_post_type( $object_id ) ) {
416 $is_set_enrollment_expiry = (int) get_tutor_course_settings( $object_id, 'enrollment_expiry' );
417 $enrollment_expiry_enabled = (bool) get_tutor_option( 'enrollment_expiry_enabled' );
418 if ( $enrollment_expiry_enabled && $is_set_enrollment_expiry ) {
419 global $wpdb;
420 QueryHelper::update(
421 $wpdb->posts,
422 array(
423 'post_date' => current_time( 'mysql' ),
424 'post_date_gmt' => current_time( 'mysql', true ),
425 ),
426 array(
427 'ID' => $has_enrollment->ID,
428 'post_type' => tutor()->enrollment_post_type,
429 )
430 );
431 }
432 }
433
434 if ( OrderModel::ORDER_COMPLETED === $order_status && OrderModel::PAYMENT_PARTIALLY_REFUNDED !== $order->payment_status ) {
435 do_action( 'tutor_after_enrolled', $object_id, $student_id, $has_enrollment->ID );
436 }
437 }
438
439 if ( 'completed' === $enrollment_status ) {
440 do_action( 'tutor_order_enrolled', $order, $has_enrollment->ID );
441 }
442 }
443 } else {
444 if ( $order->order_status === $this->order_model::ORDER_COMPLETED ) {
445 // Insert enrollment.
446 add_filter( 'tutor_enroll_data', fn( $enroll_data) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
447
448 $enrollment_id = EnrollmentModel::do_enroll( $object_id, $order_id, $student_id );
449 if ( $enrollment_id ) {
450 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
451 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
452 }
453 update_post_meta( $enrollment_id, EnrollmentModel::ENROLLMENT_ORDER_ID_META, $order_id );
454
455 do_action( 'tutor_order_enrolled', $order, $enrollment_id );
456 } else {
457 // Log error message with student id and course id.
458 error_log( "Error updating enrollment for student {$student_id} and course {$object_id}" );
459 }
460 }
461 }
462 }
463
464 // Update earnings.
465 $earnings->prepare_order_earnings( $order_id );
466 $earnings->remove_before_store_earnings();
467 }
468
469 /**
470 * Update order data for the free checkout
471 *
472 * @since 3.4.0
473 *
474 * @param array $order_data Order data.
475 *
476 * @return array
477 */
478 public function update_order_data( array $order_data ) {
479 if ( empty( $order_data['total_price'] ) && OrderModel::TYPE_SINGLE_ORDER === $order_data['order_type'] ) {
480 $order_data['order_status'] = OrderModel::ORDER_COMPLETED;
481 $order_data['payment_status'] = OrderModel::PAYMENT_PAID;
482 $order_data['payment_method'] = 'free';
483 }
484 return $order_data;
485 }
486
487 /**
488 * Enroll user to the course when free checkout
489 *
490 * @since 3.4.0
491 *
492 * @param array $order_data Order data.
493 *
494 * @return array
495 */
496 public function handle_free_checkout( array $order_data ) {
497 if ( empty( $order_data['total_price'] ) && OrderModel::TYPE_SINGLE_ORDER === $order_data['order_type'] ) {
498 $order_id = $order_data['id'];
499 $user_id = $order_data['user_id'];
500 $items = $order_data['items'];
501 foreach ( $items as $item ) {
502 $is_gift_item = apply_filters( 'tutor_is_gift_item', false, $item );
503 if ( $is_gift_item ) {
504 continue;
505 }
506
507 add_filter( 'tutor_enroll_data', fn( $enroll_data ) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
508
509 $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id );
510 if ( $enrolled_id && tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) {
511 BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id );
512 }
513 }
514
515 // Store coupon usage.
516 $this->coupon_ctrl->store_coupon_usage( $order_id );
517 }
518 return $order_data;
519 }
520
521 /**
522 * Redirect user to the course after free checkout when item is 1.
523 * If user checkout multiple items and keep the default behavior.
524 *
525 * @since 3.4.0
526 *
527 * @param string $url Default redirect url.
528 * @param string $status Order placement status.
529 * @param integer $order_id Order id.
530 *
531 * @return string
532 */
533 public function redirect_to_the_course( string $url, string $status, int $order_id ): string {
534 $user_id = get_current_user_id();
535 if ( OrderModel::ORDER_PLACEMENT_SUCCESS === $status ) {
536 $order = $this->order_model->get_order_by_id( $order_id );
537 if ( $order && count( $order->items ) === 1 && empty( $order->total_price ) && OrderModel::TYPE_SINGLE_ORDER === $order->order_type ) {
538
539 // Firing hook to clear cart.
540 do_action( 'tutor_order_placement_success', $order_id );
541
542 // Clear the alert message.
543 delete_transient( CheckoutController::PAY_NOW_ALERT_MSG_TRANSIENT_KEY . $user_id );
544 delete_transient( CheckoutController::PAY_NOW_ERROR_TRANSIENT_KEY . $user_id );
545 $course_id = $order->items[0]->id;
546 $url = get_the_permalink( $course_id );
547 }
548 }
549 return $url;
550 }
551
552 /**
553 * Store billing address for an order when order is placed.
554 *
555 * @since 3.5.0
556 *
557 * @param array $order_data order data.
558 *
559 * @return void
560 */
561 public function store_billing_address_for_order( array $order_data ) {
562 $order_id = $order_data['id'];
563 $user_id = $order_data['user_id'];
564 $billing_info = ( new BillingController( false ) )->get_billing_info( $user_id );
565
566 /**
567 * JSON_UNESCAPED_UNICODE is used to ensure that the billing info is stored in a readable format.
568 * This is important for languages that use non-ASCII characters like ñ, á, é, í, ó, ú, ü, etc.
569 *
570 * @since 3.7.1
571 */
572 $meta_value = '{}';
573 if ( $billing_info ) {
574 $meta_value = wp_json_encode( $billing_info, JSON_UNESCAPED_UNICODE );
575 } else {
576 /**
577 * Store user data as billing info
578 * If user has no billing info during order like manual enrollment from CSV.
579 */
580 $user_data = get_userdata( $user_id );
581 $meta_value = wp_json_encode(
582 array(
583 'billing_first_name' => $user_data->first_name,
584 'billing_last_name' => $user_data->last_name,
585 'billing_email' => $user_data->user_email,
586 ),
587 JSON_UNESCAPED_UNICODE
588 );
589 }
590
591 OrderMetaModel::add_meta(
592 $order_id,
593 OrderModel::META_KEY_BILLING_ADDRESS,
594 $meta_value
595 );
596 }
597 }
598