LLMS_Controller_Orders

LLMS_Controller_Orders class.


Source Source

File: includes/controllers/class.llms.controller.orders.php

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
class LLMS_Controller_Orders {
 
    /**
     * Constructor.
     *
     * @since 3.0.0
     * @since 3.19.0 Updated.
     * @since 3.33.0 Added `before_delete_post` action to handle order deletion.
     * @since 4.2.0 Added `llms_user_enrollment_deleted` action to handle order status change on enrollment deletion.
     * @since 5.4.0 Perform `error_order()` when Detect a product deletion while processing a recurring charge.
     * @since 7.0.0 Added callback for `wp_untrash_post_status` filter.
     *              Remove action callbacks for order confirm, create, and payment source switch in favor of hooks in `LLMS_Controller_Checkout`.
     *
     * @return void
     */
    public function __construct() {
 
        add_filter( 'wp_untrash_post_status', array( $this, 'set_untrash_status' ), 10, 3 );
 
        // This action adds our lifterlms specific actions when order & transaction statuses change.
        add_action( 'transition_post_status', array( $this, 'transition_status' ), 10, 3 );
 
        // This action adds lifterlms specific action when an order is deleted, just before the WP post postmetas are removed.
        add_action( 'before_delete_post', array( $this, 'on_delete_order' ) );
 
        // This action is meant to do specific actions on orders when an enrollment, with an order as trigger, is deleted.
        add_action( 'llms_user_enrollment_deleted', array( $this, 'on_user_enrollment_deleted' ), 10, 3 );
 
        // Transaction status changes cascade up to the order to change the order status.
        add_action( 'lifterlms_transaction_status_failed', array( $this, 'transaction_failed' ), 10, 1 );
        add_action( 'lifterlms_transaction_status_refunded', array( $this, 'transaction_refunded' ), 10, 1 );
        add_action( 'lifterlms_transaction_status_succeeded', array( $this, 'transaction_succeeded' ), 10, 1 );
 
        // Status changes for orders to enroll students and trigger completion actions.
        add_action( 'lifterlms_order_status_completed', array( $this, 'complete_order' ), 10, 2 );
        add_action( 'lifterlms_order_status_active', array( $this, 'complete_order' ), 10, 2 );
 
        // Status changes to pending cancel.
        add_action( 'lifterlms_order_status_pending-cancel', array( $this, 'pending_cancel_order' ), 10, 1 );
 
        // Status changes for orders to unenroll students upon purchase.
        add_action( 'lifterlms_order_status_refunded', array( $this, 'error_order' ), 10, 1 );
        add_action( 'lifterlms_order_status_cancelled', array( $this, 'error_order' ), 10, 1 );
        add_action( 'lifterlms_order_status_expired', array( $this, 'error_order' ), 10, 1 );
        add_action( 'lifterlms_order_status_failed', array( $this, 'error_order' ), 10, 1 );
        add_action( 'lifterlms_order_status_on-hold', array( $this, 'error_order' ), 10, 1 );
        add_action( 'lifterlms_order_status_trash', array( $this, 'error_order' ), 10, 1 );
 
        // Detect a product deletion while processing a recurring charge.
        add_action( 'llms_order_recurring_charge_aborted_product_deleted', array( $this, 'error_order' ), 10, 1 );
 
        /**
         * Scheduler Actions
         */
 
        // Charge recurring payments.
        add_action( 'llms_charge_recurring_payment', array( $this, 'recurring_charge' ), 10, 1 );
 
        // Expire access plans.
        add_action( 'llms_access_plan_expiration', array( $this, 'expire_access' ), 10, 1 );
 
    }
 
    /**
     * Perform actions on a successful order completion.
     *
     * @since 1.0.0
     * @since 3.19.0 Unknown.
     *
     * @param LLMS_Order $order      Instance of an LLMS_Order.
     * @param string     $old_status Previous order status (eg: 'pending').
     * @return void
     */
    public function complete_order( $order, $old_status ) {
 
        // Clear expiration date when moving from a pending-cancel order.
        if ( 'pending-cancel' === $old_status ) {
            $order->set( 'date_access_expires', '' );
        }
 
        // Record access start time & maybe schedule expiration.
        $order->start_access();
 
        $order_id   = $order->get( 'id' );
        $product_id = $order->get( 'product_id' );
        $user_id    = $order->get( 'user_id' );
 
        unset( llms()->session->llms_coupon );
 
        /**
         * Action fired on order complete.
         *
         * Prior to the students being enrolled.
         *
         * @since 1.0.0
         *
         * @param integer $order_id The WP_Post ID of the order.
         */
        do_action( 'lifterlms_order_complete', $order_id ); // @todo used by AffiliateWP only, can remove after updating AffiliateWP.
 
        // Enroll student.
        llms_enroll_student( $user_id, $product_id, 'order_' . $order_id );
 
        // Trigger purchase action, used by engagements.
 
        /**
         * Action fired on product purchased.
         *
         * After the student has been enrolled.
         *
         * @since Unknown
         *
         * @param integer $user_id    The WP_User ID of the buyer.
         * @param integer $product_id The WP_Post ID of the purchased product (course/membership).
         */
        do_action( 'lifterlms_product_purchased', $user_id, $product_id );
 
        /**
         * Action fired on access plan purchased.
         *
         * After the student has been enrolled.
         *
         * @since Unknown
         *
         * @param integer $user_id    The WP_User ID of the buyer.
         * @param integer $product_id The WP_Post ID of the purchased access plan.
         */
        do_action( 'lifterlms_access_plan_purchased', $user_id, $order->get( 'plan_id' ) );
 
        // Maybe schedule a payment.
        $order->maybe_schedule_payment();
 
    }
 
    /**
     * Called when an order's status changes to refunded, cancelled, expired, or failed.
     *
     * Also called on product deletion detected while processing a recurring charge.
 
     * @since 3.0.0
     * @since 3.10.0 Unknown.
     * @since 4.2.0 Added `llms_unenroll_on_error_order` filter hook.
     * @since 5.4.0 Unenroll with 'cancelled' status on 'llms_order_recurring_charge_aborted_product_deleted'.
     *              The `$order` param can be also a WP_Post or its `ID`.
     *
     * @param int|WP_Post|LLMS_Order $order Instance of an LLMS_Order, WP_Post or WP_Post ID of the order.
     * @return void
     */
    public function error_order( $order ) {
 
        $order = is_a( $order, 'LLMS_Order' ) ? $order : llms_get_post( $order );
        if ( ! ( $order && is_a( $order, 'LLMS_Order' ) ) ) {
            return;
        }
 
        $order->unschedule_recurring_payment();
 
        /**
         * Determine if student should be unenrolled on order error.
         *
         * @since 4.2.0
         *
         * @param bool       $unenroll_on_error_order True if the student should be unenrolled, false otherwise. Default true.
         * @param LLMS_Order $order                   Order object.
         */
        if ( ! apply_filters( 'llms_unenroll_on_error_order', true, $order ) ) {
            return;
        }
 
        switch ( current_filter() ) {
 
            case 'lifterlms_order_status_trash':
            case 'lifterlms_order_status_cancelled':
            case 'lifterlms_order_status_on-hold':
            case 'lifterlms_order_status_refunded':
            case 'llms_order_recurring_charge_aborted_product_deleted':
                $status = 'cancelled';
                break;
 
            case 'lifterlms_order_status_expired':
            case 'lifterlms_order_status_failed':
            default:
                $status = 'expired';
                break;
 
        }
 
        llms_unenroll_student( $order->get( 'user_id' ), $order->get( 'product_id' ), $status, 'order_' . $order->get( 'id' ) );
 
    }
 
    /**
     * Called when a post is permanently deleted.
     *
     * Will delete any enrollment records linked to the LLMS_Order with the ID of the deleted post.
     *
     * @since 3.33.0
     *
     * @param int $post_id WP_Post ID.
     * @return void
     */
    public function on_delete_order( $post_id ) {
 
        $order = llms_get_post( $post_id );
        if ( $order && is_a( $order, 'LLMS_Order' ) ) {
            llms_delete_student_enrollment( $order->get( 'user_id' ), $order->get( 'product_id' ), 'order_' . $order->get( 'id' ) );
        }
 
    }
 
    /**
     * Called when an user enrollment is deleted.
     *
     * Will set the related order status to 'cancelled'.
     *
     * @since 4.2.0
     *
     * @param int    $user_id    WP User ID.
     * @param int    $product_id WP Post ID of the course or membership.
     * @param string $trigger    The deleted enrollment trigger, or 'any' if no specific trigger.
     * @return void
     */
    public function on_user_enrollment_deleted( $user_id, $product_id, $trigger ) {
 
        $order_id = 'order_' === substr( $trigger, 0, 6 ) ? absint( substr( $trigger, 6 ) ) : false;
        $order    = $order_id ? llms_get_post( $order_id ) : false;
 
        if ( $order && is_a( $order, 'LLMS_Order' ) ) {
 
            // No need to run an unenrollment as we're reacting to an enrollment deletion, user enrollments data already removed.
            add_filter( 'llms_unenroll_on_error_order', '__return_false', 100 );
            $order->set_status( 'cancelled' );
            // Reset unenrollment's suspension..
            remove_filter( 'llms_unenroll_on_error_order', '__return_false', 100 );
 
        }
 
    }
 
    /**
     * Handle expiration & cancellation from a course / membership.
     *
     * Called via scheduled action set during order completion for plans with a limited access plan.
     * Additionally called when an order is marked as "pending-cancel" to revoke access at the end of a pre-paid period.
     *
     * @since 3.0.0
     * @since 3.19.0 Unknown.
     * @since 7.5.0 Potentially allow recurring payment to go ahead even if access plans expired.
     *
     * @param int $order_id WP_Post ID of the LLMS Order.
     * @return void
     */
    public function expire_access( $order_id ) {
 
        $order            = new LLMS_Order( $order_id );
        $new_order_status = false;
 
        // Pending cancel order moves to cancelled.
        if ( 'llms-pending-cancel' === $order->get( 'status' ) ) {
 
            $status           = 'cancelled'; // Enrollment status.
            $note             = __( 'Student unenrolled at the end of access period due to subscription cancellation.', 'lifterlms' );
            $new_order_status = 'cancelled';
 
            // All others move to expired.
        } else {
 
            $status = 'expired'; // Enrollment status.
            $note   = __( 'Student unenrolled due to automatic access plan expiration', 'lifterlms' );
 
        }
 
        /**
         * Filters whether or not recurring payments should be stopped on access plan expiration.
         *
         * By default when an access plan expires, recurring payments are stopped.
         *
         * @since 7.5.0
         *
         * @param bool
         * @param LLMS_Order $order             Instance of the order.
         * @param mixed      $new_order_status  New order status. If `false` it means that the new order status is not
         *                                      going to change. At this stage the orders status is not changed yet.
         * @param string     $enrollment_status The new enrollment status. At this stage the enrollment status is not changed yet.
         */
        $unschedule_recurring_payment = apply_filters(
            'llms_unschedule_recurring_payment_on_access_plan_expiration',
            true,
            $order,
            $new_order_status,
            $status
        );
 
        llms_unenroll_student( $order->get( 'user_id' ), $order->get( 'product_id' ), $status, 'order_' . $order->get( 'id' ) );
        $order->add_note( $note );
 
        if ( $unschedule_recurring_payment ) {
            $order->unschedule_recurring_payment();
        }
 
        if ( $new_order_status ) {
            $order->set_status( $new_order_status );
        }
 
    }
 
    /**
     * Unschedule recurring payments and schedule access expiration.
     *
     * @since 3.19.0
     *
     * @param LLMS_Order $order LLMS_Order object.
     * @return void
     */
    public function pending_cancel_order( $order ) {
 
        $date = $order->get_next_payment_due_date( 'Y-m-d H:i:s' );
        $order->set( 'date_access_expires', $date );
 
        $order->unschedule_recurring_payment();
        $order->maybe_schedule_expiration();
 
    }
 
    /**
     * Trigger a recurring payment.
     *
     * Called by action scheduler.
     *
     * @since 3.0.0
     * @since 3.32.0 Record order notes and trigger actions during errors.
     * @since 3.36.1 Made sure to process only proper LLMS_Orders of existing users.
     * @since 5.2.0 Fixed buggy logging on gateway error because it doesn't support recurring payments.
     * @since 5.4.0 Handle case when the order's related product has been removed.
     *
     * @param int $order_id WP Post ID of the order.
     * @return bool `false` if the recurring charge cannot be processed, `true` when the charge is successfully handed off to the gateway.
     */
    public function recurring_charge( $order_id ) {
 
        // Make sure the order still exists.
        $order = llms_get_post( $order_id );
        if ( ! $order || ! is_a( $order, 'LLMS_Order' ) ) {
 
            /**
             * Fired when a LifterLMS order's recurring charge errors because the order doesn't exist anymore
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_order_error', $order_id, $this );
            llms_log( sprintf( 'Recurring charge for Order #%d could not be processed because the order no longer exists.', $order_id ), 'recurring-payments' );
            return false;
 
        }
 
        // Check the user still exists.
        $user_id = $order->get( 'user_id' );
        if ( ! get_user_by( 'id', $user_id ) ) {
 
            /**
             * Fired when a LifterLMS order's recurring charge errors because the user who placed the order doesn't exist anymore
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param int                    $user_id    WP User ID of the user who placed the order.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_user_error', $order_id, $user_id, $this );
            llms_log( sprintf( 'Recurring charge for Order #%1$d could not be processed because the user (#%2$d) no longer exists.', $order_id, $user_id ), 'recurring-payments' );
 
            // Translators: %d = The deleted user's ID.
            $order->add_note( sprintf( __( 'Recurring charge skipped. The user (#%d) no longer exists.', 'lifterlms' ), $user_id ) );
            return false;
 
        }
 
        // Ensure Gateway is still available.
        $gateway = $order->get_gateway();
 
        if ( is_wp_error( $gateway ) ) {
            /**
             * Fired when a LifterLMS order's recurring charge errors because of a gateway error. E.g. it's not available anymore.
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param WP_Error               $error      WP_Error instance.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_gateway_error', $order_id, $gateway, $this );
 
            llms_log(
                sprintf(
                    'Recurring charge for Order #%1$d could not be processed because the "%2$s" gateway is no longer available. Gateway Error: %3$s',
                    $order_id,
                    $order->get( 'payment_gateway' ),
                    $gateway->get_error_message()
                ),
                'recurring-payments'
            );
 
            $order->add_note(
                sprintf(
                    // Translators: %s = error message encountered while loading the gateway.
                    __( 'Recurring charge was not processed due to an error encountered while loading the payment gateway: %s.', 'lifterlms' ),
                    $gateway->get_error_message()
                )
            );
            return false;
 
        }
 
        // Gateway doesn't support recurring payments.
        if ( ! $gateway->supports( 'recurring_payments' ) ) {
 
            /**
             * Fired when a LifterLMS order's recurring charge errors because the selected gateway doesn't support recurring payments.
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param LLMS_Payment_Gateway   $gateway    LLMS_Payment_Gateway extending class instance.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_gateway_payments_disabled', $order_id, $gateway, $this );
            llms_log(
                sprintf(
                    'Recurring charge for order #%d could not be processed because the gateway no longer supports recurring payments.',
                    $order_id
                ),
                'recurring-payments'
            );
 
            $order->add_note( __( 'Recurring charge skipped because recurring payments are disabled for the payment gateway.', 'lifterlms' ) );
            return false;
 
        }
 
        // Recurring payments disabled as a site feature when in staging mode.
        if ( ! LLMS_Site::get_feature( 'recurring_payments' ) ) {
 
            /**
             * Fired when a LifterLMS order's recurring charge errors because the recurring payments site feature is disabled.
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param LLMS_Payment_Gateway   $gateway    LLMS_Payment_Gateway extending class instance.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_skipped', $order_id, $gateway, $this );
            $order->add_note( __( 'Recurring charge skipped because recurring payments are disabled in staging mode.', 'lifterlms' ) );
            return false;
 
        }
 
        // Related product removed.
        if ( empty( $order->get_product() ) ) {
 
            /**
             * Fired when a LifterLMS order's recurring charge errors because the purchased product (Course/Membership) doesn't exist anymore.
             *
             * @since Unknown
             *
             * @param int                    $order_id   WP Post ID of the order.
             * @param LLMS_Controller_Orders $controller This controller's instance.
             */
            do_action( 'llms_order_recurring_charge_aborted_product_deleted', $order_id, $this );
            llms_log(
                sprintf(
                    'Recurring charge for order #%d could not be processed because the product #%d does not exist anymore.',
                    $order_id,
                    $order->get( 'product_id' )
                ),
                'recurring-payments'
            );
 
            $order->add_note( __( 'Recurring charge aborted because the purchased product does not exist anymore.', 'lifterlms' ) );
            return false;
 
        }
 
        // Passed validation, hand off to the gateway.
        $gateway->handle_recurring_transaction( $order );
        return true;
 
    }
 
    /**
     * Sets an order's post status to `llms-pending` when untrashing an order.
     *
     * This is a filter hook callback for the WP core filter `wp_untrash_post_status`.
     *
     * @since 7.0.0
     *
     * @param string $new_status      The new status of the post after untrashing.
     * @param int    $post_id         The WP_Post ID of the order.
     * @param string $previous_status The status of the post at the point where it was trashed.
     * @return string
     */
    public function set_untrash_status( $new_status, $post_id, $previous_status ) {
 
        if ( 'llms_order' === get_post_type( $post_id ) ) {
            /**
             * Filters the status that an order post gets assigned when it is restored from the trash.
             *
             * This is a filter nearly identical to `wp_untrash_post_status` applied specifically to `llms_order` posts.
             *
             * @since 7.0.0
             *
             *
             * @param string $new_status      The new status of the post being restored.
             * @param int    $post_id         The ID of the post being restored.
             * @param string $previous_status The status of the post at the point where it was trashed.
             */
            $new_status = apply_filters( 'llms_untrash_order_status', 'llms-pending', $post_id, $previous_status );
        }
 
        return $new_status;
 
    }
 
    /**
     * When a transaction fails, update the parent order's status.
     *
     * @since 3.0.0
     * @since 3.10.0 Unknown.
     *
     * @param LLMS_Transaction $txn Instance of the LLMS_Transaction.
     * @return void
     */
    public function transaction_failed( $txn ) {
 
        $order = $txn->get_order();
 
        // Halt if legacy.
        if ( $order->is_legacy() ) {
            return;
        }
 
        if ( $order->can_be_retried() ) {
 
            $order->maybe_schedule_retry();
 
        } else {
 
            $order->set( 'status', 'llms-failed' );
 
        }
 
    }
 
    /**
     * When a transaction is refunded, update the parent order's status.
     *
     * @since 3.0.0
     *
     * @param LLMS_Transaction $txn Instance of the LLMS_Transaction.
     * @return void
     */
    public function transaction_refunded( $txn ) {
 
        $order = $txn->get_order();
 
        // Halt if legacy.
        if ( $order->is_legacy() ) {
            return; }
 
        $order->set( 'status', 'llms-refunded' );
 
    }
 
    /**
     * When a transaction succeeds, update the parent order's status.
     *
     * @since 3.0.0
     * @since 3.10.0 Unknown.
     *
     * @param LLMS_Transaction $txn Instance of the LLMS_Transaction.
     * @return void
     */
    public function transaction_succeeded( $txn ) {
 
        // Get the order.
        $order = $txn->get_order();
 
        // Halt if legacy.
        if ( $order->is_legacy() ) {
            return;
        }
 
        // Update the status based on the order type.
        $status = $order->is_recurring() ? 'llms-active' : 'llms-completed';
        $order->set( 'status', $status );
        $order->set( 'last_retry_rule', '' ); // Retries should always start with tne first rule for new transactions.
 
        // Maybe schedule a payment.
        $order->maybe_schedule_payment();
 
    }
 
    /**
     * Trigger actions when the status of LifterLMS Orders and LifterLMS Transactions change status.
     *
     * @since 3.0.0
     * @since 3.19.0 Unknown.
     *
     * @param string  $new_status New status.
     * @param string  $old_status Old status.
     * @param WP_Post $post       WP_Post instance of the transaction.
     * @return void
     */
    public function transition_status( $new_status, $old_status, $post ) {
 
        // Don't do anything if the status hasn't changed.
        if ( $new_status === $old_status ) {
            return;
        }
 
        // We're only concerned with order post statuses here.
        if ( 'llms_order' !== $post->post_type && 'llms_transaction' !== $post->post_type ) {
            return;
        }
 
        $post_type = str_replace( 'llms_', '', $post->post_type );
        $obj       = 'order' === $post_type ? new LLMS_Order( $post ) : new LLMS_Transaction( $post );
 
        // Record order status changes as notes.
        if ( 'order' === $post_type ) {
            $obj->add_note( sprintf( __( 'Order status changed from %1$s to %2$s', 'lifterlms' ), llms_get_order_status_name( $old_status ), llms_get_order_status_name( $new_status ) ) );
        }
 
        // Remove prefixes from all the things.
        $new_status = str_replace( array( 'llms-', 'txn-' ), '', $new_status );
        $old_status = str_replace( array( 'llms-', 'txn-' ), '', $old_status );
 
        /**
         * Fired when a LifterLMS order or transaction changes status.
         *
         * The first dynamic portion of this hook, `$post_type`, refers to the unprefixed object post type ('order|transaction').
         * The second dynamic portion of this hook, `$old_status`, refers to the previous object status.
         * The third dynamic portion of this hook, `$new_status`, refers to the new object status.
         *
         * @since Unknown
         *
         * @param LLMS_Order|LLMS_Transaction $object     The LifterLMS order or transaction instance.
         * @param string                      $old_status The previous order or transaction status.
         * @param string                      $new_status The new order or transaction status.
         */
        do_action( "lifterlms_{$post_type}_status_{$old_status}_to_{$new_status}", $obj, $old_status, $new_status );
 
        /**
         * Fired when a LifterLMS order or transaction changes status.
         *
         * The first dynamic portion of this hook, `$post_type`, refers to the unprefixed object post type ('order|transaction').
         * The second dynamic portion of this hook, `$new_status`, refers to the new object status.
         *
         * @since Unknown
         *
         * @param LLMS_Order|LLMS_Transaction $object     The LifterLMS order or transaction instance.
         * @param string                      $old_status The previous order or transaction status.
         * @param string                      $new_status The new order or transaction status.
         */
        do_action( "lifterlms_{$post_type}_status_{$new_status}", $obj, $old_status, $new_status );
 
    }
 
    /**
     * Validate a gateway can be used to process the current action / transaction.
     *
     * @since 3.10.0
     *
     * @param string           $gateway_id Gateway's id.
     * @param LLMS_Access_Plan $plan       Instance of the LLMS_Access_Plan related to the action/transaction.
     * @return WP_Error|LLMS_Payment_Gateway WP_Error or LLMS_Payment_Gateway subclass.
     */
    private function validate_selected_gateway( $gateway_id, $plan ) {
 
        $gateway = llms()->payment_gateways()->get_gateway_by_id( $gateway_id );
        $err     = new WP_Error();
 
        // Valid gateway.
        if ( is_subclass_of( $gateway, 'LLMS_Payment_Gateway' ) ) {
 
            // Gateway not enabled.
            if ( 'manual' !== $gateway->get_id() && ! $gateway->is_enabled() ) {
 
                return $err->add( 'gateway-error', __( 'The selected payment gateway is not currently enabled.', 'lifterlms' ) );
 
                // It's a recurring plan and the gateway doesn't support recurring.
            } elseif ( $plan->is_recurring() && ! $gateway->supports( 'recurring_payments' ) ) {
                // Translators: %s = The gateway display name.
                return $err->add( 'gateway-error', sprintf( __( '%s does not support recurring payments and cannot process this transaction.', 'lifterlms' ), $gateway->get_title() ) );
 
                // Not recurring and the gateway doesn't support single payments.
            } elseif ( ! $plan->is_recurring() && ! $gateway->supports( 'single_payments' ) ) {
                // Translators: %s = The gateway display name.
                return $err->add( 'gateway-error', sprintf( __( '%s does not support single payments and cannot process this transaction.', 'lifterlms' ), $gateway->get_title() ) );
 
            }
        } else {
 
            return $err->add( 'invalid-gateway', __( 'An invalid payment method was selected.', 'lifterlms' ) );
 
        }
 
        return $gateway;
 
    }
 
    /**
     * Confirm order form post.
     *
     * User clicks confirm order or gateway determines the order is confirmed.
     *
     * Executes payment gateway confirm order method and completes order.
     * Redirects user to appropriate page / post
     *
     * @since 3.0.0
     * @since 3.4.0 Unknown.
     * @since 3.34.4 Added filter `llms_order_can_be_confirmed`.
     * @since 3.34.5 Fixed logic error in `llms_order_can_be_confirmed` conditional.
     * @since 3.35.0 Return early if nonce doesn't pass verification and sanitize `$_POST` data.
     * @since 5.9.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
     * @deprecated 7.0.0 Deprecated in favor of {@see LLMS_Controller_Checkout::confirm_pending_order()}.
     *
     * @return void
     */
    public function confirm_pending_order() {
        _deprecated_function( __METHOD__, '7.0.0', 'LLMS_Controller_Checkout::confirm_pending_order' );
        LLMS_Controller_Checkout::instance()->confirm_pending_order();
    }
 
    /**
     * Handle form submission of the checkout / payment form.
     *
     *      1. Logs in or Registers a user
     *      2. Validates all fields
     *      3. Handles coupon pricing adjustments
     *      4. Creates a PENDING llms_order
     *
     *      If errors, returns error on screen to user
     *      If success, passes to the selected gateways "process_payment" method
     *          the process_payment method should complete by returning an error or
     *          triggering the "lifterlms_process_payment_redirect" // Todo check this last statement.
     *
     * @since 3.0.0
     * @since 3.27.0 Unknown.
     * @since 3.35.0 Sanitize `$_POST` data.
     * @since 5.0.0 Build customer data using LLMS_Forms fields information.
     * @since 5.0.1 Delegate sanitization of user information fields of the `$_POST` to LLMS_Form_Handler::submit().
     * @since 5.9.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
     * @deprecated 7.0.0 Deprecated in favor of {@see LLMS_Controller_Checkout::create_pending_order()}.
     *

Top ↑

Methods Methods


Top ↑

Changelog Changelog

Changelog
Version Description
5.0.0 Build customer data using LLMS_Forms fields information.
4.2.0 Added logic to set the order status to 'cancelled' when an enrollment linked to an order is deleted. Also llms_unenroll_on_error_order fiter hook added.
3.36.1 In recurring_charge(), made sure to process only proper LLMS_Orders of existing users.
3.34.5 Fixed logic error in llms_order_can_be_confirmed conditional.
3.34.4 Added filter llms_order_can_be_confirmed.
3.33.0 Added logic to delete any enrollment records linked to an LLMS_Order on its permanent deletion.
3.0.0 Introduced.

Top ↑

User Contributed Notes User Contributed Notes

You must log in before being able to contribute a note or feedback.