Completed
Branch master (59af22)
by
unknown
07:35 queued 03:06
created
reg_steps/payment_options/EE_SPCO_Reg_Step_Payment_Options.class.php 1 patch
Indentation   +2931 added lines, -2931 removed lines patch added patch discarded remove patch
@@ -23,2935 +23,2935 @@
 block discarded – undo
23 23
  */
24 24
 class EE_SPCO_Reg_Step_Payment_Options extends EE_SPCO_Reg_Step
25 25
 {
26
-    /**
27
-     * @var EE_Line_Item_Display $Line_Item_Display
28
-     */
29
-    protected $line_item_display;
30
-
31
-    /**
32
-     * @var boolean $handle_IPN_in_this_request
33
-     */
34
-    protected $handle_IPN_in_this_request = false;
35
-
36
-
37
-    /**
38
-     *    set_hooks - for hooking into EE Core, other modules, etc
39
-     *
40
-     * @access    public
41
-     * @return    void
42
-     */
43
-    public static function set_hooks()
44
-    {
45
-        add_filter(
46
-            'FHEE__SPCO__EE_Line_Item_Filter_Collection',
47
-            ['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
48
-        );
49
-        add_action(
50
-            'wp_ajax_switch_spco_billing_form',
51
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
52
-        );
53
-        add_action(
54
-            'wp_ajax_nopriv_switch_spco_billing_form',
55
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
56
-        );
57
-        add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
58
-        add_action(
59
-            'wp_ajax_nopriv_save_payer_details',
60
-            ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
61
-        );
62
-        add_action(
63
-            'wp_ajax_get_transaction_details_for_gateways',
64
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
65
-        );
66
-        add_action(
67
-            'wp_ajax_nopriv_get_transaction_details_for_gateways',
68
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
69
-        );
70
-        add_filter(
71
-            'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
72
-            ['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
73
-        );
74
-    }
75
-
76
-
77
-    /**
78
-     *    ajax switch_spco_billing_form
79
-     *
80
-     */
81
-    public static function switch_spco_billing_form()
82
-    {
83
-        EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
84
-    }
85
-
86
-
87
-    /**
88
-     *    ajax save_payer_details
89
-     *
90
-     */
91
-    public static function save_payer_details()
92
-    {
93
-        EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
94
-    }
95
-
96
-
97
-    /**
98
-     *    ajax get_transaction_details
99
-     *
100
-     */
101
-    public static function get_transaction_details()
102
-    {
103
-        EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
104
-    }
105
-
106
-
107
-    /**
108
-     * bypass_recaptcha_for_load_payment_method
109
-     *
110
-     * @access public
111
-     * @return array
112
-     * @throws InvalidArgumentException
113
-     * @throws InvalidDataTypeException
114
-     * @throws InvalidInterfaceException
115
-     */
116
-    public static function bypass_recaptcha_for_load_payment_method()
117
-    {
118
-        return [
119
-            'EESID'  => EE_Registry::instance()->SSN->id(),
120
-            'step'   => 'payment_options',
121
-            'action' => 'spco_billing_form',
122
-        ];
123
-    }
124
-
125
-
126
-    /**
127
-     *    class constructor
128
-     *
129
-     * @access    public
130
-     * @param EE_Checkout $checkout
131
-     */
132
-    public function __construct(EE_Checkout $checkout)
133
-    {
134
-        $this->request   = EED_Single_Page_Checkout::getRequest();
135
-        $this->_slug     = 'payment_options';
136
-        $this->_name     = esc_html__('Payment Options', 'event_espresso');
137
-        $this->_template = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
138
-        $this->checkout  = $checkout;
139
-        $this->_reset_success_message();
140
-        $this->set_instructions(
141
-            esc_html__(
142
-                'Please select a method of payment and provide any necessary billing information before proceeding.',
143
-                'event_espresso'
144
-            )
145
-        );
146
-    }
147
-
148
-
149
-    /**
150
-     * @return null
151
-     */
152
-    public function line_item_display()
153
-    {
154
-        return $this->line_item_display;
155
-    }
156
-
157
-
158
-    /**
159
-     * @param null $line_item_display
160
-     */
161
-    public function set_line_item_display($line_item_display)
162
-    {
163
-        $this->line_item_display = $line_item_display;
164
-    }
165
-
166
-
167
-    /**
168
-     * @return boolean
169
-     */
170
-    public function handle_IPN_in_this_request()
171
-    {
172
-        return $this->handle_IPN_in_this_request;
173
-    }
174
-
175
-
176
-    /**
177
-     * @param boolean $handle_IPN_in_this_request
178
-     */
179
-    public function set_handle_IPN_in_this_request($handle_IPN_in_this_request)
180
-    {
181
-        $this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
182
-    }
183
-
184
-
185
-    /**
186
-     * translate_js_strings
187
-     *
188
-     * @return void
189
-     */
190
-    public function translate_js_strings()
191
-    {
192
-        EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
193
-            'Please select a method of payment in order to continue.',
194
-            'event_espresso'
195
-        );
196
-        EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
197
-            'A valid method of payment could not be determined. Please refresh the page and try again.',
198
-            'event_espresso'
199
-        );
200
-        EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
201
-            'Forwarding to Secure Payment Provider.',
202
-            'event_espresso'
203
-        );
204
-    }
205
-
206
-
207
-    /**
208
-     * enqueue_styles_and_scripts
209
-     *
210
-     * @return void
211
-     * @throws EE_Error
212
-     * @throws InvalidArgumentException
213
-     * @throws InvalidDataTypeException
214
-     * @throws InvalidInterfaceException
215
-     * @throws ReflectionException
216
-     */
217
-    public function enqueue_styles_and_scripts()
218
-    {
219
-        $transaction = $this->checkout->transaction;
220
-        // if the transaction isn't set or nothing is owed on it, don't enqueue any JS
221
-        if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
222
-            return;
223
-        }
224
-        foreach (
225
-            EEM_Payment_Method::instance()->get_all_for_transaction(
226
-                $transaction,
227
-                EEM_Payment_Method::scope_cart
228
-            ) as $payment_method
229
-        ) {
230
-            $type_obj = $payment_method->type_obj();
231
-            if ($type_obj instanceof EE_PMT_Base) {
232
-                $billing_form = $type_obj->generate_new_billing_form($transaction);
233
-                if ($billing_form instanceof EE_Form_Section_Proper) {
234
-                    $billing_form->enqueue_js();
235
-                }
236
-            }
237
-        }
238
-    }
239
-
240
-
241
-    /**
242
-     * initialize_reg_step
243
-     *
244
-     * @return bool
245
-     * @throws EE_Error
246
-     * @throws InvalidArgumentException
247
-     * @throws ReflectionException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     */
251
-    public function initialize_reg_step()
252
-    {
253
-        // TODO: if /when we implement donations, then this will need overriding
254
-        if (
255
-            // don't need payment options for:
256
-            // registrations made via the admin
257
-            // completed transactions
258
-            // overpaid transactions
259
-            // $ 0.00 transactions(no payment required)
260
-            ! $this->checkout->payment_required()
261
-            // but do NOT remove if current action being called belongs to this reg step
262
-            && ! is_callable([$this, $this->checkout->action])
263
-            && ! $this->completed()
264
-        ) {
265
-            // and if so, then we no longer need the Payment Options step
266
-            if ($this->is_current_step()) {
267
-                $this->checkout->generate_reg_form = false;
268
-            }
269
-            $this->checkout->remove_reg_step($this->_slug);
270
-            // DEBUG LOG
271
-            // $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
272
-            return false;
273
-        }
274
-        // load EEM_Payment_Method
275
-        EE_Registry::instance()->load_model('Payment_Method');
276
-        // get all active payment methods
277
-        $this->checkout->available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
278
-            $this->checkout->transaction,
279
-            EEM_Payment_Method::scope_cart
280
-        );
281
-        return true;
282
-    }
283
-
284
-
285
-    /**
286
-     * @return EE_Form_Section_Proper
287
-     * @throws EE_Error
288
-     * @throws InvalidArgumentException
289
-     * @throws ReflectionException
290
-     * @throws EntityNotFoundException
291
-     * @throws InvalidDataTypeException
292
-     * @throws InvalidInterfaceException
293
-     * @throws InvalidStatusException
294
-     */
295
-    public function generate_reg_form()
296
-    {
297
-        // reset in case someone changes their mind
298
-        $this->_reset_selected_method_of_payment();
299
-        // set some defaults
300
-        $this->checkout->selected_method_of_payment = 'payments_closed';
301
-        $registrations_requiring_payment            = [];
302
-        $registrations_for_free_events              = [];
303
-        $registrations_requiring_pre_approval       = [];
304
-        $sold_out_events                            = [];
305
-        $insufficient_spaces_available              = [];
306
-        $no_payment_required                        = true;
307
-        // loop thru registrations to gather info
308
-        $registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
309
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
310
-            $registrations,
311
-            $this->checkout->revisit
312
-        );
313
-        foreach ($registrations as $REG_ID => $registration) {
314
-            /** @var $registration EE_Registration */
315
-            // Skip if the registration has been moved
316
-            if ($registration->wasMoved()) {
317
-                continue;
318
-            }
319
-            // has this registration lost it's space ?
320
-            if (isset($ejected_registrations[ $REG_ID ])) {
321
-                if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
322
-                    $sold_out_events[ $registration->event()->ID() ] = $registration->event();
323
-                } else {
324
-                    $insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
325
-                }
326
-                continue;
327
-            }
328
-            // event requires admin approval
329
-            if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
330
-                // add event to list of events with pre-approval reg status
331
-                $registrations_requiring_pre_approval[ $REG_ID ] = $registration;
332
-                do_action(
333
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
334
-                    $registration->event(),
335
-                    $this
336
-                );
337
-                continue;
338
-            }
339
-            if (
340
-                $this->checkout->revisit
341
-                && $registration->status_ID() !== RegStatus::APPROVED
342
-                && (
343
-                    $registration->event()->is_sold_out()
344
-                    || $registration->event()->is_sold_out(true)
345
-                )
346
-            ) {
347
-                // add event to list of events that are sold out
348
-                $sold_out_events[ $registration->event()->ID() ] = $registration->event();
349
-                do_action(
350
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
351
-                    $registration->event(),
352
-                    $this
353
-                );
354
-                continue;
355
-            }
356
-            // are they allowed to pay now and is there monies owing?
357
-            if ($registration->owes_monies_and_can_pay()) {
358
-                $registrations_requiring_payment[ $REG_ID ] = $registration;
359
-                do_action(
360
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
361
-                    $registration->event(),
362
-                    $this
363
-                );
364
-            } elseif (
365
-                ! $this->checkout->revisit
366
-                      && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
367
-                      && $registration->ticket()->is_free()
368
-            ) {
369
-                $registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
370
-            }
371
-        }
372
-        $subsections = [];
373
-        // now decide which template to load
374
-        if (! empty($sold_out_events)) {
375
-            $subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
376
-        }
377
-        if (! empty($insufficient_spaces_available)) {
378
-            $subsections['insufficient_space'] = $this->_insufficient_spaces_available(
379
-                $insufficient_spaces_available
380
-            );
381
-        }
382
-        if (! empty($registrations_requiring_pre_approval)) {
383
-            $subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
384
-                $registrations_requiring_pre_approval
385
-            );
386
-        }
387
-        if (! empty($registrations_for_free_events)) {
388
-            $subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
389
-        }
390
-        if (! empty($registrations_requiring_payment)) {
391
-            if ($this->checkout->amount_owing > 0) {
392
-                // autoload Line_Item_Display classes
393
-                EEH_Autoloader::register_line_item_filter_autoloaders();
394
-                $line_item_filter_processor = new EE_Line_Item_Filter_Processor(
395
-                    apply_filters(
396
-                        'FHEE__SPCO__EE_Line_Item_Filter_Collection',
397
-                        new EE_Line_Item_Filter_Collection()
398
-                    ),
399
-                    $this->checkout->cart->get_grand_total()
400
-                );
401
-                /** @var EE_Line_Item $filtered_line_item_tree */
402
-                $filtered_line_item_tree = $line_item_filter_processor->process();
403
-                EEH_Autoloader::register_line_item_display_autoloaders();
404
-                $this->set_line_item_display(new EE_Line_Item_Display('spco'));
405
-                $subsections['payment_options'] = $this->_display_payment_options(
406
-                    $this->line_item_display->display_line_item(
407
-                        $filtered_line_item_tree,
408
-                        ['registrations' => $registrations]
409
-                    )
410
-                );
411
-                $this->checkout->amount_owing   = $filtered_line_item_tree->total();
412
-                $this->_apply_registration_payments_to_amount_owing($registrations);
413
-            }
414
-            $no_payment_required = false;
415
-        } else {
416
-            $this->_hide_reg_step_submit_button_if_revisit();
417
-        }
418
-        $this->_save_selected_method_of_payment();
419
-
420
-        $subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
421
-        $subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
422
-
423
-        return new EE_Form_Section_Proper(
424
-            [
425
-                'name'            => $this->reg_form_name(),
426
-                'html_id'         => $this->reg_form_name(),
427
-                'subsections'     => $subsections,
428
-                'layout_strategy' => new EE_No_Layout(),
429
-            ]
430
-        );
431
-    }
432
-
433
-
434
-    /**
435
-     * add line item filters required for this reg step
436
-     * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
437
-     *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
438
-     *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
439
-     *        payment options reg step, can apply these filters via the following: apply_filters(
440
-     *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
441
-     *        filter collection by passing that instead of instantiating a new collection
442
-     *
443
-     * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
444
-     * @return EE_Line_Item_Filter_Collection
445
-     * @throws EE_Error
446
-     * @throws InvalidArgumentException
447
-     * @throws ReflectionException
448
-     * @throws EntityNotFoundException
449
-     * @throws InvalidDataTypeException
450
-     * @throws InvalidInterfaceException
451
-     * @throws InvalidStatusException
452
-     */
453
-    public static function add_spco_line_item_filters(EE_Line_Item_Filter_Collection $line_item_filter_collection)
454
-    {
455
-        if (! EE_Registry::instance()->SSN instanceof EE_Session) {
456
-            return $line_item_filter_collection;
457
-        }
458
-        if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
459
-            return $line_item_filter_collection;
460
-        }
461
-        if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
462
-            return $line_item_filter_collection;
463
-        }
464
-        $line_item_filter_collection->add(
465
-            new EE_Billable_Line_Item_Filter(
466
-                EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
467
-                    EE_Registry::instance()->SSN->checkout()->transaction->registrations(
468
-                        EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
469
-                    )
470
-                )
471
-            )
472
-        );
473
-        $line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
474
-        return $line_item_filter_collection;
475
-    }
476
-
477
-
478
-    /**
479
-     * remove_ejected_registrations
480
-     * if a registrant has lost their potential space at an event due to lack of payment,
481
-     * then this method removes them from the list of registrations being paid for during this request
482
-     *
483
-     * @param EE_Registration[] $registrations
484
-     * @return EE_Registration[]
485
-     * @throws EE_Error
486
-     * @throws InvalidArgumentException
487
-     * @throws ReflectionException
488
-     * @throws EntityNotFoundException
489
-     * @throws InvalidDataTypeException
490
-     * @throws InvalidInterfaceException
491
-     * @throws InvalidStatusException
492
-     */
493
-    public static function remove_ejected_registrations(array $registrations)
494
-    {
495
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
496
-            $registrations,
497
-            EE_Registry::instance()->SSN->checkout()->revisit
498
-        );
499
-        foreach ($registrations as $REG_ID => $registration) {
500
-            // has this registration lost it's space ?
501
-            if (isset($ejected_registrations[ $REG_ID ])) {
502
-                unset($registrations[ $REG_ID ]);
503
-            }
504
-        }
505
-        return $registrations;
506
-    }
507
-
508
-
509
-    /**
510
-     * find_registrations_that_lost_their_space
511
-     * If a registrant chooses an offline payment method like Invoice,
512
-     * then no space is reserved for them at the event until they fully pay fo that site
513
-     * (unless the event's default reg status is set to APPROVED)
514
-     * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
515
-     * then this method will determine which registrations have lost the ability to complete the reg process.
516
-     *
517
-     * @param EE_Registration[] $registrations
518
-     * @param bool              $revisit
519
-     * @return array
520
-     * @throws EE_Error
521
-     * @throws InvalidArgumentException
522
-     * @throws ReflectionException
523
-     * @throws EntityNotFoundException
524
-     * @throws InvalidDataTypeException
525
-     * @throws InvalidInterfaceException
526
-     * @throws InvalidStatusException
527
-     */
528
-    public static function find_registrations_that_lost_their_space(array $registrations, $revisit = false)
529
-    {
530
-        // registrations per event
531
-        $event_reg_count = [];
532
-        // spaces left per event
533
-        $event_spaces_remaining = [];
534
-        // tickets left sorted by ID
535
-        $tickets_remaining = [];
536
-        // registrations that have lost their space
537
-        $ejected_registrations = [];
538
-        foreach ($registrations as $REG_ID => $registration) {
539
-            if (
540
-                $registration->status_ID() === RegStatus::APPROVED
541
-                || apply_filters(
542
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
543
-                    false,
544
-                    $registration,
545
-                    $revisit
546
-                )
547
-            ) {
548
-                continue;
549
-            }
550
-            $EVT_ID = $registration->event_ID();
551
-            $ticket = $registration->ticket();
552
-            if (! isset($tickets_remaining[ $ticket->ID() ])) {
553
-                $tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
554
-            }
555
-            if ($tickets_remaining[ $ticket->ID() ] > 0) {
556
-                if (! isset($event_reg_count[ $EVT_ID ])) {
557
-                    $event_reg_count[ $EVT_ID ] = 0;
558
-                }
559
-                $event_reg_count[ $EVT_ID ]++;
560
-                if (! isset($event_spaces_remaining[ $EVT_ID ])) {
561
-                    $event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
562
-                }
563
-            }
564
-            if (
565
-                $revisit
566
-                && ($tickets_remaining[ $ticket->ID() ] === 0
567
-                    || $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
568
-                )
569
-            ) {
570
-                $ejected_registrations[ $REG_ID ] = $registration->event();
571
-                if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
572
-                    /** @type EE_Registration_Processor $registration_processor */
573
-                    $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
574
-                    // at this point, we should have enough details about the registrant to consider the registration
575
-                    // NOT incomplete
576
-                    $registration_processor->manually_update_registration_status(
577
-                        $registration,
578
-                        RegStatus::WAIT_LIST
579
-                    );
580
-                }
581
-            }
582
-        }
583
-        return $ejected_registrations;
584
-    }
585
-
586
-
587
-    /**
588
-     * _hide_reg_step_submit_button
589
-     * removes the html for the reg step submit button
590
-     * by replacing it with an empty string via filter callback
591
-     *
592
-     * @return void
593
-     */
594
-    protected function _adjust_registration_status_if_event_old_sold()
595
-    {
596
-    }
597
-
598
-
599
-    /**
600
-     * _hide_reg_step_submit_button
601
-     * removes the html for the reg step submit button
602
-     * by replacing it with an empty string via filter callback
603
-     *
604
-     * @return void
605
-     */
606
-    protected function _hide_reg_step_submit_button_if_revisit()
607
-    {
608
-        if ($this->checkout->revisit) {
609
-            add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
610
-        }
611
-    }
612
-
613
-
614
-    /**
615
-     * sold_out_events
616
-     * displays notices regarding events that have sold out since hte registrant first signed up
617
-     *
618
-     * @param EE_Event[] $sold_out_events_array
619
-     * @return EE_Form_Section_Proper
620
-     * @throws EE_Error
621
-     */
622
-    private function _sold_out_events($sold_out_events_array = [])
623
-    {
624
-        // set some defaults
625
-        $this->checkout->selected_method_of_payment = 'events_sold_out';
626
-        $sold_out_events                            = '';
627
-        foreach ($sold_out_events_array as $sold_out_event) {
628
-            $sold_out_events .= EEH_HTML::li(
629
-                EEH_HTML::span(
630
-                    '  ' . $sold_out_event->name(),
631
-                    '',
632
-                    'dashicons dashicons-marker ee-icon-size-16 pink-text'
633
-                )
634
-            );
635
-        }
636
-        return new EE_Form_Section_Proper(
637
-            [
638
-                'layout_strategy' => new EE_Template_Layout(
639
-                    [
640
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
641
-                                                  . $this->_slug
642
-                                                  . '/sold_out_events.template.php',
643
-                        'template_args'        => apply_filters(
644
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
645
-                            [
646
-                                'sold_out_events'     => $sold_out_events,
647
-                                'sold_out_events_msg' => apply_filters(
648
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
649
-                                    sprintf(
650
-                                        esc_html__(
651
-                                            'It appears that the event you were about to make a payment for has sold out since you first registered. If you have already made a partial payment towards this event, please contact the event administrator for a refund.%3$s%3$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%2$s',
652
-                                            'event_espresso'
653
-                                        ),
654
-                                        '<strong>',
655
-                                        '</strong>',
656
-                                        '<br />'
657
-                                    )
658
-                                ),
659
-                            ]
660
-                        ),
661
-                    ]
662
-                ),
663
-            ]
664
-        );
665
-    }
666
-
667
-
668
-    /**
669
-     * _insufficient_spaces_available
670
-     * displays notices regarding events that do not have enough remaining spaces
671
-     * to satisfy the current number of registrations looking to pay
672
-     *
673
-     * @param EE_Event[] $insufficient_spaces_events_array
674
-     * @return EE_Form_Section_Proper
675
-     * @throws EE_Error
676
-     * @throws ReflectionException
677
-     */
678
-    private function _insufficient_spaces_available($insufficient_spaces_events_array = [])
679
-    {
680
-        // set some defaults
681
-        $this->checkout->selected_method_of_payment = 'invoice';
682
-        $insufficient_space_events                  = '';
683
-        foreach ($insufficient_spaces_events_array as $event) {
684
-            if ($event instanceof EE_Event) {
685
-                $insufficient_space_events .= EEH_HTML::li(
686
-                    EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
687
-                );
688
-            }
689
-        }
690
-        return new EE_Form_Section_Proper(
691
-            [
692
-                'subsections'     => [
693
-                    'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
694
-                    'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
695
-                ],
696
-                'layout_strategy' => new EE_Template_Layout(
697
-                    [
698
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
699
-                                                  . $this->_slug
700
-                                                  . '/sold_out_events.template.php',
701
-                        'template_args'        => apply_filters(
702
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
703
-                            [
704
-                                'sold_out_events'     => $insufficient_space_events,
705
-                                'sold_out_events_msg' => apply_filters(
706
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
707
-                                    esc_html__(
708
-                                        'It appears that the event you were about to make a payment for has sold additional tickets since you first registered, and there are no longer enough spaces left to accommodate your selections. You may continue to pay and secure the available space(s) remaining, or simply cancel if you no longer wish to purchase. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
709
-                                        'event_espresso'
710
-                                    )
711
-                                ),
712
-                            ]
713
-                        ),
714
-                    ]
715
-                ),
716
-            ]
717
-        );
718
-    }
719
-
720
-
721
-    /**
722
-     * registrations_requiring_pre_approval
723
-     *
724
-     * @param array $registrations_requiring_pre_approval
725
-     * @return EE_Form_Section_Proper
726
-     * @throws EE_Error
727
-     * @throws EntityNotFoundException
728
-     * @throws ReflectionException
729
-     */
730
-    private function _registrations_requiring_pre_approval($registrations_requiring_pre_approval = [])
731
-    {
732
-        $events_requiring_pre_approval = [];
733
-        foreach ($registrations_requiring_pre_approval as $registration) {
734
-            if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
735
-                $events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
736
-                    EEH_HTML::span(
737
-                        '',
738
-                        '',
739
-                        'dashicons dashicons-marker ee-icon-size-16 orange-text'
740
-                    )
741
-                    . EEH_HTML::span($registration->event()->name(), '', 'orange-text')
742
-                );
743
-            }
744
-        }
745
-        return new EE_Form_Section_Proper(
746
-            [
747
-                'layout_strategy' => new EE_Template_Layout(
748
-                    [
749
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
750
-                                                  . $this->_slug
751
-                                                  . '/events_requiring_pre_approval.template.php', // layout_template
752
-                        'template_args'        => apply_filters(
753
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
754
-                            [
755
-                                'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
756
-                                'events_requiring_pre_approval_msg' => apply_filters(
757
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
758
-                                    esc_html__(
759
-                                        'The following events do not require payment at this time and will not be billed during this transaction. Billing will only occur after the attendee has been approved by the event organizer. You will be notified when your registration has been processed. If this is a free event, then no billing will occur.',
760
-                                        'event_espresso'
761
-                                    )
762
-                                ),
763
-                            ]
764
-                        ),
765
-                    ]
766
-                ),
767
-            ]
768
-        );
769
-    }
770
-
771
-
772
-    /**
773
-     * _no_payment_required
774
-     *
775
-     * @param EE_Event[] $registrations_for_free_events
776
-     * @return EE_Form_Section_Proper
777
-     * @throws EE_Error
778
-     */
779
-    private function _no_payment_required($registrations_for_free_events = [])
780
-    {
781
-        // set some defaults
782
-        $this->checkout->selected_method_of_payment = 'no_payment_required';
783
-        // generate no_payment_required form
784
-        return new EE_Form_Section_Proper(
785
-            [
786
-                'layout_strategy' => new EE_Template_Layout(
787
-                    [
788
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
789
-                                                  . $this->_slug
790
-                                                  . '/no_payment_required.template.php', // layout_template
791
-                        'template_args'        => apply_filters(
792
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
793
-                            [
794
-                                'revisit'                       => $this->checkout->revisit,
795
-                                'registrations'                 => [],
796
-                                'ticket_count'                  => [],
797
-                                'registrations_for_free_events' => $registrations_for_free_events,
798
-                                'no_payment_required_msg'       => EEH_HTML::p(
799
-                                    esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
800
-                                ),
801
-                            ]
802
-                        ),
803
-                    ]
804
-                ),
805
-            ]
806
-        );
807
-    }
808
-
809
-
810
-    /**
811
-     * _display_payment_options
812
-     *
813
-     * @param string $transaction_details
814
-     * @return EE_Form_Section_Proper
815
-     * @throws EE_Error
816
-     * @throws InvalidArgumentException
817
-     * @throws InvalidDataTypeException
818
-     * @throws InvalidInterfaceException
819
-     */
820
-    private function _display_payment_options($transaction_details = '')
821
-    {
822
-        // has method_of_payment been set by no-js user?
823
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
824
-        // build payment options form
825
-        return apply_filters(
826
-            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
827
-            new EE_Form_Section_Proper(
828
-                [
829
-                    'subsections'     => [
830
-                        'before_payment_options' => apply_filters(
831
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
832
-                            new EE_Form_Section_Proper(
833
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
834
-                            )
835
-                        ),
836
-                        'payment_options'        => $this->_setup_payment_options(),
837
-                        'after_payment_options'  => apply_filters(
838
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
839
-                            new EE_Form_Section_Proper(
840
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
841
-                            )
842
-                        ),
843
-                    ],
844
-                    'layout_strategy' => new EE_Template_Layout(
845
-                        [
846
-                            'layout_template_file' => $this->_template,
847
-                            'template_args'        => apply_filters(
848
-                                'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
849
-                                [
850
-                                    'reg_count'                 => $this->line_item_display->total_items(),
851
-                                    'transaction_details'       => $transaction_details,
852
-                                    'available_payment_methods' => [],
853
-                                ]
854
-                            ),
855
-                        ]
856
-                    ),
857
-                ]
858
-            )
859
-        );
860
-    }
861
-
862
-
863
-    /**
864
-     * _extra_hidden_inputs
865
-     *
866
-     * @param bool $no_payment_required
867
-     * @return EE_Form_Section_Proper
868
-     * @throws EE_Error
869
-     * @throws ReflectionException
870
-     */
871
-    private function _extra_hidden_inputs($no_payment_required = true)
872
-    {
873
-        return new EE_Form_Section_Proper(
874
-            [
875
-                'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
876
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
877
-                'subsections'     => [
878
-                    'spco_no_payment_required' => new EE_Hidden_Input(
879
-                        [
880
-                            'normalization_strategy' => new EE_Boolean_Normalization(),
881
-                            'html_name'              => 'spco_no_payment_required',
882
-                            'html_id'                => 'spco-no-payment-required-payment_options',
883
-                            'default'                => $no_payment_required,
884
-                        ]
885
-                    ),
886
-                    'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
887
-                        [
888
-                            'normalization_strategy' => new EE_Int_Normalization(),
889
-                            'html_name'              => 'spco_transaction_id',
890
-                            'html_id'                => 'spco-transaction-id',
891
-                            'default'                => $this->checkout->transaction->ID(),
892
-                        ]
893
-                    ),
894
-                ],
895
-            ]
896
-        );
897
-    }
898
-
899
-
900
-    /**
901
-     *    _apply_registration_payments_to_amount_owing
902
-     *
903
-     * @param array $registrations
904
-     * @throws EE_Error
905
-     */
906
-    protected function _apply_registration_payments_to_amount_owing(array $registrations)
907
-    {
908
-        $payments = [];
909
-        foreach ($registrations as $registration) {
910
-            if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
911
-                $payments += $registration->registration_payments();
912
-            }
913
-        }
914
-        if (! empty($payments)) {
915
-            foreach ($payments as $payment) {
916
-                if ($payment instanceof EE_Registration_Payment) {
917
-                    $this->checkout->amount_owing -= $payment->amount();
918
-                }
919
-            }
920
-        }
921
-    }
922
-
923
-
924
-    /**
925
-     *    _reset_selected_method_of_payment
926
-     *
927
-     * @access    private
928
-     * @param bool $force_reset
929
-     * @return void
930
-     * @throws InvalidArgumentException
931
-     * @throws InvalidDataTypeException
932
-     * @throws InvalidInterfaceException
933
-     */
934
-    private function _reset_selected_method_of_payment($force_reset = false)
935
-    {
936
-        /** @var RequestInterface $request */
937
-        $request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
938
-        $reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
939
-        if ($reset_payment_method) {
940
-            $this->checkout->selected_method_of_payment = null;
941
-            $this->checkout->payment_method             = null;
942
-            $this->checkout->billing_form               = null;
943
-            $this->_save_selected_method_of_payment();
944
-        }
945
-    }
946
-
947
-
948
-    /**
949
-     * _save_selected_method_of_payment
950
-     * stores the selected_method_of_payment in the session
951
-     * so that it's available for all subsequent requests including AJAX
952
-     *
953
-     * @access        private
954
-     * @param string $selected_method_of_payment
955
-     * @return void
956
-     * @throws InvalidArgumentException
957
-     * @throws InvalidDataTypeException
958
-     * @throws InvalidInterfaceException
959
-     */
960
-    private function _save_selected_method_of_payment($selected_method_of_payment = '')
961
-    {
962
-        $selected_method_of_payment = ! empty($selected_method_of_payment)
963
-            ? $selected_method_of_payment
964
-            : $this->checkout->selected_method_of_payment;
965
-        EE_Registry::instance()->SSN->set_session_data(
966
-            ['selected_method_of_payment' => $selected_method_of_payment]
967
-        );
968
-    }
969
-
970
-
971
-    /**
972
-     * _setup_payment_options
973
-     *
974
-     * @return EE_Form_Section_Proper
975
-     * @throws EE_Error
976
-     * @throws InvalidArgumentException
977
-     * @throws InvalidDataTypeException
978
-     * @throws InvalidInterfaceException
979
-     */
980
-    public function _setup_payment_options()
981
-    {
982
-        // load payment method classes
983
-        $this->checkout->available_payment_methods = $this->_get_available_payment_methods();
984
-        if (empty($this->checkout->available_payment_methods)) {
985
-            EE_Error::add_error(
986
-                apply_filters(
987
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
988
-                    sprintf(
989
-                        esc_html__(
990
-                            'Sorry, you cannot complete your purchase because a payment method is not active.%1$s Please contact %2$s for assistance and provide a description of the problem.',
991
-                            'event_espresso'
992
-                        ),
993
-                        '<br>',
994
-                        EE_Registry::instance()->CFG->organization->get_pretty('email')
995
-                    )
996
-                ),
997
-                __FILE__,
998
-                __FUNCTION__,
999
-                __LINE__
1000
-            );
1001
-        }
1002
-        // switch up header depending on number of available payment methods
1003
-        $payment_method_header     = count($this->checkout->available_payment_methods) > 1
1004
-            ? apply_filters(
1005
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
1006
-                esc_html__('Please Select Your Method of Payment', 'event_espresso')
1007
-            )
1008
-            : apply_filters(
1009
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
1010
-                esc_html__('Method of Payment', 'event_espresso')
1011
-            );
1012
-        $available_payment_methods = [
1013
-            // display the "Payment Method" header
1014
-            'payment_method_header' => new EE_Form_Section_HTML(
1015
-                apply_filters(
1016
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
1017
-                    EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
1018
-                    $payment_method_header
1019
-                )
1020
-            ),
1021
-        ];
1022
-        // the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
1023
-        $available_payment_method_options = [];
1024
-        $default_payment_method_option    = [];
1025
-        // additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
1026
-        $payment_methods_billing_info = [
1027
-            new EE_Form_Section_HTML(
1028
-                EEH_HTML::div('<br />', '', '', 'clear:both;')
1029
-            ),
1030
-        ];
1031
-        // loop through payment methods
1032
-        foreach ($this->checkout->available_payment_methods as $payment_method) {
1033
-            if ($payment_method instanceof EE_Payment_Method) {
1034
-                $payment_method_button = EEH_HTML::img(
1035
-                    $payment_method->button_url(),
1036
-                    $payment_method->name(),
1037
-                    'spco-payment-method-' . $payment_method->slug() . '-btn-img',
1038
-                    'spco-payment-method-btn-img'
1039
-                );
1040
-                // check if any payment methods are set as default
1041
-                // if payment method is already selected OR nothing is selected and this payment method should be
1042
-                // open_by_default
1043
-                if (
1044
-                    ($this->checkout->selected_method_of_payment === $payment_method->slug())
1045
-                    || (! $this->checkout->selected_method_of_payment && $payment_method->open_by_default())
1046
-                ) {
1047
-                    $this->checkout->selected_method_of_payment = $payment_method->slug();
1048
-                    $this->_save_selected_method_of_payment();
1049
-                    $default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1050
-                } else {
1051
-                    $available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1052
-                }
1053
-                $payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1054
-                    $this->_payment_method_billing_info(
1055
-                        $payment_method
1056
-                    );
1057
-            }
1058
-        }
1059
-        // prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1060
-        // of PMs
1061
-        $available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1062
-        // now generate the actual form  inputs
1063
-        $available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1064
-            $available_payment_method_options
1065
-        );
1066
-        $available_payment_methods                              += $payment_methods_billing_info;
1067
-        // build the available payment methods form
1068
-        return new EE_Form_Section_Proper(
1069
-            [
1070
-                'html_id'         => 'spco-available-methods-of-payment-dv',
1071
-                'subsections'     => $available_payment_methods,
1072
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1073
-            ]
1074
-        );
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * _get_available_payment_methods
1080
-     *
1081
-     * @return EE_Payment_Method[]
1082
-     * @throws EE_Error
1083
-     * @throws InvalidArgumentException
1084
-     * @throws InvalidDataTypeException
1085
-     * @throws InvalidInterfaceException
1086
-     */
1087
-    protected function _get_available_payment_methods()
1088
-    {
1089
-        if (! empty($this->checkout->available_payment_methods)) {
1090
-            return $this->checkout->available_payment_methods;
1091
-        }
1092
-        $available_payment_methods = [];
1093
-        $EEM_Payment_Method        = EEM_Payment_Method::instance();
1094
-        // get all active payment methods
1095
-        $payment_methods = $EEM_Payment_Method->get_all_for_transaction(
1096
-            $this->checkout->transaction,
1097
-            EEM_Payment_Method::scope_cart
1098
-        );
1099
-        foreach ($payment_methods as $payment_method) {
1100
-            if ($payment_method instanceof EE_Payment_Method) {
1101
-                $available_payment_methods[ $payment_method->slug() ] = $payment_method;
1102
-            }
1103
-        }
1104
-        return $available_payment_methods;
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     *    _available_payment_method_inputs
1110
-     *
1111
-     * @access    private
1112
-     * @param array $available_payment_method_options
1113
-     * @return    EE_Form_Section_Proper
1114
-     * @throws EE_Error
1115
-     * @throws EE_Error
1116
-     */
1117
-    private function _available_payment_method_inputs($available_payment_method_options = [])
1118
-    {
1119
-        // generate inputs
1120
-        return new EE_Form_Section_Proper(
1121
-            [
1122
-                'html_id'         => 'ee-available-payment-method-inputs',
1123
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1124
-                'subsections'     => [
1125
-                    '' => new EE_Radio_Button_Input(
1126
-                        $available_payment_method_options,
1127
-                        [
1128
-                            'html_name'          => 'selected_method_of_payment',
1129
-                            'html_class'         => 'spco-payment-method',
1130
-                            'default'            => $this->checkout->selected_method_of_payment,
1131
-                            'label_size'         => 11,
1132
-                            'enforce_label_size' => true,
1133
-                        ]
1134
-                    ),
1135
-                ],
1136
-            ]
1137
-        );
1138
-    }
1139
-
1140
-
1141
-    /**
1142
-     *    _payment_method_billing_info
1143
-     *
1144
-     * @access    private
1145
-     * @param EE_Payment_Method $payment_method
1146
-     * @return EE_Form_Section_Proper
1147
-     * @throws EE_Error
1148
-     * @throws InvalidArgumentException
1149
-     * @throws InvalidDataTypeException
1150
-     * @throws InvalidInterfaceException
1151
-     */
1152
-    private function _payment_method_billing_info(EE_Payment_Method $payment_method)
1153
-    {
1154
-        $currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1155
-        // generate the billing form for payment method
1156
-        $billing_form                 = $currently_selected
1157
-            ? $this->_get_billing_form_for_payment_method($payment_method)
1158
-            : new EE_Form_Section_HTML();
1159
-        $this->checkout->billing_form = $currently_selected
1160
-            ? $billing_form
1161
-            : $this->checkout->billing_form;
1162
-        // it's all in the details
1163
-        $info_html = EEH_HTML::h3(
1164
-            esc_html__('Important information regarding your payment', 'event_espresso'),
1165
-            '',
1166
-            'spco-payment-method-hdr'
1167
-        );
1168
-        // add some info regarding the step, either from what's saved in the admin,
1169
-        // or a default string depending on whether the PM has a billing form or not
1170
-        if ($payment_method->description()) {
1171
-            $payment_method_info = $payment_method->description();
1172
-        } elseif ($billing_form instanceof EE_Billing_Info_Form) {
1173
-            $payment_method_info = sprintf(
1174
-                esc_html__(
1175
-                    'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1176
-                    'event_espresso'
1177
-                ),
1178
-                $this->submit_button_text()
1179
-            );
1180
-        } else {
1181
-            $payment_method_info = sprintf(
1182
-                esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1183
-                $this->submit_button_text()
1184
-            );
1185
-        }
1186
-        $info_html .= EEH_HTML::div(
1187
-            apply_filters(
1188
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1189
-                $payment_method_info
1190
-            ),
1191
-            '',
1192
-            'spco-payment-method-desc ee-attention'
1193
-        );
1194
-        return new EE_Form_Section_Proper(
1195
-            [
1196
-                'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1197
-                'html_class'      => 'spco-payment-method-info-dv',
1198
-                // only display the selected or default PM
1199
-                'html_style'      => $currently_selected ? '' : 'display:none;',
1200
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1201
-                'subsections'     => [
1202
-                    'info'         => new EE_Form_Section_HTML($info_html),
1203
-                    'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1204
-                ],
1205
-            ]
1206
-        );
1207
-    }
1208
-
1209
-
1210
-    /**
1211
-     * get_billing_form_html_for_payment_method
1212
-     *
1213
-     * @return bool
1214
-     * @throws EE_Error
1215
-     * @throws InvalidArgumentException
1216
-     * @throws ReflectionException
1217
-     * @throws InvalidDataTypeException
1218
-     * @throws InvalidInterfaceException
1219
-     */
1220
-    public function get_billing_form_html_for_payment_method()
1221
-    {
1222
-        // how have they chosen to pay?
1223
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1224
-        $this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1225
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1226
-            return false;
1227
-        }
1228
-        if (
1229
-            apply_filters(
1230
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1231
-                false
1232
-            )
1233
-        ) {
1234
-            EE_Error::add_success(
1235
-                apply_filters(
1236
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1237
-                    sprintf(
1238
-                        esc_html__(
1239
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1240
-                            'event_espresso'
1241
-                        ),
1242
-                        $this->checkout->payment_method->name()
1243
-                    )
1244
-                )
1245
-            );
1246
-        }
1247
-        // now generate billing form for selected method of payment
1248
-        $payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1249
-        // fill form with attendee info if applicable
1250
-        if (
1251
-            $payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1252
-            && $this->checkout->transaction_has_primary_registrant()
1253
-        ) {
1254
-            $payment_method_billing_form->populate_from_attendee(
1255
-                $this->checkout->transaction->primary_registration()->attendee()
1256
-            );
1257
-        }
1258
-        // and debug content
1259
-        if (
1260
-            $payment_method_billing_form instanceof EE_Billing_Info_Form
1261
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1262
-        ) {
1263
-            $payment_method_billing_form =
1264
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1265
-                    $payment_method_billing_form
1266
-                );
1267
-        }
1268
-        $billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1269
-            ? $payment_method_billing_form->get_html()
1270
-            : '';
1271
-        $this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1272
-        // localize validation rules for main form
1273
-        $this->checkout->current_step->reg_form->localize_validation_rules();
1274
-        $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1275
-        return true;
1276
-    }
1277
-
1278
-
1279
-    /**
1280
-     * _get_billing_form_for_payment_method
1281
-     *
1282
-     * @param EE_Payment_Method $payment_method
1283
-     * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1284
-     * @throws EE_Error
1285
-     * @throws InvalidArgumentException
1286
-     * @throws InvalidDataTypeException
1287
-     * @throws InvalidInterfaceException
1288
-     */
1289
-    private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1290
-    {
1291
-        $billing_form = $payment_method->type_obj()->billing_form(
1292
-            $this->checkout->transaction,
1293
-            ['amount_owing' => $this->checkout->amount_owing]
1294
-        );
1295
-        if ($billing_form instanceof EE_Billing_Info_Form) {
1296
-            if (
1297
-                apply_filters(
1298
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1299
-                    false
1300
-                )
1301
-                && $this->request->requestParamIsSet('payment_method')
1302
-            ) {
1303
-                EE_Error::add_success(
1304
-                    apply_filters(
1305
-                        'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1306
-                        sprintf(
1307
-                            esc_html__(
1308
-                                'You have selected "%s" as your method of payment. Please note the important payment information below.',
1309
-                                'event_espresso'
1310
-                            ),
1311
-                            $payment_method->name()
1312
-                        )
1313
-                    )
1314
-                );
1315
-            }
1316
-            return apply_filters(
1317
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1318
-                $billing_form,
1319
-                $payment_method
1320
-            );
1321
-        }
1322
-        // no actual billing form, so return empty HTML form section
1323
-        return new EE_Form_Section_HTML();
1324
-    }
1325
-
1326
-
1327
-    /**
1328
-     * _get_selected_method_of_payment
1329
-     *
1330
-     * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1331
-     *                          is not found in the incoming request
1332
-     * @param string  $request_param
1333
-     * @return NULL|string
1334
-     * @throws EE_Error
1335
-     * @throws InvalidArgumentException
1336
-     * @throws InvalidDataTypeException
1337
-     * @throws InvalidInterfaceException
1338
-     */
1339
-    private function _get_selected_method_of_payment(
1340
-        $required = false,
1341
-        $request_param = 'selected_method_of_payment'
1342
-    ) {
1343
-        // is selected_method_of_payment set in the request ?
1344
-        $selected_method_of_payment = $this->request->getRequestParam($request_param);
1345
-        if ($selected_method_of_payment) {
1346
-            // sanitize it
1347
-            $selected_method_of_payment = is_array($selected_method_of_payment)
1348
-                ? array_shift($selected_method_of_payment)
1349
-                : $selected_method_of_payment;
1350
-            $selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1351
-            // store it in the session so that it's available for all subsequent requests including AJAX
1352
-            $this->_save_selected_method_of_payment($selected_method_of_payment);
1353
-        } else {
1354
-            // or is it set in the session ?
1355
-            $selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1356
-                'selected_method_of_payment'
1357
-            );
1358
-        }
1359
-        if (empty($selected_method_of_payment) && $required) {
1360
-            EE_Error::add_error(
1361
-                sprintf(
1362
-                    esc_html__(
1363
-                        'The selected method of payment could not be determined.%sPlease ensure that you have selected one before proceeding.%sIf you continue to experience difficulties, then refresh your browser and try again, or contact %s for assistance.',
1364
-                        'event_espresso'
1365
-                    ),
1366
-                    '<br/>',
1367
-                    '<br/>',
1368
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
1369
-                ),
1370
-                __FILE__,
1371
-                __FUNCTION__,
1372
-                __LINE__
1373
-            );
1374
-            return null;
1375
-        }
1376
-        return $selected_method_of_payment;
1377
-    }
1378
-
1379
-
1380
-
1381
-
1382
-
1383
-
1384
-    /********************************************************************************************************/
1385
-    /***********************************  SWITCH PAYMENT METHOD  ************************************/
1386
-    /********************************************************************************************************/
1387
-    /**
1388
-     * switch_payment_method
1389
-     *
1390
-     * @return bool
1391
-     * @throws EE_Error
1392
-     * @throws InvalidArgumentException
1393
-     * @throws InvalidDataTypeException
1394
-     * @throws InvalidInterfaceException
1395
-     * @throws ReflectionException
1396
-     */
1397
-    public function switch_payment_method()
1398
-    {
1399
-        if (! $this->_verify_payment_method_is_set()) {
1400
-            return false;
1401
-        }
1402
-        if (
1403
-            apply_filters(
1404
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1405
-                false
1406
-            )
1407
-        ) {
1408
-            EE_Error::add_success(
1409
-                apply_filters(
1410
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1411
-                    sprintf(
1412
-                        esc_html__(
1413
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1414
-                            'event_espresso'
1415
-                        ),
1416
-                        $this->checkout->payment_method->name()
1417
-                    )
1418
-                )
1419
-            );
1420
-        }
1421
-        // generate billing form for selected method of payment if it hasn't been done already
1422
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1423
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1424
-                $this->checkout->payment_method
1425
-            );
1426
-        }
1427
-        // fill form with attendee info if applicable
1428
-        if (
1429
-            apply_filters(
1430
-                'FHEE__populate_billing_form_fields_from_attendee',
1431
-                (
1432
-                    $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1433
-                    && $this->checkout->transaction_has_primary_registrant()
1434
-                ),
1435
-                $this->checkout->billing_form,
1436
-                $this->checkout->transaction
1437
-            )
1438
-        ) {
1439
-            $this->checkout->billing_form->populate_from_attendee(
1440
-                $this->checkout->transaction->primary_registration()->attendee()
1441
-            );
1442
-        }
1443
-        // and debug content
1444
-        if (
1445
-            $this->checkout->billing_form instanceof EE_Billing_Info_Form
1446
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1447
-        ) {
1448
-            $this->checkout->billing_form =
1449
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1450
-                    $this->checkout->billing_form
1451
-                );
1452
-        }
1453
-        // get html and validation rules for form
1454
-        if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1455
-            $this->checkout->json_response->set_return_data(
1456
-                ['payment_method_info' => $this->checkout->billing_form->get_html()]
1457
-            );
1458
-            // localize validation rules for main form
1459
-            $this->checkout->billing_form->localize_validation_rules(true);
1460
-            $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1461
-        } else {
1462
-            $this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1463
-        }
1464
-        // prevents advancement to next step
1465
-        $this->checkout->continue_reg = false;
1466
-        return true;
1467
-    }
1468
-
1469
-
1470
-    /**
1471
-     * _verify_payment_method_is_set
1472
-     *
1473
-     * @return bool
1474
-     * @throws EE_Error
1475
-     * @throws InvalidArgumentException
1476
-     * @throws ReflectionException
1477
-     * @throws InvalidDataTypeException
1478
-     * @throws InvalidInterfaceException
1479
-     */
1480
-    protected function _verify_payment_method_is_set()
1481
-    {
1482
-        // generate billing form for selected method of payment if it hasn't been done already
1483
-        if (empty($this->checkout->selected_method_of_payment)) {
1484
-            // how have they chosen to pay?
1485
-            $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1486
-        } else {
1487
-            // choose your own adventure based on method_of_payment
1488
-            switch ($this->checkout->selected_method_of_payment) {
1489
-                case 'events_sold_out':
1490
-                    EE_Error::add_attention(
1491
-                        apply_filters(
1492
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1493
-                            esc_html__(
1494
-                                'It appears that the event you were about to make a payment for has sold out since this form first loaded. Please contact the event administrator if you believe this is an error.',
1495
-                                'event_espresso'
1496
-                            )
1497
-                        ),
1498
-                        __FILE__,
1499
-                        __FUNCTION__,
1500
-                        __LINE__
1501
-                    );
1502
-                    return false;
1503
-                case 'payments_closed':
1504
-                    EE_Error::add_attention(
1505
-                        apply_filters(
1506
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1507
-                            esc_html__(
1508
-                                'It appears that the event you were about to make a payment for is not accepting payments at this time. Please contact the event administrator if you believe this is an error.',
1509
-                                'event_espresso'
1510
-                            )
1511
-                        ),
1512
-                        __FILE__,
1513
-                        __FUNCTION__,
1514
-                        __LINE__
1515
-                    );
1516
-                    return false;
1517
-                case 'no_payment_required':
1518
-                    EE_Error::add_attention(
1519
-                        apply_filters(
1520
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1521
-                            esc_html__(
1522
-                                'It appears that the event you were about to make a payment for does not require payment. Please contact the event administrator if you believe this is an error.',
1523
-                                'event_espresso'
1524
-                            )
1525
-                        ),
1526
-                        __FILE__,
1527
-                        __FUNCTION__,
1528
-                        __LINE__
1529
-                    );
1530
-                    return false;
1531
-                default:
1532
-            }
1533
-        }
1534
-        // verify payment method
1535
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1536
-            // get payment method for selected method of payment
1537
-            $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1538
-        }
1539
-        return $this->checkout->payment_method instanceof EE_Payment_Method;
1540
-    }
1541
-
1542
-
1543
-
1544
-    /********************************************************************************************************/
1545
-    /***************************************  SAVE PAYER DETAILS  ****************************************/
1546
-    /********************************************************************************************************/
1547
-    /**
1548
-     * save_payer_details_via_ajax
1549
-     *
1550
-     * @return void
1551
-     * @throws EE_Error
1552
-     * @throws InvalidArgumentException
1553
-     * @throws ReflectionException
1554
-     * @throws RuntimeException
1555
-     * @throws InvalidDataTypeException
1556
-     * @throws InvalidInterfaceException
1557
-     */
1558
-    public function save_payer_details_via_ajax()
1559
-    {
1560
-        if (! $this->_verify_payment_method_is_set()) {
1561
-            return;
1562
-        }
1563
-        // generate billing form for selected method of payment if it hasn't been done already
1564
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1565
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1566
-                $this->checkout->payment_method
1567
-            );
1568
-        }
1569
-        // generate primary attendee from payer info if applicable
1570
-        if (! $this->checkout->transaction_has_primary_registrant()) {
1571
-            $attendee = $this->_create_attendee_from_request_data();
1572
-            if ($attendee instanceof EE_Attendee) {
1573
-                foreach ($this->checkout->transaction->registrations() as $registration) {
1574
-                    if ($registration->is_primary_registrant()) {
1575
-                        $this->checkout->primary_attendee_obj = $attendee;
1576
-                        $registration->_add_relation_to($attendee, 'Attendee');
1577
-                        $registration->set_attendee_id($attendee->ID());
1578
-                        $registration->update_cache_after_object_save('Attendee', $attendee);
1579
-                    }
1580
-                }
1581
-            }
1582
-        }
1583
-    }
1584
-
1585
-
1586
-    /**
1587
-     * create_attendee_from_request_data
1588
-     * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1589
-     *
1590
-     * @return EE_Attendee
1591
-     * @throws EE_Error
1592
-     * @throws InvalidArgumentException
1593
-     * @throws ReflectionException
1594
-     * @throws InvalidDataTypeException
1595
-     * @throws InvalidInterfaceException
1596
-     */
1597
-    protected function _create_attendee_from_request_data()
1598
-    {
1599
-        // get State ID
1600
-        $STA_ID = $this->request->getRequestParam('state');
1601
-        if (! empty($STA_ID)) {
1602
-            // can we get state object from name ?
1603
-            EE_Registry::instance()->load_model('State');
1604
-            $state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1605
-            $STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1606
-        }
1607
-        // get Country ISO
1608
-        $CNT_ISO = $this->request->getRequestParam('country');
1609
-        if (! empty($CNT_ISO)) {
1610
-            // can we get country object from name ?
1611
-            EE_Registry::instance()->load_model('Country');
1612
-            $country = EEM_Country::instance()->get_col(
1613
-                [['CNT_name' => $CNT_ISO], 'limit' => 1],
1614
-                'CNT_ISO'
1615
-            );
1616
-            $CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1617
-        }
1618
-        // grab attendee data
1619
-        $attendee_data = [
1620
-            'ATT_fname'    => $this->request->getRequestParam('first_name'),
1621
-            'ATT_lname'    => $this->request->getRequestParam('last_name'),
1622
-            'ATT_email'    => $this->request->getRequestParam('email'),
1623
-            'ATT_address'  => $this->request->getRequestParam('address'),
1624
-            'ATT_address2' => $this->request->getRequestParam('address2'),
1625
-            'ATT_city'     => $this->request->getRequestParam('city'),
1626
-            'STA_ID'       => $STA_ID,
1627
-            'CNT_ISO'      => $CNT_ISO,
1628
-            'ATT_zip'      => $this->request->getRequestParam('zip'),
1629
-            'ATT_phone'    => $this->request->getRequestParam('phone'),
1630
-        ];
1631
-        // validate the email address since it is the most important piece of info
1632
-        if (empty($attendee_data['ATT_email'])) {
1633
-            EE_Error::add_error(
1634
-                esc_html__('An invalid email address was submitted.', 'event_espresso'),
1635
-                __FILE__,
1636
-                __FUNCTION__,
1637
-                __LINE__
1638
-            );
1639
-        }
1640
-        // does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1641
-        // AND email address
1642
-        if (
1643
-            ! empty($attendee_data['ATT_fname'])
1644
-            && ! empty($attendee_data['ATT_lname'])
1645
-            && ! empty($attendee_data['ATT_email'])
1646
-        ) {
1647
-            $existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1648
-                [
1649
-                    'ATT_fname' => $attendee_data['ATT_fname'],
1650
-                    'ATT_lname' => $attendee_data['ATT_lname'],
1651
-                    'ATT_email' => $attendee_data['ATT_email'],
1652
-                ]
1653
-            );
1654
-            if ($existing_attendee instanceof EE_Attendee) {
1655
-                return $existing_attendee;
1656
-            }
1657
-        }
1658
-        // no existing attendee? kk let's create a new one
1659
-        // kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1660
-        // don't exist
1661
-        $attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1662
-            ? $attendee_data['ATT_fname']
1663
-            : $attendee_data['ATT_email'];
1664
-        $attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1665
-            ? $attendee_data['ATT_lname']
1666
-            : $attendee_data['ATT_email'];
1667
-        return EE_Attendee::new_instance($attendee_data);
1668
-    }
1669
-
1670
-
1671
-
1672
-    /********************************************************************************************************/
1673
-    /****************************************  PROCESS REG STEP  *****************************************/
1674
-    /********************************************************************************************************/
1675
-    /**
1676
-     * process_reg_step
1677
-     *
1678
-     * @return bool
1679
-     * @throws EE_Error
1680
-     * @throws InvalidArgumentException
1681
-     * @throws ReflectionException
1682
-     * @throws EntityNotFoundException
1683
-     * @throws InvalidDataTypeException
1684
-     * @throws InvalidInterfaceException
1685
-     * @throws InvalidStatusException
1686
-     */
1687
-    public function process_reg_step()
1688
-    {
1689
-        // how have they chosen to pay?
1690
-        $this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1691
-            ? 'no_payment_required'
1692
-            : $this->_get_selected_method_of_payment(true);
1693
-        // choose your own adventure based on method_of_payment
1694
-        switch ($this->checkout->selected_method_of_payment) {
1695
-            case 'events_sold_out':
1696
-                $this->checkout->redirect     = true;
1697
-                $this->checkout->redirect_url = $this->checkout->cancel_page_url;
1698
-                $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1699
-                // mark this reg step as completed
1700
-                $this->set_completed();
1701
-                return false;
1702
-
1703
-            case 'payments_closed':
1704
-                if (
1705
-                    apply_filters(
1706
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1707
-                        false
1708
-                    )
1709
-                ) {
1710
-                    EE_Error::add_success(
1711
-                        esc_html__('no payment required at this time.', 'event_espresso'),
1712
-                        __FILE__,
1713
-                        __FUNCTION__,
1714
-                        __LINE__
1715
-                    );
1716
-                }
1717
-                // mark this reg step as completed
1718
-                $this->set_completed();
1719
-                return true;
1720
-
1721
-            case 'no_payment_required':
1722
-                if (
1723
-                    apply_filters(
1724
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1725
-                        false
1726
-                    )
1727
-                ) {
1728
-                    EE_Error::add_success(
1729
-                        esc_html__('no payment required.', 'event_espresso'),
1730
-                        __FILE__,
1731
-                        __FUNCTION__,
1732
-                        __LINE__
1733
-                    );
1734
-                }
1735
-                // mark this reg step as completed
1736
-                $this->set_completed();
1737
-                return true;
1738
-
1739
-            default:
1740
-                $registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1741
-                    EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1742
-                );
1743
-                $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1744
-                    $registrations,
1745
-                    EE_Registry::instance()->SSN->checkout()->revisit
1746
-                );
1747
-                // calculate difference between the two arrays
1748
-                $registrations = array_diff($registrations, $ejected_registrations);
1749
-                if (empty($registrations)) {
1750
-                    $this->_redirect_because_event_sold_out();
1751
-                    return false;
1752
-                }
1753
-                $payment = $this->_process_payment();
1754
-                if ($payment instanceof EE_Payment) {
1755
-                    $this->checkout->continue_reg = true;
1756
-                    $this->_maybe_set_completed($payment);
1757
-                } else {
1758
-                    $this->checkout->continue_reg = false;
1759
-                }
1760
-                return $payment instanceof EE_Payment;
1761
-        }
1762
-    }
1763
-
1764
-
1765
-    /**
1766
-     * _redirect_because_event_sold_out
1767
-     *
1768
-     * @return void
1769
-     */
1770
-    protected function _redirect_because_event_sold_out()
1771
-    {
1772
-        $this->checkout->continue_reg = false;
1773
-        // set redirect URL
1774
-        $this->checkout->redirect_url = add_query_arg(
1775
-            ['e_reg_url_link' => $this->checkout->reg_url_link],
1776
-            $this->checkout->current_step->reg_step_url()
1777
-        );
1778
-        $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1779
-    }
1780
-
1781
-
1782
-    /**
1783
-     * @param EE_Payment $payment
1784
-     * @return void
1785
-     * @throws EE_Error
1786
-     */
1787
-    protected function _maybe_set_completed(EE_Payment $payment)
1788
-    {
1789
-        // Do we need to redirect them? If so, there's more work to be done.
1790
-        if (! $payment->redirect_url()) {
1791
-            $this->set_completed();
1792
-        }
1793
-    }
1794
-
1795
-
1796
-    /**
1797
-     *    update_reg_step
1798
-     *    this is the final step after a user  revisits the site to retry a payment
1799
-     *
1800
-     * @return bool
1801
-     * @throws EE_Error
1802
-     * @throws InvalidArgumentException
1803
-     * @throws ReflectionException
1804
-     * @throws EntityNotFoundException
1805
-     * @throws InvalidDataTypeException
1806
-     * @throws InvalidInterfaceException
1807
-     * @throws InvalidStatusException
1808
-     */
1809
-    public function update_reg_step()
1810
-    {
1811
-        $success = true;
1812
-        // if payment required
1813
-        if ($this->checkout->transaction->total() > 0) {
1814
-            do_action(
1815
-                'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1816
-                $this->checkout->transaction
1817
-            );
1818
-            // attempt payment via payment method
1819
-            $success = $this->process_reg_step();
1820
-        }
1821
-        if ($success && ! $this->checkout->redirect) {
1822
-            $this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1823
-                $this->checkout->transaction->ID()
1824
-            );
1825
-            // set return URL
1826
-            $this->checkout->redirect_url = add_query_arg(
1827
-                ['e_reg_url_link' => $this->checkout->reg_url_link],
1828
-                $this->checkout->thank_you_page_url
1829
-            );
1830
-        }
1831
-        return $success;
1832
-    }
1833
-
1834
-
1835
-    /**
1836
-     * @return EE_Payment|null
1837
-     * @throws EE_Error
1838
-     * @throws InvalidArgumentException
1839
-     * @throws ReflectionException
1840
-     * @throws RuntimeException
1841
-     * @throws InvalidDataTypeException
1842
-     * @throws InvalidInterfaceException
1843
-     */
1844
-    private function _process_payment()
1845
-    {
1846
-        // basically confirm that the event hasn't sold out since they hit the page
1847
-        if (! $this->_last_second_ticket_verifications()) {
1848
-            return null;
1849
-        }
1850
-        // ya gotta make a choice man
1851
-        if (empty($this->checkout->selected_method_of_payment)) {
1852
-            $this->checkout->json_response->set_plz_select_method_of_payment(
1853
-                esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1854
-            );
1855
-            return null;
1856
-        }
1857
-        // get EE_Payment_Method object
1858
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1859
-            return null;
1860
-        }
1861
-        // setup billing form
1862
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1863
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1864
-                $this->checkout->payment_method
1865
-            );
1866
-            // bad billing form ?
1867
-            if (! $this->_billing_form_is_valid()) {
1868
-                return null;
1869
-            }
1870
-        }
1871
-        // ensure primary registrant has been fully processed
1872
-        if (! $this->_setup_primary_registrant_prior_to_payment()) {
1873
-            return null;
1874
-        }
1875
-        // if session is close to expiring (under 10 minutes by default)
1876
-        if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1877
-            // add some time to session expiration so that payment can be completed
1878
-            EE_Registry::instance()->SSN->extend_expiration();
1879
-        }
1880
-        /** @type EE_Transaction_Processor $transaction_processor */
1881
-        // $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1882
-        // in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1883
-        // for events with a default reg status of Approved
1884
-        // $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1885
-        //      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1886
-        // );
1887
-        // attempt payment
1888
-        $payment = $this->_attempt_payment($this->checkout->payment_method);
1889
-        // process results
1890
-        $payment = $this->_validate_payment($payment);
1891
-        $payment = $this->_post_payment_processing($payment);
1892
-        // verify payment
1893
-        if ($payment instanceof EE_Payment) {
1894
-            // store that for later
1895
-            $this->checkout->payment = $payment;
1896
-            // we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1897
-            $this->checkout->transaction->toggle_failed_transaction_status();
1898
-            $payment_status = $payment->status();
1899
-            if (
1900
-                $payment_status === EEM_Payment::status_id_approved
1901
-                || $payment_status === EEM_Payment::status_id_pending
1902
-            ) {
1903
-                return $payment;
1904
-            }
1905
-            return null;
1906
-        }
1907
-        if ($payment === true) {
1908
-            // please note that offline payment methods will NOT make a payment,
1909
-            // but instead just mark themselves as the PMD_ID on the transaction, and return true
1910
-            $this->checkout->payment = $payment;
1911
-            return $payment;
1912
-        }
1913
-        // where's my money?
1914
-        return null;
1915
-    }
1916
-
1917
-
1918
-    /**
1919
-     * _last_second_ticket_verifications
1920
-     *
1921
-     * @return bool
1922
-     * @throws EE_Error
1923
-     * @throws ReflectionException
1924
-     */
1925
-    protected function _last_second_ticket_verifications()
1926
-    {
1927
-        // don't bother re-validating if not a return visit
1928
-        if (! $this->checkout->revisit) {
1929
-            return true;
1930
-        }
1931
-        $registrations = $this->checkout->transaction->registrations();
1932
-        if (empty($registrations)) {
1933
-            return false;
1934
-        }
1935
-        foreach ($registrations as $registration) {
1936
-            if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1937
-                $event = $registration->event_obj();
1938
-                if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1939
-                    EE_Error::add_error(
1940
-                        apply_filters(
1941
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1942
-                            sprintf(
1943
-                                esc_html__(
1944
-                                    'It appears that the %1$s event that you were about to make a payment for has sold out since you first registered and/or arrived at this page. Please refresh the page and try again. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
1945
-                                    'event_espresso'
1946
-                                ),
1947
-                                $event->name()
1948
-                            )
1949
-                        ),
1950
-                        __FILE__,
1951
-                        __FUNCTION__,
1952
-                        __LINE__
1953
-                    );
1954
-                    return false;
1955
-                }
1956
-            }
1957
-        }
1958
-        return true;
1959
-    }
1960
-
1961
-
1962
-    /**
1963
-     * redirect_form
1964
-     *
1965
-     * @return bool
1966
-     * @throws EE_Error
1967
-     * @throws InvalidArgumentException
1968
-     * @throws ReflectionException
1969
-     * @throws InvalidDataTypeException
1970
-     * @throws InvalidInterfaceException
1971
-     */
1972
-    public function redirect_form()
1973
-    {
1974
-        $payment_method_billing_info = $this->_payment_method_billing_info(
1975
-            $this->_get_payment_method_for_selected_method_of_payment()
1976
-        );
1977
-        $html                        = $payment_method_billing_info->get_html();
1978
-        $html                        .= $this->checkout->redirect_form;
1979
-        /** @var ResponseInterface $response */
1980
-        $response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1981
-        $response->addOutput($html);
1982
-        return true;
1983
-    }
1984
-
1985
-
1986
-    /**
1987
-     * _billing_form_is_valid
1988
-     *
1989
-     * @return bool
1990
-     * @throws EE_Error
1991
-     */
1992
-    private function _billing_form_is_valid()
1993
-    {
1994
-        if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1995
-            return true;
1996
-        }
1997
-        if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1998
-            if ($this->checkout->billing_form->was_submitted()) {
1999
-                $this->checkout->billing_form->receive_form_submission();
2000
-                if ($this->checkout->billing_form->is_valid()) {
2001
-                    return true;
2002
-                }
2003
-                $validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
2004
-                $error_strings     = [];
2005
-                foreach ($validation_errors as $validation_error) {
2006
-                    if ($validation_error instanceof EE_Validation_Error) {
2007
-                        $form_section = $validation_error->get_form_section();
2008
-                        if ($form_section instanceof EE_Form_Input_Base) {
2009
-                            $label = $form_section->html_label_text();
2010
-                        } elseif ($form_section instanceof EE_Form_Section_Base) {
2011
-                            $label = $form_section->name();
2012
-                        } else {
2013
-                            $label = esc_html__('Validation Error', 'event_espresso');
2014
-                        }
2015
-                        $error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
2016
-                    }
2017
-                }
2018
-                EE_Error::add_error(
2019
-                    sprintf(
2020
-                        esc_html__(
2021
-                            'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
2022
-                            'event_espresso'
2023
-                        ),
2024
-                        '<br/>',
2025
-                        implode('<br/>', $error_strings)
2026
-                    ),
2027
-                    __FILE__,
2028
-                    __FUNCTION__,
2029
-                    __LINE__
2030
-                );
2031
-            } else {
2032
-                EE_Error::add_error(
2033
-                    esc_html__(
2034
-                        'The billing form was not submitted or something prevented it\'s submission.',
2035
-                        'event_espresso'
2036
-                    ),
2037
-                    __FILE__,
2038
-                    __FUNCTION__,
2039
-                    __LINE__
2040
-                );
2041
-            }
2042
-        } else {
2043
-            EE_Error::add_error(
2044
-                esc_html__(
2045
-                    'The submitted billing form is invalid possibly due to a technical reason.',
2046
-                    'event_espresso'
2047
-                ),
2048
-                __FILE__,
2049
-                __FUNCTION__,
2050
-                __LINE__
2051
-            );
2052
-        }
2053
-        return false;
2054
-    }
2055
-
2056
-
2057
-    /**
2058
-     * _setup_primary_registrant_prior_to_payment
2059
-     * ensures that the primary registrant has a valid attendee object created with the critical details populated
2060
-     * (first & last name & email) and that both the transaction object and primary registration object have been saved
2061
-     * plz note that any other registrations will NOT be saved at this point (because they may not have any details
2062
-     * yet)
2063
-     *
2064
-     * @return bool
2065
-     * @throws EE_Error
2066
-     * @throws InvalidArgumentException
2067
-     * @throws ReflectionException
2068
-     * @throws RuntimeException
2069
-     * @throws InvalidDataTypeException
2070
-     * @throws InvalidInterfaceException
2071
-     */
2072
-    private function _setup_primary_registrant_prior_to_payment()
2073
-    {
2074
-        // check if transaction has a primary registrant and that it has a related Attendee object
2075
-        // if not, then we need to at least gather some primary registrant data before attempting payment
2076
-        if (
2077
-            $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
2078
-            && ! $this->checkout->transaction_has_primary_registrant()
2079
-            && ! $this->_capture_primary_registration_data_from_billing_form()
2080
-        ) {
2081
-            return false;
2082
-        }
2083
-        // because saving an object clears its cache, we need to do the Chevy Shuffle
2084
-        // grab the primary_registration object
2085
-        $primary_registration = $this->checkout->transaction->primary_registration();
2086
-        // at this point we'll consider a TXN to not have been failed
2087
-        $this->checkout->transaction->toggle_failed_transaction_status();
2088
-        // save the TXN ( which clears cached copy of primary_registration)
2089
-        $this->checkout->transaction->save();
2090
-        // grab TXN ID and save it to the primary_registration
2091
-        $primary_registration->set_transaction_id($this->checkout->transaction->ID());
2092
-        // save what we have so far
2093
-        $primary_registration->save();
2094
-        return true;
2095
-    }
2096
-
2097
-
2098
-    /**
2099
-     * Captures primary registration data from the billing form.
2100
-     *
2101
-     * This method is used to gather the primary registrant data before attempting payment.
2102
-     * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2103
-     * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2104
-     *
2105
-     * @return bool
2106
-     * @throws EE_Error
2107
-     * @throws InvalidArgumentException
2108
-     * @throws ReflectionException
2109
-     * @throws InvalidDataTypeException
2110
-     * @throws InvalidInterfaceException
2111
-     */
2112
-    private function _capture_primary_registration_data_from_billing_form(): bool
2113
-    {
2114
-        $primary_registration = $this->checkout->transaction->primary_registration();
2115
-        if (! $this->validatePrimaryRegistration($primary_registration)) {
2116
-            return false;
2117
-        }
2118
-
2119
-        $primary_attendee = $this->getPrimaryAttendee($primary_registration);
2120
-        if (! $this->validatePrimaryAttendee($primary_attendee)) {
2121
-            return false;
2122
-        }
2123
-
2124
-        if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2125
-            return false;
2126
-        }
2127
-        // both the primary registration and primary attendee objects should be valid entities at this point
2128
-        $this->checkout->primary_attendee_obj = $primary_attendee;
2129
-
2130
-        /** @type EE_Registration_Processor $registration_processor */
2131
-        $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2132
-        // at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2133
-        $registration_processor->toggle_incomplete_registration_status_to_default(
2134
-            $primary_registration,
2135
-            false,
2136
-            new Context(
2137
-                __METHOD__,
2138
-                esc_html__(
2139
-                    'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2140
-                    'event_espresso'
2141
-                )
2142
-            )
2143
-        );
2144
-        return true;
2145
-    }
2146
-
2147
-
2148
-    /**
2149
-     * returns true if the primary registration is a valid entity
2150
-     *
2151
-     * @param $primary_registration
2152
-     * @return bool
2153
-     * @throws EE_Error
2154
-     * @since 5.0.21.p
2155
-     */
2156
-    private function validatePrimaryRegistration($primary_registration): bool
2157
-    {
2158
-        if ($primary_registration instanceof EE_Registration) {
2159
-            return true;
2160
-        }
2161
-        EE_Error::add_error(
2162
-            sprintf(
2163
-                esc_html__(
2164
-                    'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2165
-                    'event_espresso'
2166
-                ),
2167
-                '<br/>',
2168
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2169
-            ),
2170
-            __FILE__,
2171
-            __FUNCTION__,
2172
-            __LINE__
2173
-        );
2174
-        return false;
2175
-    }
2176
-
2177
-
2178
-    /**
2179
-     * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2180
-     * if the primary registration does not have an attendee object, then one is created from the billing form info
2181
-     *
2182
-     * @param EE_Registration $primary_registration
2183
-     * @return EE_Attendee|null
2184
-     * @throws EE_Error
2185
-     * @throws ReflectionException
2186
-     * @since 5.0.21.p
2187
-     */
2188
-    private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2189
-    {
2190
-        // if we have a primary registration, then we should have a primary attendee
2191
-        $attendee = $primary_registration->attendee();
2192
-        if ($attendee instanceof EE_Attendee) {
2193
-            return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2194
-        }
2195
-        // if not, then we need to create one from the billing form
2196
-        return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2197
-    }
2198
-
2199
-
2200
-    /**
2201
-     * returns true if the primary attendee is a valid entity
2202
-     *
2203
-     * @param $primary_attendee
2204
-     * @return bool
2205
-     * @throws EE_Error
2206
-     * @since 5.0.21.p
2207
-     */
2208
-    private function validatePrimaryAttendee($primary_attendee): bool
2209
-    {
2210
-        if ($primary_attendee instanceof EE_Attendee) {
2211
-            return true;
2212
-        }
2213
-        EE_Error::add_error(
2214
-            sprintf(
2215
-                esc_html__(
2216
-                    'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2217
-                    'event_espresso'
2218
-                ),
2219
-                '<br/>',
2220
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2221
-            ),
2222
-            __FILE__,
2223
-            __FUNCTION__,
2224
-            __LINE__
2225
-        );
2226
-        return false;
2227
-    }
2228
-
2229
-
2230
-    /**
2231
-     * returns true if the attendee was successfully added to the primary registration
2232
-     *
2233
-     * @param EE_Attendee     $primary_attendee
2234
-     * @param EE_Registration $primary_registration
2235
-     * @return bool
2236
-     * @throws EE_Error
2237
-     * @throws ReflectionException
2238
-     * @since 5.0.21.p
2239
-     */
2240
-    private function addAttendeeToPrimaryRegistration(
2241
-        EE_Attendee $primary_attendee,
2242
-        EE_Registration $primary_registration
2243
-    ): bool {
2244
-        // ensure attendee has an ID by saving
2245
-        $primary_attendee->save();
2246
-
2247
-        // compare attendee IDs
2248
-        if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2249
-            return true;
2250
-        }
2251
-
2252
-        $primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2253
-        if ($primary_attendee instanceof EE_Attendee) {
2254
-            return true;
2255
-        }
2256
-
2257
-        EE_Error::add_error(
2258
-            sprintf(
2259
-                esc_html__(
2260
-                    'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2261
-                    'event_espresso'
2262
-                ),
2263
-                '<br/>',
2264
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2265
-            ),
2266
-            __FILE__,
2267
-            __FUNCTION__,
2268
-            __LINE__
2269
-        );
2270
-        return false;
2271
-    }
2272
-
2273
-
2274
-    /**
2275
-     * _get_payment_method_for_selected_method_of_payment
2276
-     * retrieves a valid payment method
2277
-     *
2278
-     * @return EE_Payment_Method
2279
-     * @throws EE_Error
2280
-     * @throws InvalidArgumentException
2281
-     * @throws ReflectionException
2282
-     * @throws InvalidDataTypeException
2283
-     * @throws InvalidInterfaceException
2284
-     */
2285
-    private function _get_payment_method_for_selected_method_of_payment()
2286
-    {
2287
-        if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2288
-            $this->_redirect_because_event_sold_out();
2289
-            return null;
2290
-        }
2291
-        // get EE_Payment_Method object
2292
-        if (isset($this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ])) {
2293
-            $payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ];
2294
-        } else {
2295
-            // load EEM_Payment_Method
2296
-            EE_Registry::instance()->load_model('Payment_Method');
2297
-            $EEM_Payment_Method = EEM_Payment_Method::instance();
2298
-            $payment_method     = $EEM_Payment_Method->get_one_by_slug($this->checkout->selected_method_of_payment);
2299
-        }
2300
-        // verify $payment_method
2301
-        if (! $payment_method instanceof EE_Payment_Method) {
2302
-            // not a payment
2303
-            EE_Error::add_error(
2304
-                sprintf(
2305
-                    esc_html__(
2306
-                        'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2307
-                        'event_espresso'
2308
-                    ),
2309
-                    '<br/>',
2310
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2311
-                ),
2312
-                __FILE__,
2313
-                __FUNCTION__,
2314
-                __LINE__
2315
-            );
2316
-            return null;
2317
-        }
2318
-        // and verify it has a valid Payment_Method Type object
2319
-        if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2320
-            // not a payment
2321
-            EE_Error::add_error(
2322
-                sprintf(
2323
-                    esc_html__(
2324
-                        'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2325
-                        'event_espresso'
2326
-                    ),
2327
-                    '<br/>',
2328
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2329
-                ),
2330
-                __FILE__,
2331
-                __FUNCTION__,
2332
-                __LINE__
2333
-            );
2334
-            return null;
2335
-        }
2336
-        return $payment_method;
2337
-    }
2338
-
2339
-
2340
-    /**
2341
-     *    _attempt_payment
2342
-     *
2343
-     * @access    private
2344
-     * @type    EE_Payment_Method $payment_method
2345
-     * @return EE_Payment|null
2346
-     * @throws EE_Error
2347
-     * @throws InvalidArgumentException
2348
-     * @throws ReflectionException
2349
-     * @throws InvalidDataTypeException
2350
-     * @throws InvalidInterfaceException
2351
-     */
2352
-    private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2353
-    {
2354
-        $this->checkout->transaction->save();
2355
-        /** @var PaymentProcessor $payment_processor */
2356
-        $payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2357
-        if (! $payment_processor instanceof PaymentProcessor) {
2358
-            return null;
2359
-        }
2360
-        /** @var EE_Transaction_Processor $transaction_processor */
2361
-        $transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2362
-        if ($transaction_processor instanceof EE_Transaction_Processor) {
2363
-            $transaction_processor->set_revisit($this->checkout->revisit);
2364
-        }
2365
-        try {
2366
-            // generate payment object
2367
-            return $payment_processor->processPayment(
2368
-                $payment_method,
2369
-                $this->checkout->transaction,
2370
-                $this->checkout->billing_form instanceof EE_Billing_Info_Form
2371
-                    ? $this->checkout->billing_form
2372
-                    : null,
2373
-                $this->checkout->amount_owing,
2374
-                $this->checkout->admin_request,
2375
-                true,
2376
-                $this->_get_return_url($payment_method),
2377
-                $this->reg_step_url()
2378
-            );
2379
-        } catch (Exception $e) {
2380
-            $this->_handle_payment_processor_exception($e);
2381
-        }
2382
-        return null;
2383
-    }
2384
-
2385
-
2386
-    /**
2387
-     * _handle_payment_processor_exception
2388
-     *
2389
-     * @param Exception $e
2390
-     * @return void
2391
-     * @throws EE_Error
2392
-     * @throws InvalidArgumentException
2393
-     * @throws InvalidDataTypeException
2394
-     * @throws InvalidInterfaceException
2395
-     */
2396
-    protected function _handle_payment_processor_exception(Exception $e)
2397
-    {
2398
-        EE_Error::add_error(
2399
-            sprintf(
2400
-                esc_html__(
2401
-                    'The payment could not br processed due to a technical issue.%1$sPlease try again or contact %2$s for assistance.||The following Exception was thrown in %4$s on line %5$s:%1$s%3$s',
2402
-                    'event_espresso'
2403
-                ),
2404
-                '<br/>',
2405
-                EE_Registry::instance()->CFG->organization->get_pretty('email'),
2406
-                $e->getMessage(),
2407
-                $e->getFile(),
2408
-                $e->getLine()
2409
-            ),
2410
-            __FILE__,
2411
-            __FUNCTION__,
2412
-            __LINE__
2413
-        );
2414
-    }
2415
-
2416
-
2417
-    /**
2418
-     * @param EE_Payment_Method $payment_method
2419
-     * @return string
2420
-     * @throws EE_Error
2421
-     * @throws ReflectionException
2422
-     */
2423
-    protected function _get_return_url(EE_Payment_Method $payment_method)
2424
-    {
2425
-        switch ($payment_method->type_obj()->payment_occurs()) {
2426
-            case EE_PMT_Base::offsite:
2427
-                return add_query_arg(
2428
-                    [
2429
-                        'action'                     => 'process_gateway_response',
2430
-                        'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2431
-                        'spco_txn'                   => $this->checkout->transaction->ID(),
2432
-                    ],
2433
-                    $this->reg_step_url()
2434
-                );
2435
-
2436
-            case EE_PMT_Base::onsite:
2437
-            case EE_PMT_Base::offline:
2438
-                return $this->checkout->next_step->reg_step_url();
2439
-        }
2440
-        return '';
2441
-    }
2442
-
2443
-
2444
-    /**
2445
-     * _validate_payment
2446
-     *
2447
-     * @param EE_Payment $payment
2448
-     * @return EE_Payment|bool
2449
-     * @throws EE_Error
2450
-     * @throws InvalidArgumentException
2451
-     * @throws InvalidDataTypeException
2452
-     * @throws InvalidInterfaceException
2453
-     */
2454
-    private function _validate_payment($payment = null)
2455
-    {
2456
-        if ($this->checkout->payment_method->is_off_line()) {
2457
-            return true;
2458
-        }
2459
-        // verify payment object
2460
-        if (! $payment instanceof EE_Payment) {
2461
-            // not a payment
2462
-            EE_Error::add_error(
2463
-                sprintf(
2464
-                    esc_html__(
2465
-                        'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2466
-                        'event_espresso'
2467
-                    ),
2468
-                    '<br/>',
2469
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2470
-                ),
2471
-                __FILE__,
2472
-                __FUNCTION__,
2473
-                __LINE__
2474
-            );
2475
-            return false;
2476
-        }
2477
-        return $payment;
2478
-    }
2479
-
2480
-
2481
-    /**
2482
-     * _post_payment_processing
2483
-     *
2484
-     * @param EE_Payment|bool $payment
2485
-     * @return bool|EE_Payment
2486
-     * @throws EE_Error
2487
-     * @throws InvalidArgumentException
2488
-     * @throws InvalidDataTypeException
2489
-     * @throws InvalidInterfaceException
2490
-     * @throws ReflectionException
2491
-     */
2492
-    private function _post_payment_processing($payment = null)
2493
-    {
2494
-        // Off-Line payment?
2495
-        if ($payment === true) {
2496
-            return true;
2497
-        }
2498
-        if ($payment instanceof EE_Payment) {
2499
-            // Should the user be redirected?
2500
-            if ($payment->redirect_url()) {
2501
-                do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2502
-                $this->checkout->redirect      = true;
2503
-                $this->checkout->redirect_form = $payment->redirect_form();
2504
-                $this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2505
-                // set JSON response
2506
-                $this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2507
-                // and lastly, let's bump the payment status to pending
2508
-                $payment->set_status(EEM_Payment::status_id_pending);
2509
-                $payment->save();
2510
-            } elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2511
-                // User shouldn't be redirected. So let's process it here.
2512
-                // $this->_setup_redirect_for_next_step();
2513
-                $this->checkout->continue_reg = false;
2514
-            }
2515
-            return $payment;
2516
-        }
2517
-        // ummm ya... not Off-Line, not On-Site, not off-Site ????
2518
-        $this->checkout->continue_reg = false;
2519
-        return false;
2520
-    }
2521
-
2522
-
2523
-    /**
2524
-     *    _process_payment_status
2525
-     *
2526
-     * @type    EE_Payment $payment
2527
-     * @param string       $payment_occurs
2528
-     * @return bool
2529
-     * @throws EE_Error
2530
-     * @throws InvalidArgumentException
2531
-     * @throws InvalidDataTypeException
2532
-     * @throws InvalidInterfaceException
2533
-     */
2534
-    private function _process_payment_status($payment, $payment_occurs = EE_PMT_Base::offline)
2535
-    {
2536
-        // off-line payment? carry on
2537
-        if ($payment_occurs === EE_PMT_Base::offline) {
2538
-            return true;
2539
-        }
2540
-        // verify payment validity
2541
-        if ($payment instanceof EE_Payment) {
2542
-            do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2543
-            $msg = $payment->gateway_response();
2544
-            // check results
2545
-            switch ($payment->status()) {
2546
-                // good payment
2547
-                case EEM_Payment::status_id_approved:
2548
-                    EE_Error::add_success(
2549
-                        esc_html__('Your payment was processed successfully.', 'event_espresso'),
2550
-                        __FILE__,
2551
-                        __FUNCTION__,
2552
-                        __LINE__
2553
-                    );
2554
-                    return true;
2555
-                // slow payment
2556
-                case EEM_Payment::status_id_pending:
2557
-                    if (empty($msg)) {
2558
-                        $msg = esc_html__(
2559
-                            'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2560
-                            'event_espresso'
2561
-                        );
2562
-                    }
2563
-                    EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2564
-                    return true;
2565
-                // don't wanna payment
2566
-                case EEM_Payment::status_id_cancelled:
2567
-                    if (empty($msg)) {
2568
-                        $msg = _n(
2569
-                            'Payment cancelled. Please try again.',
2570
-                            'Payment cancelled. Please try again or select another method of payment.',
2571
-                            count($this->checkout->available_payment_methods),
2572
-                            'event_espresso'
2573
-                        );
2574
-                    }
2575
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2576
-                    return false;
2577
-                // not enough payment
2578
-                case EEM_Payment::status_id_declined:
2579
-                    if (empty($msg)) {
2580
-                        $msg = _n(
2581
-                            'We\'re sorry but your payment was declined. Please try again.',
2582
-                            'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2583
-                            count($this->checkout->available_payment_methods),
2584
-                            'event_espresso'
2585
-                        );
2586
-                    }
2587
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2588
-                    return false;
2589
-                // bad payment
2590
-                case EEM_Payment::status_id_failed:
2591
-                    if (! empty($msg)) {
2592
-                        EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2593
-                        return false;
2594
-                    }
2595
-                    // default to error below
2596
-                    break;
2597
-            }
2598
-        }
2599
-        // off-site payment gateway responses are too unreliable, so let's just assume that
2600
-        // the payment processing is just running slower than the registrant's request
2601
-        if ($payment_occurs === EE_PMT_Base::offsite) {
2602
-            return true;
2603
-        }
2604
-        EE_Error::add_error(
2605
-            sprintf(
2606
-                esc_html__(
2607
-                    'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2608
-                    'event_espresso'
2609
-                ),
2610
-                '<br/>',
2611
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2612
-            ),
2613
-            __FILE__,
2614
-            __FUNCTION__,
2615
-            __LINE__
2616
-        );
2617
-        return false;
2618
-    }
2619
-
2620
-
2621
-
2622
-
2623
-
2624
-
2625
-    /********************************************************************************************************/
2626
-    /**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2627
-    /********************************************************************************************************/
2628
-    /**
2629
-     * process_gateway_response
2630
-     * this is the return point for Off-Site Payment Methods
2631
-     * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2632
-     * otherwise, it will load up the last payment made for the TXN.
2633
-     * If the payment retrieved looks good, it will then either:
2634
-     *    complete the current step and allow advancement to the next reg step
2635
-     *        or present the payment options again
2636
-     *
2637
-     * @return bool
2638
-     * @throws EE_Error
2639
-     * @throws InvalidArgumentException
2640
-     * @throws ReflectionException
2641
-     * @throws InvalidDataTypeException
2642
-     * @throws InvalidInterfaceException
2643
-     */
2644
-    public function process_gateway_response()
2645
-    {
2646
-        // how have they chosen to pay?
2647
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2648
-        // get EE_Payment_Method object
2649
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2650
-            $this->checkout->continue_reg = false;
2651
-            return false;
2652
-        }
2653
-        if (! $this->checkout->payment_method->is_off_site()) {
2654
-            return false;
2655
-        }
2656
-        $this->_validate_offsite_return();
2657
-        // verify TXN
2658
-        if ($this->checkout->transaction instanceof EE_Transaction) {
2659
-            $gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2660
-            if (! $gateway instanceof EE_Offsite_Gateway) {
2661
-                $this->checkout->continue_reg = false;
2662
-                return false;
2663
-            }
2664
-            $payment = $this->_process_off_site_payment($gateway);
2665
-            $payment = $this->_process_cancelled_payments($payment);
2666
-            $payment = $this->_validate_payment($payment);
2667
-            // if payment was not declined by the payment gateway or cancelled by the registrant
2668
-            if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2669
-                // $this->_setup_redirect_for_next_step();
2670
-                // store that for later
2671
-                $this->checkout->payment = $payment;
2672
-                // mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2673
-                // because we will complete this step during the IPN processing then
2674
-                if (! $this->handle_IPN_in_this_request()) {
2675
-                    $this->set_completed();
2676
-                }
2677
-                return true;
2678
-            }
2679
-        }
2680
-        // DEBUG LOG
2681
-        // $this->checkout->log(
2682
-        //     __CLASS__,
2683
-        //     __FUNCTION__,
2684
-        //     __LINE__,
2685
-        //     array('payment' => $payment)
2686
-        // );
2687
-        $this->checkout->continue_reg = false;
2688
-        return false;
2689
-    }
2690
-
2691
-
2692
-    /**
2693
-     * _validate_return
2694
-     *
2695
-     * @return void
2696
-     * @throws EE_Error
2697
-     * @throws InvalidArgumentException
2698
-     * @throws InvalidDataTypeException
2699
-     * @throws InvalidInterfaceException
2700
-     * @throws ReflectionException
2701
-     */
2702
-    private function _validate_offsite_return()
2703
-    {
2704
-        $TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2705
-        if ($TXN_ID !== $this->checkout->transaction->ID()) {
2706
-            // Houston... we might have a problem
2707
-            $invalid_TXN = false;
2708
-            // first gather some info
2709
-            $valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2710
-            $primary_registrant = $valid_TXN instanceof EE_Transaction
2711
-                ? $valid_TXN->primary_registration()
2712
-                : null;
2713
-            // let's start by retrieving the cart for this TXN
2714
-            $cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2715
-            if ($cart instanceof EE_Cart) {
2716
-                // verify that the current cart has tickets
2717
-                $tickets = $cart->get_tickets();
2718
-                if (empty($tickets)) {
2719
-                    $invalid_TXN = true;
2720
-                }
2721
-            } else {
2722
-                $invalid_TXN = true;
2723
-            }
2724
-            $valid_TXN_SID = $primary_registrant instanceof EE_Registration
2725
-                ? $primary_registrant->session_ID()
2726
-                : null;
2727
-            // validate current Session ID and compare against valid TXN session ID
2728
-            if (
2729
-                $invalid_TXN // if this is already true, then skip other checks
2730
-                || EE_Session::instance()->id() === null
2731
-                || (
2732
-                    // WARNING !!!
2733
-                    // this could be PayPal sending back duplicate requests (ya they do that)
2734
-                    // or it **could** mean someone is simply registering AGAIN after having just done so,
2735
-                    // so now we need to determine if this current TXN looks valid or not
2736
-                    // and whether this reg step has even been started ?
2737
-                    EE_Session::instance()->id() === $valid_TXN_SID
2738
-                    // really? you're halfway through this reg step, but you never started it ?
2739
-                    && $this->checkout->transaction->reg_step_completed($this->slug()) === false
2740
-                )
2741
-            ) {
2742
-                $invalid_TXN = true;
2743
-            }
2744
-            if ($invalid_TXN) {
2745
-                // is the valid TXN completed ?
2746
-                if ($valid_TXN instanceof EE_Transaction) {
2747
-                    // has this step even been started ?
2748
-                    $reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2749
-                    if ($reg_step_completed !== false && $reg_step_completed !== true) {
2750
-                        // so it **looks** like this is a double request from PayPal
2751
-                        // so let's try to pick up where we left off
2752
-                        $this->checkout->transaction = $valid_TXN;
2753
-                        $this->checkout->refresh_all_entities(true);
2754
-                        return;
2755
-                    }
2756
-                }
2757
-                // you appear to be lost?
2758
-                $this->_redirect_wayward_request($primary_registrant);
2759
-            }
2760
-        }
2761
-    }
2762
-
2763
-
2764
-    /**
2765
-     * _redirect_wayward_request
2766
-     *
2767
-     * @param EE_Registration|null $primary_registrant
2768
-     * @return void
2769
-     * @throws EE_Error
2770
-     * @throws InvalidArgumentException
2771
-     * @throws InvalidDataTypeException
2772
-     * @throws InvalidInterfaceException
2773
-     * @throws ReflectionException
2774
-     */
2775
-    private function _redirect_wayward_request(EE_Registration $primary_registrant)
2776
-    {
2777
-        if (! $primary_registrant instanceof EE_Registration) {
2778
-            // try redirecting based on the current TXN
2779
-            $primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2780
-                ? $this->checkout->transaction->primary_registration()
2781
-                : null;
2782
-        }
2783
-        if (! $primary_registrant instanceof EE_Registration) {
2784
-            EE_Error::add_error(
2785
-                sprintf(
2786
-                    esc_html__(
2787
-                        'Invalid information was received from the Off-Site Payment Processor and your Transaction details could not be retrieved from the database.%1$sPlease try again or contact %2$s for assistance.',
2788
-                        'event_espresso'
2789
-                    ),
2790
-                    '<br/>',
2791
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2792
-                ),
2793
-                __FILE__,
2794
-                __FUNCTION__,
2795
-                __LINE__
2796
-            );
2797
-            return;
2798
-        }
2799
-        // make sure transaction is not locked
2800
-        $this->checkout->transaction->unlock();
2801
-        wp_safe_redirect(
2802
-            add_query_arg(
2803
-                [
2804
-                    'e_reg_url_link' => $primary_registrant->reg_url_link(),
2805
-                ],
2806
-                $this->checkout->thank_you_page_url
2807
-            )
2808
-        );
2809
-        exit();
2810
-    }
2811
-
2812
-
2813
-    /**
2814
-     * _process_off_site_payment
2815
-     *
2816
-     * @param EE_Offsite_Gateway $gateway
2817
-     * @return EE_Payment
2818
-     * @throws EE_Error
2819
-     * @throws InvalidArgumentException
2820
-     * @throws InvalidDataTypeException
2821
-     * @throws InvalidInterfaceException
2822
-     * @throws ReflectionException
2823
-     */
2824
-    private function _process_off_site_payment(EE_Offsite_Gateway $gateway)
2825
-    {
2826
-        try {
2827
-            $request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2828
-            $request_data = $request->requestParams();
2829
-            // if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2830
-            $this->set_handle_IPN_in_this_request(
2831
-                $gateway->handle_IPN_in_this_request($request_data, false)
2832
-            );
2833
-            if ($this->handle_IPN_in_this_request()) {
2834
-                // get payment details and process results
2835
-                /** @var IpnHandler $payment_processor */
2836
-                $payment_processor = LoaderFactory::getShared(IpnHandler::class);
2837
-                $payment           = $payment_processor->processIPN(
2838
-                    $request_data,
2839
-                    $this->checkout->transaction,
2840
-                    $this->checkout->payment_method,
2841
-                    true,
2842
-                    false
2843
-                );
2844
-                // $payment_source = 'process_ipn';
2845
-            } else {
2846
-                $payment = $this->checkout->transaction->last_payment();
2847
-                // $payment_source = 'last_payment';
2848
-            }
2849
-        } catch (Exception $e) {
2850
-            // let's just eat the exception and try to move on using any previously set payment info
2851
-            $payment = $this->checkout->transaction->last_payment();
2852
-            // $payment_source = 'last_payment after Exception';
2853
-            // but if we STILL don't have a payment object
2854
-            if (! $payment instanceof EE_Payment) {
2855
-                // then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2856
-                $this->_handle_payment_processor_exception($e);
2857
-            }
2858
-        }
2859
-        return $payment;
2860
-    }
2861
-
2862
-
2863
-    /**
2864
-     * _process_cancelled_payments
2865
-     * just makes sure that the payment status gets updated correctly
2866
-     * so tha tan error isn't generated during payment validation
2867
-     *
2868
-     * @param EE_Payment $payment
2869
-     * @return EE_Payment|null
2870
-     * @throws EE_Error
2871
-     */
2872
-    private function _process_cancelled_payments($payment = null)
2873
-    {
2874
-        if (
2875
-            $payment instanceof EE_Payment
2876
-            && $this->request->requestParamIsSet('ee_cancel_payment')
2877
-            && $payment->status() === EEM_Payment::status_id_failed
2878
-        ) {
2879
-            $payment->set_status(EEM_Payment::status_id_cancelled);
2880
-        }
2881
-        return $payment;
2882
-    }
2883
-
2884
-
2885
-    /**
2886
-     *    get_transaction_details_for_gateways
2887
-     *
2888
-     * @access    public
2889
-     * @return void
2890
-     * @throws EE_Error
2891
-     * @throws InvalidArgumentException
2892
-     * @throws ReflectionException
2893
-     * @throws InvalidDataTypeException
2894
-     * @throws InvalidInterfaceException
2895
-     */
2896
-    public function get_transaction_details_for_gateways()
2897
-    {
2898
-        $txn_details = [];
2899
-        // ya gotta make a choice man
2900
-        if (empty($this->checkout->selected_method_of_payment)) {
2901
-            $txn_details = [
2902
-                'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2903
-            ];
2904
-        }
2905
-        // get EE_Payment_Method object
2906
-        if (
2907
-            empty($txn_details)
2908
-            && ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2909
-        ) {
2910
-            $txn_details = [
2911
-                'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2912
-                'error'                      => esc_html__(
2913
-                    'A valid Payment Method could not be determined.',
2914
-                    'event_espresso'
2915
-                ),
2916
-            ];
2917
-        }
2918
-        if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2919
-            $return_url  = $this->_get_return_url($this->checkout->payment_method);
2920
-            $txn_details = [
2921
-                'TXN_ID'         => $this->checkout->transaction->ID(),
2922
-                'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2923
-                'TXN_total'      => $this->checkout->transaction->total(),
2924
-                'TXN_paid'       => $this->checkout->transaction->paid(),
2925
-                'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2926
-                'STS_ID'         => $this->checkout->transaction->status_ID(),
2927
-                'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2928
-                'payment_amount' => $this->checkout->amount_owing,
2929
-                'return_url'     => $return_url,
2930
-                'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2931
-                'notify_url'     => EE_Config::instance()->core->txn_page_url(
2932
-                    [
2933
-                        'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2934
-                        'ee_payment_method' => $this->checkout->payment_method->slug(),
2935
-                    ]
2936
-                ),
2937
-            ];
2938
-        }
2939
-        echo wp_json_encode($txn_details);
2940
-        exit();
2941
-    }
2942
-
2943
-
2944
-    /**
2945
-     *    __sleep
2946
-     * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2947
-     * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2948
-     * reg form, because if needed, it will be regenerated anyways
2949
-     *
2950
-     * @return array
2951
-     */
2952
-    public function __sleep()
2953
-    {
2954
-        // remove the reg form and the checkout
2955
-        return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2956
-    }
26
+	/**
27
+	 * @var EE_Line_Item_Display $Line_Item_Display
28
+	 */
29
+	protected $line_item_display;
30
+
31
+	/**
32
+	 * @var boolean $handle_IPN_in_this_request
33
+	 */
34
+	protected $handle_IPN_in_this_request = false;
35
+
36
+
37
+	/**
38
+	 *    set_hooks - for hooking into EE Core, other modules, etc
39
+	 *
40
+	 * @access    public
41
+	 * @return    void
42
+	 */
43
+	public static function set_hooks()
44
+	{
45
+		add_filter(
46
+			'FHEE__SPCO__EE_Line_Item_Filter_Collection',
47
+			['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
48
+		);
49
+		add_action(
50
+			'wp_ajax_switch_spco_billing_form',
51
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
52
+		);
53
+		add_action(
54
+			'wp_ajax_nopriv_switch_spco_billing_form',
55
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
56
+		);
57
+		add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
58
+		add_action(
59
+			'wp_ajax_nopriv_save_payer_details',
60
+			['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
61
+		);
62
+		add_action(
63
+			'wp_ajax_get_transaction_details_for_gateways',
64
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
65
+		);
66
+		add_action(
67
+			'wp_ajax_nopriv_get_transaction_details_for_gateways',
68
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
69
+		);
70
+		add_filter(
71
+			'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
72
+			['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
73
+		);
74
+	}
75
+
76
+
77
+	/**
78
+	 *    ajax switch_spco_billing_form
79
+	 *
80
+	 */
81
+	public static function switch_spco_billing_form()
82
+	{
83
+		EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
84
+	}
85
+
86
+
87
+	/**
88
+	 *    ajax save_payer_details
89
+	 *
90
+	 */
91
+	public static function save_payer_details()
92
+	{
93
+		EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
94
+	}
95
+
96
+
97
+	/**
98
+	 *    ajax get_transaction_details
99
+	 *
100
+	 */
101
+	public static function get_transaction_details()
102
+	{
103
+		EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
104
+	}
105
+
106
+
107
+	/**
108
+	 * bypass_recaptcha_for_load_payment_method
109
+	 *
110
+	 * @access public
111
+	 * @return array
112
+	 * @throws InvalidArgumentException
113
+	 * @throws InvalidDataTypeException
114
+	 * @throws InvalidInterfaceException
115
+	 */
116
+	public static function bypass_recaptcha_for_load_payment_method()
117
+	{
118
+		return [
119
+			'EESID'  => EE_Registry::instance()->SSN->id(),
120
+			'step'   => 'payment_options',
121
+			'action' => 'spco_billing_form',
122
+		];
123
+	}
124
+
125
+
126
+	/**
127
+	 *    class constructor
128
+	 *
129
+	 * @access    public
130
+	 * @param EE_Checkout $checkout
131
+	 */
132
+	public function __construct(EE_Checkout $checkout)
133
+	{
134
+		$this->request   = EED_Single_Page_Checkout::getRequest();
135
+		$this->_slug     = 'payment_options';
136
+		$this->_name     = esc_html__('Payment Options', 'event_espresso');
137
+		$this->_template = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
138
+		$this->checkout  = $checkout;
139
+		$this->_reset_success_message();
140
+		$this->set_instructions(
141
+			esc_html__(
142
+				'Please select a method of payment and provide any necessary billing information before proceeding.',
143
+				'event_espresso'
144
+			)
145
+		);
146
+	}
147
+
148
+
149
+	/**
150
+	 * @return null
151
+	 */
152
+	public function line_item_display()
153
+	{
154
+		return $this->line_item_display;
155
+	}
156
+
157
+
158
+	/**
159
+	 * @param null $line_item_display
160
+	 */
161
+	public function set_line_item_display($line_item_display)
162
+	{
163
+		$this->line_item_display = $line_item_display;
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return boolean
169
+	 */
170
+	public function handle_IPN_in_this_request()
171
+	{
172
+		return $this->handle_IPN_in_this_request;
173
+	}
174
+
175
+
176
+	/**
177
+	 * @param boolean $handle_IPN_in_this_request
178
+	 */
179
+	public function set_handle_IPN_in_this_request($handle_IPN_in_this_request)
180
+	{
181
+		$this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
182
+	}
183
+
184
+
185
+	/**
186
+	 * translate_js_strings
187
+	 *
188
+	 * @return void
189
+	 */
190
+	public function translate_js_strings()
191
+	{
192
+		EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
193
+			'Please select a method of payment in order to continue.',
194
+			'event_espresso'
195
+		);
196
+		EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
197
+			'A valid method of payment could not be determined. Please refresh the page and try again.',
198
+			'event_espresso'
199
+		);
200
+		EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
201
+			'Forwarding to Secure Payment Provider.',
202
+			'event_espresso'
203
+		);
204
+	}
205
+
206
+
207
+	/**
208
+	 * enqueue_styles_and_scripts
209
+	 *
210
+	 * @return void
211
+	 * @throws EE_Error
212
+	 * @throws InvalidArgumentException
213
+	 * @throws InvalidDataTypeException
214
+	 * @throws InvalidInterfaceException
215
+	 * @throws ReflectionException
216
+	 */
217
+	public function enqueue_styles_and_scripts()
218
+	{
219
+		$transaction = $this->checkout->transaction;
220
+		// if the transaction isn't set or nothing is owed on it, don't enqueue any JS
221
+		if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
222
+			return;
223
+		}
224
+		foreach (
225
+			EEM_Payment_Method::instance()->get_all_for_transaction(
226
+				$transaction,
227
+				EEM_Payment_Method::scope_cart
228
+			) as $payment_method
229
+		) {
230
+			$type_obj = $payment_method->type_obj();
231
+			if ($type_obj instanceof EE_PMT_Base) {
232
+				$billing_form = $type_obj->generate_new_billing_form($transaction);
233
+				if ($billing_form instanceof EE_Form_Section_Proper) {
234
+					$billing_form->enqueue_js();
235
+				}
236
+			}
237
+		}
238
+	}
239
+
240
+
241
+	/**
242
+	 * initialize_reg_step
243
+	 *
244
+	 * @return bool
245
+	 * @throws EE_Error
246
+	 * @throws InvalidArgumentException
247
+	 * @throws ReflectionException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 */
251
+	public function initialize_reg_step()
252
+	{
253
+		// TODO: if /when we implement donations, then this will need overriding
254
+		if (
255
+			// don't need payment options for:
256
+			// registrations made via the admin
257
+			// completed transactions
258
+			// overpaid transactions
259
+			// $ 0.00 transactions(no payment required)
260
+			! $this->checkout->payment_required()
261
+			// but do NOT remove if current action being called belongs to this reg step
262
+			&& ! is_callable([$this, $this->checkout->action])
263
+			&& ! $this->completed()
264
+		) {
265
+			// and if so, then we no longer need the Payment Options step
266
+			if ($this->is_current_step()) {
267
+				$this->checkout->generate_reg_form = false;
268
+			}
269
+			$this->checkout->remove_reg_step($this->_slug);
270
+			// DEBUG LOG
271
+			// $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
272
+			return false;
273
+		}
274
+		// load EEM_Payment_Method
275
+		EE_Registry::instance()->load_model('Payment_Method');
276
+		// get all active payment methods
277
+		$this->checkout->available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
278
+			$this->checkout->transaction,
279
+			EEM_Payment_Method::scope_cart
280
+		);
281
+		return true;
282
+	}
283
+
284
+
285
+	/**
286
+	 * @return EE_Form_Section_Proper
287
+	 * @throws EE_Error
288
+	 * @throws InvalidArgumentException
289
+	 * @throws ReflectionException
290
+	 * @throws EntityNotFoundException
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws InvalidInterfaceException
293
+	 * @throws InvalidStatusException
294
+	 */
295
+	public function generate_reg_form()
296
+	{
297
+		// reset in case someone changes their mind
298
+		$this->_reset_selected_method_of_payment();
299
+		// set some defaults
300
+		$this->checkout->selected_method_of_payment = 'payments_closed';
301
+		$registrations_requiring_payment            = [];
302
+		$registrations_for_free_events              = [];
303
+		$registrations_requiring_pre_approval       = [];
304
+		$sold_out_events                            = [];
305
+		$insufficient_spaces_available              = [];
306
+		$no_payment_required                        = true;
307
+		// loop thru registrations to gather info
308
+		$registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
309
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
310
+			$registrations,
311
+			$this->checkout->revisit
312
+		);
313
+		foreach ($registrations as $REG_ID => $registration) {
314
+			/** @var $registration EE_Registration */
315
+			// Skip if the registration has been moved
316
+			if ($registration->wasMoved()) {
317
+				continue;
318
+			}
319
+			// has this registration lost it's space ?
320
+			if (isset($ejected_registrations[ $REG_ID ])) {
321
+				if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
322
+					$sold_out_events[ $registration->event()->ID() ] = $registration->event();
323
+				} else {
324
+					$insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
325
+				}
326
+				continue;
327
+			}
328
+			// event requires admin approval
329
+			if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
330
+				// add event to list of events with pre-approval reg status
331
+				$registrations_requiring_pre_approval[ $REG_ID ] = $registration;
332
+				do_action(
333
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
334
+					$registration->event(),
335
+					$this
336
+				);
337
+				continue;
338
+			}
339
+			if (
340
+				$this->checkout->revisit
341
+				&& $registration->status_ID() !== RegStatus::APPROVED
342
+				&& (
343
+					$registration->event()->is_sold_out()
344
+					|| $registration->event()->is_sold_out(true)
345
+				)
346
+			) {
347
+				// add event to list of events that are sold out
348
+				$sold_out_events[ $registration->event()->ID() ] = $registration->event();
349
+				do_action(
350
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
351
+					$registration->event(),
352
+					$this
353
+				);
354
+				continue;
355
+			}
356
+			// are they allowed to pay now and is there monies owing?
357
+			if ($registration->owes_monies_and_can_pay()) {
358
+				$registrations_requiring_payment[ $REG_ID ] = $registration;
359
+				do_action(
360
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
361
+					$registration->event(),
362
+					$this
363
+				);
364
+			} elseif (
365
+				! $this->checkout->revisit
366
+					  && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
367
+					  && $registration->ticket()->is_free()
368
+			) {
369
+				$registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
370
+			}
371
+		}
372
+		$subsections = [];
373
+		// now decide which template to load
374
+		if (! empty($sold_out_events)) {
375
+			$subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
376
+		}
377
+		if (! empty($insufficient_spaces_available)) {
378
+			$subsections['insufficient_space'] = $this->_insufficient_spaces_available(
379
+				$insufficient_spaces_available
380
+			);
381
+		}
382
+		if (! empty($registrations_requiring_pre_approval)) {
383
+			$subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
384
+				$registrations_requiring_pre_approval
385
+			);
386
+		}
387
+		if (! empty($registrations_for_free_events)) {
388
+			$subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
389
+		}
390
+		if (! empty($registrations_requiring_payment)) {
391
+			if ($this->checkout->amount_owing > 0) {
392
+				// autoload Line_Item_Display classes
393
+				EEH_Autoloader::register_line_item_filter_autoloaders();
394
+				$line_item_filter_processor = new EE_Line_Item_Filter_Processor(
395
+					apply_filters(
396
+						'FHEE__SPCO__EE_Line_Item_Filter_Collection',
397
+						new EE_Line_Item_Filter_Collection()
398
+					),
399
+					$this->checkout->cart->get_grand_total()
400
+				);
401
+				/** @var EE_Line_Item $filtered_line_item_tree */
402
+				$filtered_line_item_tree = $line_item_filter_processor->process();
403
+				EEH_Autoloader::register_line_item_display_autoloaders();
404
+				$this->set_line_item_display(new EE_Line_Item_Display('spco'));
405
+				$subsections['payment_options'] = $this->_display_payment_options(
406
+					$this->line_item_display->display_line_item(
407
+						$filtered_line_item_tree,
408
+						['registrations' => $registrations]
409
+					)
410
+				);
411
+				$this->checkout->amount_owing   = $filtered_line_item_tree->total();
412
+				$this->_apply_registration_payments_to_amount_owing($registrations);
413
+			}
414
+			$no_payment_required = false;
415
+		} else {
416
+			$this->_hide_reg_step_submit_button_if_revisit();
417
+		}
418
+		$this->_save_selected_method_of_payment();
419
+
420
+		$subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
421
+		$subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
422
+
423
+		return new EE_Form_Section_Proper(
424
+			[
425
+				'name'            => $this->reg_form_name(),
426
+				'html_id'         => $this->reg_form_name(),
427
+				'subsections'     => $subsections,
428
+				'layout_strategy' => new EE_No_Layout(),
429
+			]
430
+		);
431
+	}
432
+
433
+
434
+	/**
435
+	 * add line item filters required for this reg step
436
+	 * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
437
+	 *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
438
+	 *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
439
+	 *        payment options reg step, can apply these filters via the following: apply_filters(
440
+	 *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
441
+	 *        filter collection by passing that instead of instantiating a new collection
442
+	 *
443
+	 * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
444
+	 * @return EE_Line_Item_Filter_Collection
445
+	 * @throws EE_Error
446
+	 * @throws InvalidArgumentException
447
+	 * @throws ReflectionException
448
+	 * @throws EntityNotFoundException
449
+	 * @throws InvalidDataTypeException
450
+	 * @throws InvalidInterfaceException
451
+	 * @throws InvalidStatusException
452
+	 */
453
+	public static function add_spco_line_item_filters(EE_Line_Item_Filter_Collection $line_item_filter_collection)
454
+	{
455
+		if (! EE_Registry::instance()->SSN instanceof EE_Session) {
456
+			return $line_item_filter_collection;
457
+		}
458
+		if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
459
+			return $line_item_filter_collection;
460
+		}
461
+		if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
462
+			return $line_item_filter_collection;
463
+		}
464
+		$line_item_filter_collection->add(
465
+			new EE_Billable_Line_Item_Filter(
466
+				EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
467
+					EE_Registry::instance()->SSN->checkout()->transaction->registrations(
468
+						EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
469
+					)
470
+				)
471
+			)
472
+		);
473
+		$line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
474
+		return $line_item_filter_collection;
475
+	}
476
+
477
+
478
+	/**
479
+	 * remove_ejected_registrations
480
+	 * if a registrant has lost their potential space at an event due to lack of payment,
481
+	 * then this method removes them from the list of registrations being paid for during this request
482
+	 *
483
+	 * @param EE_Registration[] $registrations
484
+	 * @return EE_Registration[]
485
+	 * @throws EE_Error
486
+	 * @throws InvalidArgumentException
487
+	 * @throws ReflectionException
488
+	 * @throws EntityNotFoundException
489
+	 * @throws InvalidDataTypeException
490
+	 * @throws InvalidInterfaceException
491
+	 * @throws InvalidStatusException
492
+	 */
493
+	public static function remove_ejected_registrations(array $registrations)
494
+	{
495
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
496
+			$registrations,
497
+			EE_Registry::instance()->SSN->checkout()->revisit
498
+		);
499
+		foreach ($registrations as $REG_ID => $registration) {
500
+			// has this registration lost it's space ?
501
+			if (isset($ejected_registrations[ $REG_ID ])) {
502
+				unset($registrations[ $REG_ID ]);
503
+			}
504
+		}
505
+		return $registrations;
506
+	}
507
+
508
+
509
+	/**
510
+	 * find_registrations_that_lost_their_space
511
+	 * If a registrant chooses an offline payment method like Invoice,
512
+	 * then no space is reserved for them at the event until they fully pay fo that site
513
+	 * (unless the event's default reg status is set to APPROVED)
514
+	 * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
515
+	 * then this method will determine which registrations have lost the ability to complete the reg process.
516
+	 *
517
+	 * @param EE_Registration[] $registrations
518
+	 * @param bool              $revisit
519
+	 * @return array
520
+	 * @throws EE_Error
521
+	 * @throws InvalidArgumentException
522
+	 * @throws ReflectionException
523
+	 * @throws EntityNotFoundException
524
+	 * @throws InvalidDataTypeException
525
+	 * @throws InvalidInterfaceException
526
+	 * @throws InvalidStatusException
527
+	 */
528
+	public static function find_registrations_that_lost_their_space(array $registrations, $revisit = false)
529
+	{
530
+		// registrations per event
531
+		$event_reg_count = [];
532
+		// spaces left per event
533
+		$event_spaces_remaining = [];
534
+		// tickets left sorted by ID
535
+		$tickets_remaining = [];
536
+		// registrations that have lost their space
537
+		$ejected_registrations = [];
538
+		foreach ($registrations as $REG_ID => $registration) {
539
+			if (
540
+				$registration->status_ID() === RegStatus::APPROVED
541
+				|| apply_filters(
542
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
543
+					false,
544
+					$registration,
545
+					$revisit
546
+				)
547
+			) {
548
+				continue;
549
+			}
550
+			$EVT_ID = $registration->event_ID();
551
+			$ticket = $registration->ticket();
552
+			if (! isset($tickets_remaining[ $ticket->ID() ])) {
553
+				$tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
554
+			}
555
+			if ($tickets_remaining[ $ticket->ID() ] > 0) {
556
+				if (! isset($event_reg_count[ $EVT_ID ])) {
557
+					$event_reg_count[ $EVT_ID ] = 0;
558
+				}
559
+				$event_reg_count[ $EVT_ID ]++;
560
+				if (! isset($event_spaces_remaining[ $EVT_ID ])) {
561
+					$event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
562
+				}
563
+			}
564
+			if (
565
+				$revisit
566
+				&& ($tickets_remaining[ $ticket->ID() ] === 0
567
+					|| $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
568
+				)
569
+			) {
570
+				$ejected_registrations[ $REG_ID ] = $registration->event();
571
+				if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
572
+					/** @type EE_Registration_Processor $registration_processor */
573
+					$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
574
+					// at this point, we should have enough details about the registrant to consider the registration
575
+					// NOT incomplete
576
+					$registration_processor->manually_update_registration_status(
577
+						$registration,
578
+						RegStatus::WAIT_LIST
579
+					);
580
+				}
581
+			}
582
+		}
583
+		return $ejected_registrations;
584
+	}
585
+
586
+
587
+	/**
588
+	 * _hide_reg_step_submit_button
589
+	 * removes the html for the reg step submit button
590
+	 * by replacing it with an empty string via filter callback
591
+	 *
592
+	 * @return void
593
+	 */
594
+	protected function _adjust_registration_status_if_event_old_sold()
595
+	{
596
+	}
597
+
598
+
599
+	/**
600
+	 * _hide_reg_step_submit_button
601
+	 * removes the html for the reg step submit button
602
+	 * by replacing it with an empty string via filter callback
603
+	 *
604
+	 * @return void
605
+	 */
606
+	protected function _hide_reg_step_submit_button_if_revisit()
607
+	{
608
+		if ($this->checkout->revisit) {
609
+			add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
610
+		}
611
+	}
612
+
613
+
614
+	/**
615
+	 * sold_out_events
616
+	 * displays notices regarding events that have sold out since hte registrant first signed up
617
+	 *
618
+	 * @param EE_Event[] $sold_out_events_array
619
+	 * @return EE_Form_Section_Proper
620
+	 * @throws EE_Error
621
+	 */
622
+	private function _sold_out_events($sold_out_events_array = [])
623
+	{
624
+		// set some defaults
625
+		$this->checkout->selected_method_of_payment = 'events_sold_out';
626
+		$sold_out_events                            = '';
627
+		foreach ($sold_out_events_array as $sold_out_event) {
628
+			$sold_out_events .= EEH_HTML::li(
629
+				EEH_HTML::span(
630
+					'  ' . $sold_out_event->name(),
631
+					'',
632
+					'dashicons dashicons-marker ee-icon-size-16 pink-text'
633
+				)
634
+			);
635
+		}
636
+		return new EE_Form_Section_Proper(
637
+			[
638
+				'layout_strategy' => new EE_Template_Layout(
639
+					[
640
+						'layout_template_file' => SPCO_REG_STEPS_PATH
641
+												  . $this->_slug
642
+												  . '/sold_out_events.template.php',
643
+						'template_args'        => apply_filters(
644
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
645
+							[
646
+								'sold_out_events'     => $sold_out_events,
647
+								'sold_out_events_msg' => apply_filters(
648
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
649
+									sprintf(
650
+										esc_html__(
651
+											'It appears that the event you were about to make a payment for has sold out since you first registered. If you have already made a partial payment towards this event, please contact the event administrator for a refund.%3$s%3$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%2$s',
652
+											'event_espresso'
653
+										),
654
+										'<strong>',
655
+										'</strong>',
656
+										'<br />'
657
+									)
658
+								),
659
+							]
660
+						),
661
+					]
662
+				),
663
+			]
664
+		);
665
+	}
666
+
667
+
668
+	/**
669
+	 * _insufficient_spaces_available
670
+	 * displays notices regarding events that do not have enough remaining spaces
671
+	 * to satisfy the current number of registrations looking to pay
672
+	 *
673
+	 * @param EE_Event[] $insufficient_spaces_events_array
674
+	 * @return EE_Form_Section_Proper
675
+	 * @throws EE_Error
676
+	 * @throws ReflectionException
677
+	 */
678
+	private function _insufficient_spaces_available($insufficient_spaces_events_array = [])
679
+	{
680
+		// set some defaults
681
+		$this->checkout->selected_method_of_payment = 'invoice';
682
+		$insufficient_space_events                  = '';
683
+		foreach ($insufficient_spaces_events_array as $event) {
684
+			if ($event instanceof EE_Event) {
685
+				$insufficient_space_events .= EEH_HTML::li(
686
+					EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
687
+				);
688
+			}
689
+		}
690
+		return new EE_Form_Section_Proper(
691
+			[
692
+				'subsections'     => [
693
+					'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
694
+					'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
695
+				],
696
+				'layout_strategy' => new EE_Template_Layout(
697
+					[
698
+						'layout_template_file' => SPCO_REG_STEPS_PATH
699
+												  . $this->_slug
700
+												  . '/sold_out_events.template.php',
701
+						'template_args'        => apply_filters(
702
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
703
+							[
704
+								'sold_out_events'     => $insufficient_space_events,
705
+								'sold_out_events_msg' => apply_filters(
706
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
707
+									esc_html__(
708
+										'It appears that the event you were about to make a payment for has sold additional tickets since you first registered, and there are no longer enough spaces left to accommodate your selections. You may continue to pay and secure the available space(s) remaining, or simply cancel if you no longer wish to purchase. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
709
+										'event_espresso'
710
+									)
711
+								),
712
+							]
713
+						),
714
+					]
715
+				),
716
+			]
717
+		);
718
+	}
719
+
720
+
721
+	/**
722
+	 * registrations_requiring_pre_approval
723
+	 *
724
+	 * @param array $registrations_requiring_pre_approval
725
+	 * @return EE_Form_Section_Proper
726
+	 * @throws EE_Error
727
+	 * @throws EntityNotFoundException
728
+	 * @throws ReflectionException
729
+	 */
730
+	private function _registrations_requiring_pre_approval($registrations_requiring_pre_approval = [])
731
+	{
732
+		$events_requiring_pre_approval = [];
733
+		foreach ($registrations_requiring_pre_approval as $registration) {
734
+			if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
735
+				$events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
736
+					EEH_HTML::span(
737
+						'',
738
+						'',
739
+						'dashicons dashicons-marker ee-icon-size-16 orange-text'
740
+					)
741
+					. EEH_HTML::span($registration->event()->name(), '', 'orange-text')
742
+				);
743
+			}
744
+		}
745
+		return new EE_Form_Section_Proper(
746
+			[
747
+				'layout_strategy' => new EE_Template_Layout(
748
+					[
749
+						'layout_template_file' => SPCO_REG_STEPS_PATH
750
+												  . $this->_slug
751
+												  . '/events_requiring_pre_approval.template.php', // layout_template
752
+						'template_args'        => apply_filters(
753
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
754
+							[
755
+								'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
756
+								'events_requiring_pre_approval_msg' => apply_filters(
757
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
758
+									esc_html__(
759
+										'The following events do not require payment at this time and will not be billed during this transaction. Billing will only occur after the attendee has been approved by the event organizer. You will be notified when your registration has been processed. If this is a free event, then no billing will occur.',
760
+										'event_espresso'
761
+									)
762
+								),
763
+							]
764
+						),
765
+					]
766
+				),
767
+			]
768
+		);
769
+	}
770
+
771
+
772
+	/**
773
+	 * _no_payment_required
774
+	 *
775
+	 * @param EE_Event[] $registrations_for_free_events
776
+	 * @return EE_Form_Section_Proper
777
+	 * @throws EE_Error
778
+	 */
779
+	private function _no_payment_required($registrations_for_free_events = [])
780
+	{
781
+		// set some defaults
782
+		$this->checkout->selected_method_of_payment = 'no_payment_required';
783
+		// generate no_payment_required form
784
+		return new EE_Form_Section_Proper(
785
+			[
786
+				'layout_strategy' => new EE_Template_Layout(
787
+					[
788
+						'layout_template_file' => SPCO_REG_STEPS_PATH
789
+												  . $this->_slug
790
+												  . '/no_payment_required.template.php', // layout_template
791
+						'template_args'        => apply_filters(
792
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
793
+							[
794
+								'revisit'                       => $this->checkout->revisit,
795
+								'registrations'                 => [],
796
+								'ticket_count'                  => [],
797
+								'registrations_for_free_events' => $registrations_for_free_events,
798
+								'no_payment_required_msg'       => EEH_HTML::p(
799
+									esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
800
+								),
801
+							]
802
+						),
803
+					]
804
+				),
805
+			]
806
+		);
807
+	}
808
+
809
+
810
+	/**
811
+	 * _display_payment_options
812
+	 *
813
+	 * @param string $transaction_details
814
+	 * @return EE_Form_Section_Proper
815
+	 * @throws EE_Error
816
+	 * @throws InvalidArgumentException
817
+	 * @throws InvalidDataTypeException
818
+	 * @throws InvalidInterfaceException
819
+	 */
820
+	private function _display_payment_options($transaction_details = '')
821
+	{
822
+		// has method_of_payment been set by no-js user?
823
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
824
+		// build payment options form
825
+		return apply_filters(
826
+			'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
827
+			new EE_Form_Section_Proper(
828
+				[
829
+					'subsections'     => [
830
+						'before_payment_options' => apply_filters(
831
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
832
+							new EE_Form_Section_Proper(
833
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
834
+							)
835
+						),
836
+						'payment_options'        => $this->_setup_payment_options(),
837
+						'after_payment_options'  => apply_filters(
838
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
839
+							new EE_Form_Section_Proper(
840
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
841
+							)
842
+						),
843
+					],
844
+					'layout_strategy' => new EE_Template_Layout(
845
+						[
846
+							'layout_template_file' => $this->_template,
847
+							'template_args'        => apply_filters(
848
+								'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
849
+								[
850
+									'reg_count'                 => $this->line_item_display->total_items(),
851
+									'transaction_details'       => $transaction_details,
852
+									'available_payment_methods' => [],
853
+								]
854
+							),
855
+						]
856
+					),
857
+				]
858
+			)
859
+		);
860
+	}
861
+
862
+
863
+	/**
864
+	 * _extra_hidden_inputs
865
+	 *
866
+	 * @param bool $no_payment_required
867
+	 * @return EE_Form_Section_Proper
868
+	 * @throws EE_Error
869
+	 * @throws ReflectionException
870
+	 */
871
+	private function _extra_hidden_inputs($no_payment_required = true)
872
+	{
873
+		return new EE_Form_Section_Proper(
874
+			[
875
+				'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
876
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
877
+				'subsections'     => [
878
+					'spco_no_payment_required' => new EE_Hidden_Input(
879
+						[
880
+							'normalization_strategy' => new EE_Boolean_Normalization(),
881
+							'html_name'              => 'spco_no_payment_required',
882
+							'html_id'                => 'spco-no-payment-required-payment_options',
883
+							'default'                => $no_payment_required,
884
+						]
885
+					),
886
+					'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
887
+						[
888
+							'normalization_strategy' => new EE_Int_Normalization(),
889
+							'html_name'              => 'spco_transaction_id',
890
+							'html_id'                => 'spco-transaction-id',
891
+							'default'                => $this->checkout->transaction->ID(),
892
+						]
893
+					),
894
+				],
895
+			]
896
+		);
897
+	}
898
+
899
+
900
+	/**
901
+	 *    _apply_registration_payments_to_amount_owing
902
+	 *
903
+	 * @param array $registrations
904
+	 * @throws EE_Error
905
+	 */
906
+	protected function _apply_registration_payments_to_amount_owing(array $registrations)
907
+	{
908
+		$payments = [];
909
+		foreach ($registrations as $registration) {
910
+			if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
911
+				$payments += $registration->registration_payments();
912
+			}
913
+		}
914
+		if (! empty($payments)) {
915
+			foreach ($payments as $payment) {
916
+				if ($payment instanceof EE_Registration_Payment) {
917
+					$this->checkout->amount_owing -= $payment->amount();
918
+				}
919
+			}
920
+		}
921
+	}
922
+
923
+
924
+	/**
925
+	 *    _reset_selected_method_of_payment
926
+	 *
927
+	 * @access    private
928
+	 * @param bool $force_reset
929
+	 * @return void
930
+	 * @throws InvalidArgumentException
931
+	 * @throws InvalidDataTypeException
932
+	 * @throws InvalidInterfaceException
933
+	 */
934
+	private function _reset_selected_method_of_payment($force_reset = false)
935
+	{
936
+		/** @var RequestInterface $request */
937
+		$request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
938
+		$reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
939
+		if ($reset_payment_method) {
940
+			$this->checkout->selected_method_of_payment = null;
941
+			$this->checkout->payment_method             = null;
942
+			$this->checkout->billing_form               = null;
943
+			$this->_save_selected_method_of_payment();
944
+		}
945
+	}
946
+
947
+
948
+	/**
949
+	 * _save_selected_method_of_payment
950
+	 * stores the selected_method_of_payment in the session
951
+	 * so that it's available for all subsequent requests including AJAX
952
+	 *
953
+	 * @access        private
954
+	 * @param string $selected_method_of_payment
955
+	 * @return void
956
+	 * @throws InvalidArgumentException
957
+	 * @throws InvalidDataTypeException
958
+	 * @throws InvalidInterfaceException
959
+	 */
960
+	private function _save_selected_method_of_payment($selected_method_of_payment = '')
961
+	{
962
+		$selected_method_of_payment = ! empty($selected_method_of_payment)
963
+			? $selected_method_of_payment
964
+			: $this->checkout->selected_method_of_payment;
965
+		EE_Registry::instance()->SSN->set_session_data(
966
+			['selected_method_of_payment' => $selected_method_of_payment]
967
+		);
968
+	}
969
+
970
+
971
+	/**
972
+	 * _setup_payment_options
973
+	 *
974
+	 * @return EE_Form_Section_Proper
975
+	 * @throws EE_Error
976
+	 * @throws InvalidArgumentException
977
+	 * @throws InvalidDataTypeException
978
+	 * @throws InvalidInterfaceException
979
+	 */
980
+	public function _setup_payment_options()
981
+	{
982
+		// load payment method classes
983
+		$this->checkout->available_payment_methods = $this->_get_available_payment_methods();
984
+		if (empty($this->checkout->available_payment_methods)) {
985
+			EE_Error::add_error(
986
+				apply_filters(
987
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
988
+					sprintf(
989
+						esc_html__(
990
+							'Sorry, you cannot complete your purchase because a payment method is not active.%1$s Please contact %2$s for assistance and provide a description of the problem.',
991
+							'event_espresso'
992
+						),
993
+						'<br>',
994
+						EE_Registry::instance()->CFG->organization->get_pretty('email')
995
+					)
996
+				),
997
+				__FILE__,
998
+				__FUNCTION__,
999
+				__LINE__
1000
+			);
1001
+		}
1002
+		// switch up header depending on number of available payment methods
1003
+		$payment_method_header     = count($this->checkout->available_payment_methods) > 1
1004
+			? apply_filters(
1005
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
1006
+				esc_html__('Please Select Your Method of Payment', 'event_espresso')
1007
+			)
1008
+			: apply_filters(
1009
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
1010
+				esc_html__('Method of Payment', 'event_espresso')
1011
+			);
1012
+		$available_payment_methods = [
1013
+			// display the "Payment Method" header
1014
+			'payment_method_header' => new EE_Form_Section_HTML(
1015
+				apply_filters(
1016
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
1017
+					EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
1018
+					$payment_method_header
1019
+				)
1020
+			),
1021
+		];
1022
+		// the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
1023
+		$available_payment_method_options = [];
1024
+		$default_payment_method_option    = [];
1025
+		// additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
1026
+		$payment_methods_billing_info = [
1027
+			new EE_Form_Section_HTML(
1028
+				EEH_HTML::div('<br />', '', '', 'clear:both;')
1029
+			),
1030
+		];
1031
+		// loop through payment methods
1032
+		foreach ($this->checkout->available_payment_methods as $payment_method) {
1033
+			if ($payment_method instanceof EE_Payment_Method) {
1034
+				$payment_method_button = EEH_HTML::img(
1035
+					$payment_method->button_url(),
1036
+					$payment_method->name(),
1037
+					'spco-payment-method-' . $payment_method->slug() . '-btn-img',
1038
+					'spco-payment-method-btn-img'
1039
+				);
1040
+				// check if any payment methods are set as default
1041
+				// if payment method is already selected OR nothing is selected and this payment method should be
1042
+				// open_by_default
1043
+				if (
1044
+					($this->checkout->selected_method_of_payment === $payment_method->slug())
1045
+					|| (! $this->checkout->selected_method_of_payment && $payment_method->open_by_default())
1046
+				) {
1047
+					$this->checkout->selected_method_of_payment = $payment_method->slug();
1048
+					$this->_save_selected_method_of_payment();
1049
+					$default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1050
+				} else {
1051
+					$available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1052
+				}
1053
+				$payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1054
+					$this->_payment_method_billing_info(
1055
+						$payment_method
1056
+					);
1057
+			}
1058
+		}
1059
+		// prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1060
+		// of PMs
1061
+		$available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1062
+		// now generate the actual form  inputs
1063
+		$available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1064
+			$available_payment_method_options
1065
+		);
1066
+		$available_payment_methods                              += $payment_methods_billing_info;
1067
+		// build the available payment methods form
1068
+		return new EE_Form_Section_Proper(
1069
+			[
1070
+				'html_id'         => 'spco-available-methods-of-payment-dv',
1071
+				'subsections'     => $available_payment_methods,
1072
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1073
+			]
1074
+		);
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * _get_available_payment_methods
1080
+	 *
1081
+	 * @return EE_Payment_Method[]
1082
+	 * @throws EE_Error
1083
+	 * @throws InvalidArgumentException
1084
+	 * @throws InvalidDataTypeException
1085
+	 * @throws InvalidInterfaceException
1086
+	 */
1087
+	protected function _get_available_payment_methods()
1088
+	{
1089
+		if (! empty($this->checkout->available_payment_methods)) {
1090
+			return $this->checkout->available_payment_methods;
1091
+		}
1092
+		$available_payment_methods = [];
1093
+		$EEM_Payment_Method        = EEM_Payment_Method::instance();
1094
+		// get all active payment methods
1095
+		$payment_methods = $EEM_Payment_Method->get_all_for_transaction(
1096
+			$this->checkout->transaction,
1097
+			EEM_Payment_Method::scope_cart
1098
+		);
1099
+		foreach ($payment_methods as $payment_method) {
1100
+			if ($payment_method instanceof EE_Payment_Method) {
1101
+				$available_payment_methods[ $payment_method->slug() ] = $payment_method;
1102
+			}
1103
+		}
1104
+		return $available_payment_methods;
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 *    _available_payment_method_inputs
1110
+	 *
1111
+	 * @access    private
1112
+	 * @param array $available_payment_method_options
1113
+	 * @return    EE_Form_Section_Proper
1114
+	 * @throws EE_Error
1115
+	 * @throws EE_Error
1116
+	 */
1117
+	private function _available_payment_method_inputs($available_payment_method_options = [])
1118
+	{
1119
+		// generate inputs
1120
+		return new EE_Form_Section_Proper(
1121
+			[
1122
+				'html_id'         => 'ee-available-payment-method-inputs',
1123
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1124
+				'subsections'     => [
1125
+					'' => new EE_Radio_Button_Input(
1126
+						$available_payment_method_options,
1127
+						[
1128
+							'html_name'          => 'selected_method_of_payment',
1129
+							'html_class'         => 'spco-payment-method',
1130
+							'default'            => $this->checkout->selected_method_of_payment,
1131
+							'label_size'         => 11,
1132
+							'enforce_label_size' => true,
1133
+						]
1134
+					),
1135
+				],
1136
+			]
1137
+		);
1138
+	}
1139
+
1140
+
1141
+	/**
1142
+	 *    _payment_method_billing_info
1143
+	 *
1144
+	 * @access    private
1145
+	 * @param EE_Payment_Method $payment_method
1146
+	 * @return EE_Form_Section_Proper
1147
+	 * @throws EE_Error
1148
+	 * @throws InvalidArgumentException
1149
+	 * @throws InvalidDataTypeException
1150
+	 * @throws InvalidInterfaceException
1151
+	 */
1152
+	private function _payment_method_billing_info(EE_Payment_Method $payment_method)
1153
+	{
1154
+		$currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1155
+		// generate the billing form for payment method
1156
+		$billing_form                 = $currently_selected
1157
+			? $this->_get_billing_form_for_payment_method($payment_method)
1158
+			: new EE_Form_Section_HTML();
1159
+		$this->checkout->billing_form = $currently_selected
1160
+			? $billing_form
1161
+			: $this->checkout->billing_form;
1162
+		// it's all in the details
1163
+		$info_html = EEH_HTML::h3(
1164
+			esc_html__('Important information regarding your payment', 'event_espresso'),
1165
+			'',
1166
+			'spco-payment-method-hdr'
1167
+		);
1168
+		// add some info regarding the step, either from what's saved in the admin,
1169
+		// or a default string depending on whether the PM has a billing form or not
1170
+		if ($payment_method->description()) {
1171
+			$payment_method_info = $payment_method->description();
1172
+		} elseif ($billing_form instanceof EE_Billing_Info_Form) {
1173
+			$payment_method_info = sprintf(
1174
+				esc_html__(
1175
+					'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1176
+					'event_espresso'
1177
+				),
1178
+				$this->submit_button_text()
1179
+			);
1180
+		} else {
1181
+			$payment_method_info = sprintf(
1182
+				esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1183
+				$this->submit_button_text()
1184
+			);
1185
+		}
1186
+		$info_html .= EEH_HTML::div(
1187
+			apply_filters(
1188
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1189
+				$payment_method_info
1190
+			),
1191
+			'',
1192
+			'spco-payment-method-desc ee-attention'
1193
+		);
1194
+		return new EE_Form_Section_Proper(
1195
+			[
1196
+				'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1197
+				'html_class'      => 'spco-payment-method-info-dv',
1198
+				// only display the selected or default PM
1199
+				'html_style'      => $currently_selected ? '' : 'display:none;',
1200
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1201
+				'subsections'     => [
1202
+					'info'         => new EE_Form_Section_HTML($info_html),
1203
+					'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1204
+				],
1205
+			]
1206
+		);
1207
+	}
1208
+
1209
+
1210
+	/**
1211
+	 * get_billing_form_html_for_payment_method
1212
+	 *
1213
+	 * @return bool
1214
+	 * @throws EE_Error
1215
+	 * @throws InvalidArgumentException
1216
+	 * @throws ReflectionException
1217
+	 * @throws InvalidDataTypeException
1218
+	 * @throws InvalidInterfaceException
1219
+	 */
1220
+	public function get_billing_form_html_for_payment_method()
1221
+	{
1222
+		// how have they chosen to pay?
1223
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1224
+		$this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1225
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1226
+			return false;
1227
+		}
1228
+		if (
1229
+			apply_filters(
1230
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1231
+				false
1232
+			)
1233
+		) {
1234
+			EE_Error::add_success(
1235
+				apply_filters(
1236
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1237
+					sprintf(
1238
+						esc_html__(
1239
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1240
+							'event_espresso'
1241
+						),
1242
+						$this->checkout->payment_method->name()
1243
+					)
1244
+				)
1245
+			);
1246
+		}
1247
+		// now generate billing form for selected method of payment
1248
+		$payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1249
+		// fill form with attendee info if applicable
1250
+		if (
1251
+			$payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1252
+			&& $this->checkout->transaction_has_primary_registrant()
1253
+		) {
1254
+			$payment_method_billing_form->populate_from_attendee(
1255
+				$this->checkout->transaction->primary_registration()->attendee()
1256
+			);
1257
+		}
1258
+		// and debug content
1259
+		if (
1260
+			$payment_method_billing_form instanceof EE_Billing_Info_Form
1261
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1262
+		) {
1263
+			$payment_method_billing_form =
1264
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1265
+					$payment_method_billing_form
1266
+				);
1267
+		}
1268
+		$billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1269
+			? $payment_method_billing_form->get_html()
1270
+			: '';
1271
+		$this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1272
+		// localize validation rules for main form
1273
+		$this->checkout->current_step->reg_form->localize_validation_rules();
1274
+		$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1275
+		return true;
1276
+	}
1277
+
1278
+
1279
+	/**
1280
+	 * _get_billing_form_for_payment_method
1281
+	 *
1282
+	 * @param EE_Payment_Method $payment_method
1283
+	 * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1284
+	 * @throws EE_Error
1285
+	 * @throws InvalidArgumentException
1286
+	 * @throws InvalidDataTypeException
1287
+	 * @throws InvalidInterfaceException
1288
+	 */
1289
+	private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1290
+	{
1291
+		$billing_form = $payment_method->type_obj()->billing_form(
1292
+			$this->checkout->transaction,
1293
+			['amount_owing' => $this->checkout->amount_owing]
1294
+		);
1295
+		if ($billing_form instanceof EE_Billing_Info_Form) {
1296
+			if (
1297
+				apply_filters(
1298
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1299
+					false
1300
+				)
1301
+				&& $this->request->requestParamIsSet('payment_method')
1302
+			) {
1303
+				EE_Error::add_success(
1304
+					apply_filters(
1305
+						'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1306
+						sprintf(
1307
+							esc_html__(
1308
+								'You have selected "%s" as your method of payment. Please note the important payment information below.',
1309
+								'event_espresso'
1310
+							),
1311
+							$payment_method->name()
1312
+						)
1313
+					)
1314
+				);
1315
+			}
1316
+			return apply_filters(
1317
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1318
+				$billing_form,
1319
+				$payment_method
1320
+			);
1321
+		}
1322
+		// no actual billing form, so return empty HTML form section
1323
+		return new EE_Form_Section_HTML();
1324
+	}
1325
+
1326
+
1327
+	/**
1328
+	 * _get_selected_method_of_payment
1329
+	 *
1330
+	 * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1331
+	 *                          is not found in the incoming request
1332
+	 * @param string  $request_param
1333
+	 * @return NULL|string
1334
+	 * @throws EE_Error
1335
+	 * @throws InvalidArgumentException
1336
+	 * @throws InvalidDataTypeException
1337
+	 * @throws InvalidInterfaceException
1338
+	 */
1339
+	private function _get_selected_method_of_payment(
1340
+		$required = false,
1341
+		$request_param = 'selected_method_of_payment'
1342
+	) {
1343
+		// is selected_method_of_payment set in the request ?
1344
+		$selected_method_of_payment = $this->request->getRequestParam($request_param);
1345
+		if ($selected_method_of_payment) {
1346
+			// sanitize it
1347
+			$selected_method_of_payment = is_array($selected_method_of_payment)
1348
+				? array_shift($selected_method_of_payment)
1349
+				: $selected_method_of_payment;
1350
+			$selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1351
+			// store it in the session so that it's available for all subsequent requests including AJAX
1352
+			$this->_save_selected_method_of_payment($selected_method_of_payment);
1353
+		} else {
1354
+			// or is it set in the session ?
1355
+			$selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1356
+				'selected_method_of_payment'
1357
+			);
1358
+		}
1359
+		if (empty($selected_method_of_payment) && $required) {
1360
+			EE_Error::add_error(
1361
+				sprintf(
1362
+					esc_html__(
1363
+						'The selected method of payment could not be determined.%sPlease ensure that you have selected one before proceeding.%sIf you continue to experience difficulties, then refresh your browser and try again, or contact %s for assistance.',
1364
+						'event_espresso'
1365
+					),
1366
+					'<br/>',
1367
+					'<br/>',
1368
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
1369
+				),
1370
+				__FILE__,
1371
+				__FUNCTION__,
1372
+				__LINE__
1373
+			);
1374
+			return null;
1375
+		}
1376
+		return $selected_method_of_payment;
1377
+	}
1378
+
1379
+
1380
+
1381
+
1382
+
1383
+
1384
+	/********************************************************************************************************/
1385
+	/***********************************  SWITCH PAYMENT METHOD  ************************************/
1386
+	/********************************************************************************************************/
1387
+	/**
1388
+	 * switch_payment_method
1389
+	 *
1390
+	 * @return bool
1391
+	 * @throws EE_Error
1392
+	 * @throws InvalidArgumentException
1393
+	 * @throws InvalidDataTypeException
1394
+	 * @throws InvalidInterfaceException
1395
+	 * @throws ReflectionException
1396
+	 */
1397
+	public function switch_payment_method()
1398
+	{
1399
+		if (! $this->_verify_payment_method_is_set()) {
1400
+			return false;
1401
+		}
1402
+		if (
1403
+			apply_filters(
1404
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1405
+				false
1406
+			)
1407
+		) {
1408
+			EE_Error::add_success(
1409
+				apply_filters(
1410
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1411
+					sprintf(
1412
+						esc_html__(
1413
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1414
+							'event_espresso'
1415
+						),
1416
+						$this->checkout->payment_method->name()
1417
+					)
1418
+				)
1419
+			);
1420
+		}
1421
+		// generate billing form for selected method of payment if it hasn't been done already
1422
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1423
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1424
+				$this->checkout->payment_method
1425
+			);
1426
+		}
1427
+		// fill form with attendee info if applicable
1428
+		if (
1429
+			apply_filters(
1430
+				'FHEE__populate_billing_form_fields_from_attendee',
1431
+				(
1432
+					$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1433
+					&& $this->checkout->transaction_has_primary_registrant()
1434
+				),
1435
+				$this->checkout->billing_form,
1436
+				$this->checkout->transaction
1437
+			)
1438
+		) {
1439
+			$this->checkout->billing_form->populate_from_attendee(
1440
+				$this->checkout->transaction->primary_registration()->attendee()
1441
+			);
1442
+		}
1443
+		// and debug content
1444
+		if (
1445
+			$this->checkout->billing_form instanceof EE_Billing_Info_Form
1446
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1447
+		) {
1448
+			$this->checkout->billing_form =
1449
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1450
+					$this->checkout->billing_form
1451
+				);
1452
+		}
1453
+		// get html and validation rules for form
1454
+		if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1455
+			$this->checkout->json_response->set_return_data(
1456
+				['payment_method_info' => $this->checkout->billing_form->get_html()]
1457
+			);
1458
+			// localize validation rules for main form
1459
+			$this->checkout->billing_form->localize_validation_rules(true);
1460
+			$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1461
+		} else {
1462
+			$this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1463
+		}
1464
+		// prevents advancement to next step
1465
+		$this->checkout->continue_reg = false;
1466
+		return true;
1467
+	}
1468
+
1469
+
1470
+	/**
1471
+	 * _verify_payment_method_is_set
1472
+	 *
1473
+	 * @return bool
1474
+	 * @throws EE_Error
1475
+	 * @throws InvalidArgumentException
1476
+	 * @throws ReflectionException
1477
+	 * @throws InvalidDataTypeException
1478
+	 * @throws InvalidInterfaceException
1479
+	 */
1480
+	protected function _verify_payment_method_is_set()
1481
+	{
1482
+		// generate billing form for selected method of payment if it hasn't been done already
1483
+		if (empty($this->checkout->selected_method_of_payment)) {
1484
+			// how have they chosen to pay?
1485
+			$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1486
+		} else {
1487
+			// choose your own adventure based on method_of_payment
1488
+			switch ($this->checkout->selected_method_of_payment) {
1489
+				case 'events_sold_out':
1490
+					EE_Error::add_attention(
1491
+						apply_filters(
1492
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1493
+							esc_html__(
1494
+								'It appears that the event you were about to make a payment for has sold out since this form first loaded. Please contact the event administrator if you believe this is an error.',
1495
+								'event_espresso'
1496
+							)
1497
+						),
1498
+						__FILE__,
1499
+						__FUNCTION__,
1500
+						__LINE__
1501
+					);
1502
+					return false;
1503
+				case 'payments_closed':
1504
+					EE_Error::add_attention(
1505
+						apply_filters(
1506
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1507
+							esc_html__(
1508
+								'It appears that the event you were about to make a payment for is not accepting payments at this time. Please contact the event administrator if you believe this is an error.',
1509
+								'event_espresso'
1510
+							)
1511
+						),
1512
+						__FILE__,
1513
+						__FUNCTION__,
1514
+						__LINE__
1515
+					);
1516
+					return false;
1517
+				case 'no_payment_required':
1518
+					EE_Error::add_attention(
1519
+						apply_filters(
1520
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1521
+							esc_html__(
1522
+								'It appears that the event you were about to make a payment for does not require payment. Please contact the event administrator if you believe this is an error.',
1523
+								'event_espresso'
1524
+							)
1525
+						),
1526
+						__FILE__,
1527
+						__FUNCTION__,
1528
+						__LINE__
1529
+					);
1530
+					return false;
1531
+				default:
1532
+			}
1533
+		}
1534
+		// verify payment method
1535
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1536
+			// get payment method for selected method of payment
1537
+			$this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1538
+		}
1539
+		return $this->checkout->payment_method instanceof EE_Payment_Method;
1540
+	}
1541
+
1542
+
1543
+
1544
+	/********************************************************************************************************/
1545
+	/***************************************  SAVE PAYER DETAILS  ****************************************/
1546
+	/********************************************************************************************************/
1547
+	/**
1548
+	 * save_payer_details_via_ajax
1549
+	 *
1550
+	 * @return void
1551
+	 * @throws EE_Error
1552
+	 * @throws InvalidArgumentException
1553
+	 * @throws ReflectionException
1554
+	 * @throws RuntimeException
1555
+	 * @throws InvalidDataTypeException
1556
+	 * @throws InvalidInterfaceException
1557
+	 */
1558
+	public function save_payer_details_via_ajax()
1559
+	{
1560
+		if (! $this->_verify_payment_method_is_set()) {
1561
+			return;
1562
+		}
1563
+		// generate billing form for selected method of payment if it hasn't been done already
1564
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1565
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1566
+				$this->checkout->payment_method
1567
+			);
1568
+		}
1569
+		// generate primary attendee from payer info if applicable
1570
+		if (! $this->checkout->transaction_has_primary_registrant()) {
1571
+			$attendee = $this->_create_attendee_from_request_data();
1572
+			if ($attendee instanceof EE_Attendee) {
1573
+				foreach ($this->checkout->transaction->registrations() as $registration) {
1574
+					if ($registration->is_primary_registrant()) {
1575
+						$this->checkout->primary_attendee_obj = $attendee;
1576
+						$registration->_add_relation_to($attendee, 'Attendee');
1577
+						$registration->set_attendee_id($attendee->ID());
1578
+						$registration->update_cache_after_object_save('Attendee', $attendee);
1579
+					}
1580
+				}
1581
+			}
1582
+		}
1583
+	}
1584
+
1585
+
1586
+	/**
1587
+	 * create_attendee_from_request_data
1588
+	 * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1589
+	 *
1590
+	 * @return EE_Attendee
1591
+	 * @throws EE_Error
1592
+	 * @throws InvalidArgumentException
1593
+	 * @throws ReflectionException
1594
+	 * @throws InvalidDataTypeException
1595
+	 * @throws InvalidInterfaceException
1596
+	 */
1597
+	protected function _create_attendee_from_request_data()
1598
+	{
1599
+		// get State ID
1600
+		$STA_ID = $this->request->getRequestParam('state');
1601
+		if (! empty($STA_ID)) {
1602
+			// can we get state object from name ?
1603
+			EE_Registry::instance()->load_model('State');
1604
+			$state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1605
+			$STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1606
+		}
1607
+		// get Country ISO
1608
+		$CNT_ISO = $this->request->getRequestParam('country');
1609
+		if (! empty($CNT_ISO)) {
1610
+			// can we get country object from name ?
1611
+			EE_Registry::instance()->load_model('Country');
1612
+			$country = EEM_Country::instance()->get_col(
1613
+				[['CNT_name' => $CNT_ISO], 'limit' => 1],
1614
+				'CNT_ISO'
1615
+			);
1616
+			$CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1617
+		}
1618
+		// grab attendee data
1619
+		$attendee_data = [
1620
+			'ATT_fname'    => $this->request->getRequestParam('first_name'),
1621
+			'ATT_lname'    => $this->request->getRequestParam('last_name'),
1622
+			'ATT_email'    => $this->request->getRequestParam('email'),
1623
+			'ATT_address'  => $this->request->getRequestParam('address'),
1624
+			'ATT_address2' => $this->request->getRequestParam('address2'),
1625
+			'ATT_city'     => $this->request->getRequestParam('city'),
1626
+			'STA_ID'       => $STA_ID,
1627
+			'CNT_ISO'      => $CNT_ISO,
1628
+			'ATT_zip'      => $this->request->getRequestParam('zip'),
1629
+			'ATT_phone'    => $this->request->getRequestParam('phone'),
1630
+		];
1631
+		// validate the email address since it is the most important piece of info
1632
+		if (empty($attendee_data['ATT_email'])) {
1633
+			EE_Error::add_error(
1634
+				esc_html__('An invalid email address was submitted.', 'event_espresso'),
1635
+				__FILE__,
1636
+				__FUNCTION__,
1637
+				__LINE__
1638
+			);
1639
+		}
1640
+		// does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1641
+		// AND email address
1642
+		if (
1643
+			! empty($attendee_data['ATT_fname'])
1644
+			&& ! empty($attendee_data['ATT_lname'])
1645
+			&& ! empty($attendee_data['ATT_email'])
1646
+		) {
1647
+			$existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1648
+				[
1649
+					'ATT_fname' => $attendee_data['ATT_fname'],
1650
+					'ATT_lname' => $attendee_data['ATT_lname'],
1651
+					'ATT_email' => $attendee_data['ATT_email'],
1652
+				]
1653
+			);
1654
+			if ($existing_attendee instanceof EE_Attendee) {
1655
+				return $existing_attendee;
1656
+			}
1657
+		}
1658
+		// no existing attendee? kk let's create a new one
1659
+		// kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1660
+		// don't exist
1661
+		$attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1662
+			? $attendee_data['ATT_fname']
1663
+			: $attendee_data['ATT_email'];
1664
+		$attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1665
+			? $attendee_data['ATT_lname']
1666
+			: $attendee_data['ATT_email'];
1667
+		return EE_Attendee::new_instance($attendee_data);
1668
+	}
1669
+
1670
+
1671
+
1672
+	/********************************************************************************************************/
1673
+	/****************************************  PROCESS REG STEP  *****************************************/
1674
+	/********************************************************************************************************/
1675
+	/**
1676
+	 * process_reg_step
1677
+	 *
1678
+	 * @return bool
1679
+	 * @throws EE_Error
1680
+	 * @throws InvalidArgumentException
1681
+	 * @throws ReflectionException
1682
+	 * @throws EntityNotFoundException
1683
+	 * @throws InvalidDataTypeException
1684
+	 * @throws InvalidInterfaceException
1685
+	 * @throws InvalidStatusException
1686
+	 */
1687
+	public function process_reg_step()
1688
+	{
1689
+		// how have they chosen to pay?
1690
+		$this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1691
+			? 'no_payment_required'
1692
+			: $this->_get_selected_method_of_payment(true);
1693
+		// choose your own adventure based on method_of_payment
1694
+		switch ($this->checkout->selected_method_of_payment) {
1695
+			case 'events_sold_out':
1696
+				$this->checkout->redirect     = true;
1697
+				$this->checkout->redirect_url = $this->checkout->cancel_page_url;
1698
+				$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1699
+				// mark this reg step as completed
1700
+				$this->set_completed();
1701
+				return false;
1702
+
1703
+			case 'payments_closed':
1704
+				if (
1705
+					apply_filters(
1706
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1707
+						false
1708
+					)
1709
+				) {
1710
+					EE_Error::add_success(
1711
+						esc_html__('no payment required at this time.', 'event_espresso'),
1712
+						__FILE__,
1713
+						__FUNCTION__,
1714
+						__LINE__
1715
+					);
1716
+				}
1717
+				// mark this reg step as completed
1718
+				$this->set_completed();
1719
+				return true;
1720
+
1721
+			case 'no_payment_required':
1722
+				if (
1723
+					apply_filters(
1724
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1725
+						false
1726
+					)
1727
+				) {
1728
+					EE_Error::add_success(
1729
+						esc_html__('no payment required.', 'event_espresso'),
1730
+						__FILE__,
1731
+						__FUNCTION__,
1732
+						__LINE__
1733
+					);
1734
+				}
1735
+				// mark this reg step as completed
1736
+				$this->set_completed();
1737
+				return true;
1738
+
1739
+			default:
1740
+				$registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1741
+					EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1742
+				);
1743
+				$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1744
+					$registrations,
1745
+					EE_Registry::instance()->SSN->checkout()->revisit
1746
+				);
1747
+				// calculate difference between the two arrays
1748
+				$registrations = array_diff($registrations, $ejected_registrations);
1749
+				if (empty($registrations)) {
1750
+					$this->_redirect_because_event_sold_out();
1751
+					return false;
1752
+				}
1753
+				$payment = $this->_process_payment();
1754
+				if ($payment instanceof EE_Payment) {
1755
+					$this->checkout->continue_reg = true;
1756
+					$this->_maybe_set_completed($payment);
1757
+				} else {
1758
+					$this->checkout->continue_reg = false;
1759
+				}
1760
+				return $payment instanceof EE_Payment;
1761
+		}
1762
+	}
1763
+
1764
+
1765
+	/**
1766
+	 * _redirect_because_event_sold_out
1767
+	 *
1768
+	 * @return void
1769
+	 */
1770
+	protected function _redirect_because_event_sold_out()
1771
+	{
1772
+		$this->checkout->continue_reg = false;
1773
+		// set redirect URL
1774
+		$this->checkout->redirect_url = add_query_arg(
1775
+			['e_reg_url_link' => $this->checkout->reg_url_link],
1776
+			$this->checkout->current_step->reg_step_url()
1777
+		);
1778
+		$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1779
+	}
1780
+
1781
+
1782
+	/**
1783
+	 * @param EE_Payment $payment
1784
+	 * @return void
1785
+	 * @throws EE_Error
1786
+	 */
1787
+	protected function _maybe_set_completed(EE_Payment $payment)
1788
+	{
1789
+		// Do we need to redirect them? If so, there's more work to be done.
1790
+		if (! $payment->redirect_url()) {
1791
+			$this->set_completed();
1792
+		}
1793
+	}
1794
+
1795
+
1796
+	/**
1797
+	 *    update_reg_step
1798
+	 *    this is the final step after a user  revisits the site to retry a payment
1799
+	 *
1800
+	 * @return bool
1801
+	 * @throws EE_Error
1802
+	 * @throws InvalidArgumentException
1803
+	 * @throws ReflectionException
1804
+	 * @throws EntityNotFoundException
1805
+	 * @throws InvalidDataTypeException
1806
+	 * @throws InvalidInterfaceException
1807
+	 * @throws InvalidStatusException
1808
+	 */
1809
+	public function update_reg_step()
1810
+	{
1811
+		$success = true;
1812
+		// if payment required
1813
+		if ($this->checkout->transaction->total() > 0) {
1814
+			do_action(
1815
+				'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1816
+				$this->checkout->transaction
1817
+			);
1818
+			// attempt payment via payment method
1819
+			$success = $this->process_reg_step();
1820
+		}
1821
+		if ($success && ! $this->checkout->redirect) {
1822
+			$this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1823
+				$this->checkout->transaction->ID()
1824
+			);
1825
+			// set return URL
1826
+			$this->checkout->redirect_url = add_query_arg(
1827
+				['e_reg_url_link' => $this->checkout->reg_url_link],
1828
+				$this->checkout->thank_you_page_url
1829
+			);
1830
+		}
1831
+		return $success;
1832
+	}
1833
+
1834
+
1835
+	/**
1836
+	 * @return EE_Payment|null
1837
+	 * @throws EE_Error
1838
+	 * @throws InvalidArgumentException
1839
+	 * @throws ReflectionException
1840
+	 * @throws RuntimeException
1841
+	 * @throws InvalidDataTypeException
1842
+	 * @throws InvalidInterfaceException
1843
+	 */
1844
+	private function _process_payment()
1845
+	{
1846
+		// basically confirm that the event hasn't sold out since they hit the page
1847
+		if (! $this->_last_second_ticket_verifications()) {
1848
+			return null;
1849
+		}
1850
+		// ya gotta make a choice man
1851
+		if (empty($this->checkout->selected_method_of_payment)) {
1852
+			$this->checkout->json_response->set_plz_select_method_of_payment(
1853
+				esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1854
+			);
1855
+			return null;
1856
+		}
1857
+		// get EE_Payment_Method object
1858
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1859
+			return null;
1860
+		}
1861
+		// setup billing form
1862
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1863
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1864
+				$this->checkout->payment_method
1865
+			);
1866
+			// bad billing form ?
1867
+			if (! $this->_billing_form_is_valid()) {
1868
+				return null;
1869
+			}
1870
+		}
1871
+		// ensure primary registrant has been fully processed
1872
+		if (! $this->_setup_primary_registrant_prior_to_payment()) {
1873
+			return null;
1874
+		}
1875
+		// if session is close to expiring (under 10 minutes by default)
1876
+		if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1877
+			// add some time to session expiration so that payment can be completed
1878
+			EE_Registry::instance()->SSN->extend_expiration();
1879
+		}
1880
+		/** @type EE_Transaction_Processor $transaction_processor */
1881
+		// $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1882
+		// in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1883
+		// for events with a default reg status of Approved
1884
+		// $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1885
+		//      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1886
+		// );
1887
+		// attempt payment
1888
+		$payment = $this->_attempt_payment($this->checkout->payment_method);
1889
+		// process results
1890
+		$payment = $this->_validate_payment($payment);
1891
+		$payment = $this->_post_payment_processing($payment);
1892
+		// verify payment
1893
+		if ($payment instanceof EE_Payment) {
1894
+			// store that for later
1895
+			$this->checkout->payment = $payment;
1896
+			// we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1897
+			$this->checkout->transaction->toggle_failed_transaction_status();
1898
+			$payment_status = $payment->status();
1899
+			if (
1900
+				$payment_status === EEM_Payment::status_id_approved
1901
+				|| $payment_status === EEM_Payment::status_id_pending
1902
+			) {
1903
+				return $payment;
1904
+			}
1905
+			return null;
1906
+		}
1907
+		if ($payment === true) {
1908
+			// please note that offline payment methods will NOT make a payment,
1909
+			// but instead just mark themselves as the PMD_ID on the transaction, and return true
1910
+			$this->checkout->payment = $payment;
1911
+			return $payment;
1912
+		}
1913
+		// where's my money?
1914
+		return null;
1915
+	}
1916
+
1917
+
1918
+	/**
1919
+	 * _last_second_ticket_verifications
1920
+	 *
1921
+	 * @return bool
1922
+	 * @throws EE_Error
1923
+	 * @throws ReflectionException
1924
+	 */
1925
+	protected function _last_second_ticket_verifications()
1926
+	{
1927
+		// don't bother re-validating if not a return visit
1928
+		if (! $this->checkout->revisit) {
1929
+			return true;
1930
+		}
1931
+		$registrations = $this->checkout->transaction->registrations();
1932
+		if (empty($registrations)) {
1933
+			return false;
1934
+		}
1935
+		foreach ($registrations as $registration) {
1936
+			if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1937
+				$event = $registration->event_obj();
1938
+				if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1939
+					EE_Error::add_error(
1940
+						apply_filters(
1941
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1942
+							sprintf(
1943
+								esc_html__(
1944
+									'It appears that the %1$s event that you were about to make a payment for has sold out since you first registered and/or arrived at this page. Please refresh the page and try again. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
1945
+									'event_espresso'
1946
+								),
1947
+								$event->name()
1948
+							)
1949
+						),
1950
+						__FILE__,
1951
+						__FUNCTION__,
1952
+						__LINE__
1953
+					);
1954
+					return false;
1955
+				}
1956
+			}
1957
+		}
1958
+		return true;
1959
+	}
1960
+
1961
+
1962
+	/**
1963
+	 * redirect_form
1964
+	 *
1965
+	 * @return bool
1966
+	 * @throws EE_Error
1967
+	 * @throws InvalidArgumentException
1968
+	 * @throws ReflectionException
1969
+	 * @throws InvalidDataTypeException
1970
+	 * @throws InvalidInterfaceException
1971
+	 */
1972
+	public function redirect_form()
1973
+	{
1974
+		$payment_method_billing_info = $this->_payment_method_billing_info(
1975
+			$this->_get_payment_method_for_selected_method_of_payment()
1976
+		);
1977
+		$html                        = $payment_method_billing_info->get_html();
1978
+		$html                        .= $this->checkout->redirect_form;
1979
+		/** @var ResponseInterface $response */
1980
+		$response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1981
+		$response->addOutput($html);
1982
+		return true;
1983
+	}
1984
+
1985
+
1986
+	/**
1987
+	 * _billing_form_is_valid
1988
+	 *
1989
+	 * @return bool
1990
+	 * @throws EE_Error
1991
+	 */
1992
+	private function _billing_form_is_valid()
1993
+	{
1994
+		if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1995
+			return true;
1996
+		}
1997
+		if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1998
+			if ($this->checkout->billing_form->was_submitted()) {
1999
+				$this->checkout->billing_form->receive_form_submission();
2000
+				if ($this->checkout->billing_form->is_valid()) {
2001
+					return true;
2002
+				}
2003
+				$validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
2004
+				$error_strings     = [];
2005
+				foreach ($validation_errors as $validation_error) {
2006
+					if ($validation_error instanceof EE_Validation_Error) {
2007
+						$form_section = $validation_error->get_form_section();
2008
+						if ($form_section instanceof EE_Form_Input_Base) {
2009
+							$label = $form_section->html_label_text();
2010
+						} elseif ($form_section instanceof EE_Form_Section_Base) {
2011
+							$label = $form_section->name();
2012
+						} else {
2013
+							$label = esc_html__('Validation Error', 'event_espresso');
2014
+						}
2015
+						$error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
2016
+					}
2017
+				}
2018
+				EE_Error::add_error(
2019
+					sprintf(
2020
+						esc_html__(
2021
+							'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
2022
+							'event_espresso'
2023
+						),
2024
+						'<br/>',
2025
+						implode('<br/>', $error_strings)
2026
+					),
2027
+					__FILE__,
2028
+					__FUNCTION__,
2029
+					__LINE__
2030
+				);
2031
+			} else {
2032
+				EE_Error::add_error(
2033
+					esc_html__(
2034
+						'The billing form was not submitted or something prevented it\'s submission.',
2035
+						'event_espresso'
2036
+					),
2037
+					__FILE__,
2038
+					__FUNCTION__,
2039
+					__LINE__
2040
+				);
2041
+			}
2042
+		} else {
2043
+			EE_Error::add_error(
2044
+				esc_html__(
2045
+					'The submitted billing form is invalid possibly due to a technical reason.',
2046
+					'event_espresso'
2047
+				),
2048
+				__FILE__,
2049
+				__FUNCTION__,
2050
+				__LINE__
2051
+			);
2052
+		}
2053
+		return false;
2054
+	}
2055
+
2056
+
2057
+	/**
2058
+	 * _setup_primary_registrant_prior_to_payment
2059
+	 * ensures that the primary registrant has a valid attendee object created with the critical details populated
2060
+	 * (first & last name & email) and that both the transaction object and primary registration object have been saved
2061
+	 * plz note that any other registrations will NOT be saved at this point (because they may not have any details
2062
+	 * yet)
2063
+	 *
2064
+	 * @return bool
2065
+	 * @throws EE_Error
2066
+	 * @throws InvalidArgumentException
2067
+	 * @throws ReflectionException
2068
+	 * @throws RuntimeException
2069
+	 * @throws InvalidDataTypeException
2070
+	 * @throws InvalidInterfaceException
2071
+	 */
2072
+	private function _setup_primary_registrant_prior_to_payment()
2073
+	{
2074
+		// check if transaction has a primary registrant and that it has a related Attendee object
2075
+		// if not, then we need to at least gather some primary registrant data before attempting payment
2076
+		if (
2077
+			$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
2078
+			&& ! $this->checkout->transaction_has_primary_registrant()
2079
+			&& ! $this->_capture_primary_registration_data_from_billing_form()
2080
+		) {
2081
+			return false;
2082
+		}
2083
+		// because saving an object clears its cache, we need to do the Chevy Shuffle
2084
+		// grab the primary_registration object
2085
+		$primary_registration = $this->checkout->transaction->primary_registration();
2086
+		// at this point we'll consider a TXN to not have been failed
2087
+		$this->checkout->transaction->toggle_failed_transaction_status();
2088
+		// save the TXN ( which clears cached copy of primary_registration)
2089
+		$this->checkout->transaction->save();
2090
+		// grab TXN ID and save it to the primary_registration
2091
+		$primary_registration->set_transaction_id($this->checkout->transaction->ID());
2092
+		// save what we have so far
2093
+		$primary_registration->save();
2094
+		return true;
2095
+	}
2096
+
2097
+
2098
+	/**
2099
+	 * Captures primary registration data from the billing form.
2100
+	 *
2101
+	 * This method is used to gather the primary registrant data before attempting payment.
2102
+	 * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2103
+	 * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2104
+	 *
2105
+	 * @return bool
2106
+	 * @throws EE_Error
2107
+	 * @throws InvalidArgumentException
2108
+	 * @throws ReflectionException
2109
+	 * @throws InvalidDataTypeException
2110
+	 * @throws InvalidInterfaceException
2111
+	 */
2112
+	private function _capture_primary_registration_data_from_billing_form(): bool
2113
+	{
2114
+		$primary_registration = $this->checkout->transaction->primary_registration();
2115
+		if (! $this->validatePrimaryRegistration($primary_registration)) {
2116
+			return false;
2117
+		}
2118
+
2119
+		$primary_attendee = $this->getPrimaryAttendee($primary_registration);
2120
+		if (! $this->validatePrimaryAttendee($primary_attendee)) {
2121
+			return false;
2122
+		}
2123
+
2124
+		if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2125
+			return false;
2126
+		}
2127
+		// both the primary registration and primary attendee objects should be valid entities at this point
2128
+		$this->checkout->primary_attendee_obj = $primary_attendee;
2129
+
2130
+		/** @type EE_Registration_Processor $registration_processor */
2131
+		$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2132
+		// at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2133
+		$registration_processor->toggle_incomplete_registration_status_to_default(
2134
+			$primary_registration,
2135
+			false,
2136
+			new Context(
2137
+				__METHOD__,
2138
+				esc_html__(
2139
+					'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2140
+					'event_espresso'
2141
+				)
2142
+			)
2143
+		);
2144
+		return true;
2145
+	}
2146
+
2147
+
2148
+	/**
2149
+	 * returns true if the primary registration is a valid entity
2150
+	 *
2151
+	 * @param $primary_registration
2152
+	 * @return bool
2153
+	 * @throws EE_Error
2154
+	 * @since 5.0.21.p
2155
+	 */
2156
+	private function validatePrimaryRegistration($primary_registration): bool
2157
+	{
2158
+		if ($primary_registration instanceof EE_Registration) {
2159
+			return true;
2160
+		}
2161
+		EE_Error::add_error(
2162
+			sprintf(
2163
+				esc_html__(
2164
+					'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2165
+					'event_espresso'
2166
+				),
2167
+				'<br/>',
2168
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2169
+			),
2170
+			__FILE__,
2171
+			__FUNCTION__,
2172
+			__LINE__
2173
+		);
2174
+		return false;
2175
+	}
2176
+
2177
+
2178
+	/**
2179
+	 * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2180
+	 * if the primary registration does not have an attendee object, then one is created from the billing form info
2181
+	 *
2182
+	 * @param EE_Registration $primary_registration
2183
+	 * @return EE_Attendee|null
2184
+	 * @throws EE_Error
2185
+	 * @throws ReflectionException
2186
+	 * @since 5.0.21.p
2187
+	 */
2188
+	private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2189
+	{
2190
+		// if we have a primary registration, then we should have a primary attendee
2191
+		$attendee = $primary_registration->attendee();
2192
+		if ($attendee instanceof EE_Attendee) {
2193
+			return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2194
+		}
2195
+		// if not, then we need to create one from the billing form
2196
+		return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2197
+	}
2198
+
2199
+
2200
+	/**
2201
+	 * returns true if the primary attendee is a valid entity
2202
+	 *
2203
+	 * @param $primary_attendee
2204
+	 * @return bool
2205
+	 * @throws EE_Error
2206
+	 * @since 5.0.21.p
2207
+	 */
2208
+	private function validatePrimaryAttendee($primary_attendee): bool
2209
+	{
2210
+		if ($primary_attendee instanceof EE_Attendee) {
2211
+			return true;
2212
+		}
2213
+		EE_Error::add_error(
2214
+			sprintf(
2215
+				esc_html__(
2216
+					'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2217
+					'event_espresso'
2218
+				),
2219
+				'<br/>',
2220
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2221
+			),
2222
+			__FILE__,
2223
+			__FUNCTION__,
2224
+			__LINE__
2225
+		);
2226
+		return false;
2227
+	}
2228
+
2229
+
2230
+	/**
2231
+	 * returns true if the attendee was successfully added to the primary registration
2232
+	 *
2233
+	 * @param EE_Attendee     $primary_attendee
2234
+	 * @param EE_Registration $primary_registration
2235
+	 * @return bool
2236
+	 * @throws EE_Error
2237
+	 * @throws ReflectionException
2238
+	 * @since 5.0.21.p
2239
+	 */
2240
+	private function addAttendeeToPrimaryRegistration(
2241
+		EE_Attendee $primary_attendee,
2242
+		EE_Registration $primary_registration
2243
+	): bool {
2244
+		// ensure attendee has an ID by saving
2245
+		$primary_attendee->save();
2246
+
2247
+		// compare attendee IDs
2248
+		if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2249
+			return true;
2250
+		}
2251
+
2252
+		$primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2253
+		if ($primary_attendee instanceof EE_Attendee) {
2254
+			return true;
2255
+		}
2256
+
2257
+		EE_Error::add_error(
2258
+			sprintf(
2259
+				esc_html__(
2260
+					'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2261
+					'event_espresso'
2262
+				),
2263
+				'<br/>',
2264
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2265
+			),
2266
+			__FILE__,
2267
+			__FUNCTION__,
2268
+			__LINE__
2269
+		);
2270
+		return false;
2271
+	}
2272
+
2273
+
2274
+	/**
2275
+	 * _get_payment_method_for_selected_method_of_payment
2276
+	 * retrieves a valid payment method
2277
+	 *
2278
+	 * @return EE_Payment_Method
2279
+	 * @throws EE_Error
2280
+	 * @throws InvalidArgumentException
2281
+	 * @throws ReflectionException
2282
+	 * @throws InvalidDataTypeException
2283
+	 * @throws InvalidInterfaceException
2284
+	 */
2285
+	private function _get_payment_method_for_selected_method_of_payment()
2286
+	{
2287
+		if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2288
+			$this->_redirect_because_event_sold_out();
2289
+			return null;
2290
+		}
2291
+		// get EE_Payment_Method object
2292
+		if (isset($this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ])) {
2293
+			$payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ];
2294
+		} else {
2295
+			// load EEM_Payment_Method
2296
+			EE_Registry::instance()->load_model('Payment_Method');
2297
+			$EEM_Payment_Method = EEM_Payment_Method::instance();
2298
+			$payment_method     = $EEM_Payment_Method->get_one_by_slug($this->checkout->selected_method_of_payment);
2299
+		}
2300
+		// verify $payment_method
2301
+		if (! $payment_method instanceof EE_Payment_Method) {
2302
+			// not a payment
2303
+			EE_Error::add_error(
2304
+				sprintf(
2305
+					esc_html__(
2306
+						'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2307
+						'event_espresso'
2308
+					),
2309
+					'<br/>',
2310
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2311
+				),
2312
+				__FILE__,
2313
+				__FUNCTION__,
2314
+				__LINE__
2315
+			);
2316
+			return null;
2317
+		}
2318
+		// and verify it has a valid Payment_Method Type object
2319
+		if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2320
+			// not a payment
2321
+			EE_Error::add_error(
2322
+				sprintf(
2323
+					esc_html__(
2324
+						'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2325
+						'event_espresso'
2326
+					),
2327
+					'<br/>',
2328
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2329
+				),
2330
+				__FILE__,
2331
+				__FUNCTION__,
2332
+				__LINE__
2333
+			);
2334
+			return null;
2335
+		}
2336
+		return $payment_method;
2337
+	}
2338
+
2339
+
2340
+	/**
2341
+	 *    _attempt_payment
2342
+	 *
2343
+	 * @access    private
2344
+	 * @type    EE_Payment_Method $payment_method
2345
+	 * @return EE_Payment|null
2346
+	 * @throws EE_Error
2347
+	 * @throws InvalidArgumentException
2348
+	 * @throws ReflectionException
2349
+	 * @throws InvalidDataTypeException
2350
+	 * @throws InvalidInterfaceException
2351
+	 */
2352
+	private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2353
+	{
2354
+		$this->checkout->transaction->save();
2355
+		/** @var PaymentProcessor $payment_processor */
2356
+		$payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2357
+		if (! $payment_processor instanceof PaymentProcessor) {
2358
+			return null;
2359
+		}
2360
+		/** @var EE_Transaction_Processor $transaction_processor */
2361
+		$transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2362
+		if ($transaction_processor instanceof EE_Transaction_Processor) {
2363
+			$transaction_processor->set_revisit($this->checkout->revisit);
2364
+		}
2365
+		try {
2366
+			// generate payment object
2367
+			return $payment_processor->processPayment(
2368
+				$payment_method,
2369
+				$this->checkout->transaction,
2370
+				$this->checkout->billing_form instanceof EE_Billing_Info_Form
2371
+					? $this->checkout->billing_form
2372
+					: null,
2373
+				$this->checkout->amount_owing,
2374
+				$this->checkout->admin_request,
2375
+				true,
2376
+				$this->_get_return_url($payment_method),
2377
+				$this->reg_step_url()
2378
+			);
2379
+		} catch (Exception $e) {
2380
+			$this->_handle_payment_processor_exception($e);
2381
+		}
2382
+		return null;
2383
+	}
2384
+
2385
+
2386
+	/**
2387
+	 * _handle_payment_processor_exception
2388
+	 *
2389
+	 * @param Exception $e
2390
+	 * @return void
2391
+	 * @throws EE_Error
2392
+	 * @throws InvalidArgumentException
2393
+	 * @throws InvalidDataTypeException
2394
+	 * @throws InvalidInterfaceException
2395
+	 */
2396
+	protected function _handle_payment_processor_exception(Exception $e)
2397
+	{
2398
+		EE_Error::add_error(
2399
+			sprintf(
2400
+				esc_html__(
2401
+					'The payment could not br processed due to a technical issue.%1$sPlease try again or contact %2$s for assistance.||The following Exception was thrown in %4$s on line %5$s:%1$s%3$s',
2402
+					'event_espresso'
2403
+				),
2404
+				'<br/>',
2405
+				EE_Registry::instance()->CFG->organization->get_pretty('email'),
2406
+				$e->getMessage(),
2407
+				$e->getFile(),
2408
+				$e->getLine()
2409
+			),
2410
+			__FILE__,
2411
+			__FUNCTION__,
2412
+			__LINE__
2413
+		);
2414
+	}
2415
+
2416
+
2417
+	/**
2418
+	 * @param EE_Payment_Method $payment_method
2419
+	 * @return string
2420
+	 * @throws EE_Error
2421
+	 * @throws ReflectionException
2422
+	 */
2423
+	protected function _get_return_url(EE_Payment_Method $payment_method)
2424
+	{
2425
+		switch ($payment_method->type_obj()->payment_occurs()) {
2426
+			case EE_PMT_Base::offsite:
2427
+				return add_query_arg(
2428
+					[
2429
+						'action'                     => 'process_gateway_response',
2430
+						'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2431
+						'spco_txn'                   => $this->checkout->transaction->ID(),
2432
+					],
2433
+					$this->reg_step_url()
2434
+				);
2435
+
2436
+			case EE_PMT_Base::onsite:
2437
+			case EE_PMT_Base::offline:
2438
+				return $this->checkout->next_step->reg_step_url();
2439
+		}
2440
+		return '';
2441
+	}
2442
+
2443
+
2444
+	/**
2445
+	 * _validate_payment
2446
+	 *
2447
+	 * @param EE_Payment $payment
2448
+	 * @return EE_Payment|bool
2449
+	 * @throws EE_Error
2450
+	 * @throws InvalidArgumentException
2451
+	 * @throws InvalidDataTypeException
2452
+	 * @throws InvalidInterfaceException
2453
+	 */
2454
+	private function _validate_payment($payment = null)
2455
+	{
2456
+		if ($this->checkout->payment_method->is_off_line()) {
2457
+			return true;
2458
+		}
2459
+		// verify payment object
2460
+		if (! $payment instanceof EE_Payment) {
2461
+			// not a payment
2462
+			EE_Error::add_error(
2463
+				sprintf(
2464
+					esc_html__(
2465
+						'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2466
+						'event_espresso'
2467
+					),
2468
+					'<br/>',
2469
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2470
+				),
2471
+				__FILE__,
2472
+				__FUNCTION__,
2473
+				__LINE__
2474
+			);
2475
+			return false;
2476
+		}
2477
+		return $payment;
2478
+	}
2479
+
2480
+
2481
+	/**
2482
+	 * _post_payment_processing
2483
+	 *
2484
+	 * @param EE_Payment|bool $payment
2485
+	 * @return bool|EE_Payment
2486
+	 * @throws EE_Error
2487
+	 * @throws InvalidArgumentException
2488
+	 * @throws InvalidDataTypeException
2489
+	 * @throws InvalidInterfaceException
2490
+	 * @throws ReflectionException
2491
+	 */
2492
+	private function _post_payment_processing($payment = null)
2493
+	{
2494
+		// Off-Line payment?
2495
+		if ($payment === true) {
2496
+			return true;
2497
+		}
2498
+		if ($payment instanceof EE_Payment) {
2499
+			// Should the user be redirected?
2500
+			if ($payment->redirect_url()) {
2501
+				do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2502
+				$this->checkout->redirect      = true;
2503
+				$this->checkout->redirect_form = $payment->redirect_form();
2504
+				$this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2505
+				// set JSON response
2506
+				$this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2507
+				// and lastly, let's bump the payment status to pending
2508
+				$payment->set_status(EEM_Payment::status_id_pending);
2509
+				$payment->save();
2510
+			} elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2511
+				// User shouldn't be redirected. So let's process it here.
2512
+				// $this->_setup_redirect_for_next_step();
2513
+				$this->checkout->continue_reg = false;
2514
+			}
2515
+			return $payment;
2516
+		}
2517
+		// ummm ya... not Off-Line, not On-Site, not off-Site ????
2518
+		$this->checkout->continue_reg = false;
2519
+		return false;
2520
+	}
2521
+
2522
+
2523
+	/**
2524
+	 *    _process_payment_status
2525
+	 *
2526
+	 * @type    EE_Payment $payment
2527
+	 * @param string       $payment_occurs
2528
+	 * @return bool
2529
+	 * @throws EE_Error
2530
+	 * @throws InvalidArgumentException
2531
+	 * @throws InvalidDataTypeException
2532
+	 * @throws InvalidInterfaceException
2533
+	 */
2534
+	private function _process_payment_status($payment, $payment_occurs = EE_PMT_Base::offline)
2535
+	{
2536
+		// off-line payment? carry on
2537
+		if ($payment_occurs === EE_PMT_Base::offline) {
2538
+			return true;
2539
+		}
2540
+		// verify payment validity
2541
+		if ($payment instanceof EE_Payment) {
2542
+			do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2543
+			$msg = $payment->gateway_response();
2544
+			// check results
2545
+			switch ($payment->status()) {
2546
+				// good payment
2547
+				case EEM_Payment::status_id_approved:
2548
+					EE_Error::add_success(
2549
+						esc_html__('Your payment was processed successfully.', 'event_espresso'),
2550
+						__FILE__,
2551
+						__FUNCTION__,
2552
+						__LINE__
2553
+					);
2554
+					return true;
2555
+				// slow payment
2556
+				case EEM_Payment::status_id_pending:
2557
+					if (empty($msg)) {
2558
+						$msg = esc_html__(
2559
+							'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2560
+							'event_espresso'
2561
+						);
2562
+					}
2563
+					EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2564
+					return true;
2565
+				// don't wanna payment
2566
+				case EEM_Payment::status_id_cancelled:
2567
+					if (empty($msg)) {
2568
+						$msg = _n(
2569
+							'Payment cancelled. Please try again.',
2570
+							'Payment cancelled. Please try again or select another method of payment.',
2571
+							count($this->checkout->available_payment_methods),
2572
+							'event_espresso'
2573
+						);
2574
+					}
2575
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2576
+					return false;
2577
+				// not enough payment
2578
+				case EEM_Payment::status_id_declined:
2579
+					if (empty($msg)) {
2580
+						$msg = _n(
2581
+							'We\'re sorry but your payment was declined. Please try again.',
2582
+							'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2583
+							count($this->checkout->available_payment_methods),
2584
+							'event_espresso'
2585
+						);
2586
+					}
2587
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2588
+					return false;
2589
+				// bad payment
2590
+				case EEM_Payment::status_id_failed:
2591
+					if (! empty($msg)) {
2592
+						EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2593
+						return false;
2594
+					}
2595
+					// default to error below
2596
+					break;
2597
+			}
2598
+		}
2599
+		// off-site payment gateway responses are too unreliable, so let's just assume that
2600
+		// the payment processing is just running slower than the registrant's request
2601
+		if ($payment_occurs === EE_PMT_Base::offsite) {
2602
+			return true;
2603
+		}
2604
+		EE_Error::add_error(
2605
+			sprintf(
2606
+				esc_html__(
2607
+					'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2608
+					'event_espresso'
2609
+				),
2610
+				'<br/>',
2611
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2612
+			),
2613
+			__FILE__,
2614
+			__FUNCTION__,
2615
+			__LINE__
2616
+		);
2617
+		return false;
2618
+	}
2619
+
2620
+
2621
+
2622
+
2623
+
2624
+
2625
+	/********************************************************************************************************/
2626
+	/**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2627
+	/********************************************************************************************************/
2628
+	/**
2629
+	 * process_gateway_response
2630
+	 * this is the return point for Off-Site Payment Methods
2631
+	 * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2632
+	 * otherwise, it will load up the last payment made for the TXN.
2633
+	 * If the payment retrieved looks good, it will then either:
2634
+	 *    complete the current step and allow advancement to the next reg step
2635
+	 *        or present the payment options again
2636
+	 *
2637
+	 * @return bool
2638
+	 * @throws EE_Error
2639
+	 * @throws InvalidArgumentException
2640
+	 * @throws ReflectionException
2641
+	 * @throws InvalidDataTypeException
2642
+	 * @throws InvalidInterfaceException
2643
+	 */
2644
+	public function process_gateway_response()
2645
+	{
2646
+		// how have they chosen to pay?
2647
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2648
+		// get EE_Payment_Method object
2649
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2650
+			$this->checkout->continue_reg = false;
2651
+			return false;
2652
+		}
2653
+		if (! $this->checkout->payment_method->is_off_site()) {
2654
+			return false;
2655
+		}
2656
+		$this->_validate_offsite_return();
2657
+		// verify TXN
2658
+		if ($this->checkout->transaction instanceof EE_Transaction) {
2659
+			$gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2660
+			if (! $gateway instanceof EE_Offsite_Gateway) {
2661
+				$this->checkout->continue_reg = false;
2662
+				return false;
2663
+			}
2664
+			$payment = $this->_process_off_site_payment($gateway);
2665
+			$payment = $this->_process_cancelled_payments($payment);
2666
+			$payment = $this->_validate_payment($payment);
2667
+			// if payment was not declined by the payment gateway or cancelled by the registrant
2668
+			if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2669
+				// $this->_setup_redirect_for_next_step();
2670
+				// store that for later
2671
+				$this->checkout->payment = $payment;
2672
+				// mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2673
+				// because we will complete this step during the IPN processing then
2674
+				if (! $this->handle_IPN_in_this_request()) {
2675
+					$this->set_completed();
2676
+				}
2677
+				return true;
2678
+			}
2679
+		}
2680
+		// DEBUG LOG
2681
+		// $this->checkout->log(
2682
+		//     __CLASS__,
2683
+		//     __FUNCTION__,
2684
+		//     __LINE__,
2685
+		//     array('payment' => $payment)
2686
+		// );
2687
+		$this->checkout->continue_reg = false;
2688
+		return false;
2689
+	}
2690
+
2691
+
2692
+	/**
2693
+	 * _validate_return
2694
+	 *
2695
+	 * @return void
2696
+	 * @throws EE_Error
2697
+	 * @throws InvalidArgumentException
2698
+	 * @throws InvalidDataTypeException
2699
+	 * @throws InvalidInterfaceException
2700
+	 * @throws ReflectionException
2701
+	 */
2702
+	private function _validate_offsite_return()
2703
+	{
2704
+		$TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2705
+		if ($TXN_ID !== $this->checkout->transaction->ID()) {
2706
+			// Houston... we might have a problem
2707
+			$invalid_TXN = false;
2708
+			// first gather some info
2709
+			$valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2710
+			$primary_registrant = $valid_TXN instanceof EE_Transaction
2711
+				? $valid_TXN->primary_registration()
2712
+				: null;
2713
+			// let's start by retrieving the cart for this TXN
2714
+			$cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2715
+			if ($cart instanceof EE_Cart) {
2716
+				// verify that the current cart has tickets
2717
+				$tickets = $cart->get_tickets();
2718
+				if (empty($tickets)) {
2719
+					$invalid_TXN = true;
2720
+				}
2721
+			} else {
2722
+				$invalid_TXN = true;
2723
+			}
2724
+			$valid_TXN_SID = $primary_registrant instanceof EE_Registration
2725
+				? $primary_registrant->session_ID()
2726
+				: null;
2727
+			// validate current Session ID and compare against valid TXN session ID
2728
+			if (
2729
+				$invalid_TXN // if this is already true, then skip other checks
2730
+				|| EE_Session::instance()->id() === null
2731
+				|| (
2732
+					// WARNING !!!
2733
+					// this could be PayPal sending back duplicate requests (ya they do that)
2734
+					// or it **could** mean someone is simply registering AGAIN after having just done so,
2735
+					// so now we need to determine if this current TXN looks valid or not
2736
+					// and whether this reg step has even been started ?
2737
+					EE_Session::instance()->id() === $valid_TXN_SID
2738
+					// really? you're halfway through this reg step, but you never started it ?
2739
+					&& $this->checkout->transaction->reg_step_completed($this->slug()) === false
2740
+				)
2741
+			) {
2742
+				$invalid_TXN = true;
2743
+			}
2744
+			if ($invalid_TXN) {
2745
+				// is the valid TXN completed ?
2746
+				if ($valid_TXN instanceof EE_Transaction) {
2747
+					// has this step even been started ?
2748
+					$reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2749
+					if ($reg_step_completed !== false && $reg_step_completed !== true) {
2750
+						// so it **looks** like this is a double request from PayPal
2751
+						// so let's try to pick up where we left off
2752
+						$this->checkout->transaction = $valid_TXN;
2753
+						$this->checkout->refresh_all_entities(true);
2754
+						return;
2755
+					}
2756
+				}
2757
+				// you appear to be lost?
2758
+				$this->_redirect_wayward_request($primary_registrant);
2759
+			}
2760
+		}
2761
+	}
2762
+
2763
+
2764
+	/**
2765
+	 * _redirect_wayward_request
2766
+	 *
2767
+	 * @param EE_Registration|null $primary_registrant
2768
+	 * @return void
2769
+	 * @throws EE_Error
2770
+	 * @throws InvalidArgumentException
2771
+	 * @throws InvalidDataTypeException
2772
+	 * @throws InvalidInterfaceException
2773
+	 * @throws ReflectionException
2774
+	 */
2775
+	private function _redirect_wayward_request(EE_Registration $primary_registrant)
2776
+	{
2777
+		if (! $primary_registrant instanceof EE_Registration) {
2778
+			// try redirecting based on the current TXN
2779
+			$primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2780
+				? $this->checkout->transaction->primary_registration()
2781
+				: null;
2782
+		}
2783
+		if (! $primary_registrant instanceof EE_Registration) {
2784
+			EE_Error::add_error(
2785
+				sprintf(
2786
+					esc_html__(
2787
+						'Invalid information was received from the Off-Site Payment Processor and your Transaction details could not be retrieved from the database.%1$sPlease try again or contact %2$s for assistance.',
2788
+						'event_espresso'
2789
+					),
2790
+					'<br/>',
2791
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2792
+				),
2793
+				__FILE__,
2794
+				__FUNCTION__,
2795
+				__LINE__
2796
+			);
2797
+			return;
2798
+		}
2799
+		// make sure transaction is not locked
2800
+		$this->checkout->transaction->unlock();
2801
+		wp_safe_redirect(
2802
+			add_query_arg(
2803
+				[
2804
+					'e_reg_url_link' => $primary_registrant->reg_url_link(),
2805
+				],
2806
+				$this->checkout->thank_you_page_url
2807
+			)
2808
+		);
2809
+		exit();
2810
+	}
2811
+
2812
+
2813
+	/**
2814
+	 * _process_off_site_payment
2815
+	 *
2816
+	 * @param EE_Offsite_Gateway $gateway
2817
+	 * @return EE_Payment
2818
+	 * @throws EE_Error
2819
+	 * @throws InvalidArgumentException
2820
+	 * @throws InvalidDataTypeException
2821
+	 * @throws InvalidInterfaceException
2822
+	 * @throws ReflectionException
2823
+	 */
2824
+	private function _process_off_site_payment(EE_Offsite_Gateway $gateway)
2825
+	{
2826
+		try {
2827
+			$request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2828
+			$request_data = $request->requestParams();
2829
+			// if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2830
+			$this->set_handle_IPN_in_this_request(
2831
+				$gateway->handle_IPN_in_this_request($request_data, false)
2832
+			);
2833
+			if ($this->handle_IPN_in_this_request()) {
2834
+				// get payment details and process results
2835
+				/** @var IpnHandler $payment_processor */
2836
+				$payment_processor = LoaderFactory::getShared(IpnHandler::class);
2837
+				$payment           = $payment_processor->processIPN(
2838
+					$request_data,
2839
+					$this->checkout->transaction,
2840
+					$this->checkout->payment_method,
2841
+					true,
2842
+					false
2843
+				);
2844
+				// $payment_source = 'process_ipn';
2845
+			} else {
2846
+				$payment = $this->checkout->transaction->last_payment();
2847
+				// $payment_source = 'last_payment';
2848
+			}
2849
+		} catch (Exception $e) {
2850
+			// let's just eat the exception and try to move on using any previously set payment info
2851
+			$payment = $this->checkout->transaction->last_payment();
2852
+			// $payment_source = 'last_payment after Exception';
2853
+			// but if we STILL don't have a payment object
2854
+			if (! $payment instanceof EE_Payment) {
2855
+				// then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2856
+				$this->_handle_payment_processor_exception($e);
2857
+			}
2858
+		}
2859
+		return $payment;
2860
+	}
2861
+
2862
+
2863
+	/**
2864
+	 * _process_cancelled_payments
2865
+	 * just makes sure that the payment status gets updated correctly
2866
+	 * so tha tan error isn't generated during payment validation
2867
+	 *
2868
+	 * @param EE_Payment $payment
2869
+	 * @return EE_Payment|null
2870
+	 * @throws EE_Error
2871
+	 */
2872
+	private function _process_cancelled_payments($payment = null)
2873
+	{
2874
+		if (
2875
+			$payment instanceof EE_Payment
2876
+			&& $this->request->requestParamIsSet('ee_cancel_payment')
2877
+			&& $payment->status() === EEM_Payment::status_id_failed
2878
+		) {
2879
+			$payment->set_status(EEM_Payment::status_id_cancelled);
2880
+		}
2881
+		return $payment;
2882
+	}
2883
+
2884
+
2885
+	/**
2886
+	 *    get_transaction_details_for_gateways
2887
+	 *
2888
+	 * @access    public
2889
+	 * @return void
2890
+	 * @throws EE_Error
2891
+	 * @throws InvalidArgumentException
2892
+	 * @throws ReflectionException
2893
+	 * @throws InvalidDataTypeException
2894
+	 * @throws InvalidInterfaceException
2895
+	 */
2896
+	public function get_transaction_details_for_gateways()
2897
+	{
2898
+		$txn_details = [];
2899
+		// ya gotta make a choice man
2900
+		if (empty($this->checkout->selected_method_of_payment)) {
2901
+			$txn_details = [
2902
+				'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2903
+			];
2904
+		}
2905
+		// get EE_Payment_Method object
2906
+		if (
2907
+			empty($txn_details)
2908
+			&& ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2909
+		) {
2910
+			$txn_details = [
2911
+				'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2912
+				'error'                      => esc_html__(
2913
+					'A valid Payment Method could not be determined.',
2914
+					'event_espresso'
2915
+				),
2916
+			];
2917
+		}
2918
+		if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2919
+			$return_url  = $this->_get_return_url($this->checkout->payment_method);
2920
+			$txn_details = [
2921
+				'TXN_ID'         => $this->checkout->transaction->ID(),
2922
+				'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2923
+				'TXN_total'      => $this->checkout->transaction->total(),
2924
+				'TXN_paid'       => $this->checkout->transaction->paid(),
2925
+				'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2926
+				'STS_ID'         => $this->checkout->transaction->status_ID(),
2927
+				'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2928
+				'payment_amount' => $this->checkout->amount_owing,
2929
+				'return_url'     => $return_url,
2930
+				'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2931
+				'notify_url'     => EE_Config::instance()->core->txn_page_url(
2932
+					[
2933
+						'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2934
+						'ee_payment_method' => $this->checkout->payment_method->slug(),
2935
+					]
2936
+				),
2937
+			];
2938
+		}
2939
+		echo wp_json_encode($txn_details);
2940
+		exit();
2941
+	}
2942
+
2943
+
2944
+	/**
2945
+	 *    __sleep
2946
+	 * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2947
+	 * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2948
+	 * reg form, because if needed, it will be regenerated anyways
2949
+	 *
2950
+	 * @return array
2951
+	 */
2952
+	public function __sleep()
2953
+	{
2954
+		// remove the reg form and the checkout
2955
+		return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2956
+	}
2957 2957
 }
Please login to merge, or discard this patch.
modules/add_new_state/EED_Add_New_State.module.php 2 patches
Indentation   +800 added lines, -800 removed lines patch added patch discarded remove patch
@@ -17,804 +17,804 @@
 block discarded – undo
17 17
  */
18 18
 class EED_Add_New_State extends EED_Module
19 19
 {
20
-    /**
21
-     * @return EED_Add_New_State|EED_Module
22
-     * @throws EE_Error
23
-     * @throws ReflectionException
24
-     */
25
-    public static function instance()
26
-    {
27
-        return parent::get_instance(__CLASS__);
28
-    }
29
-
30
-
31
-    /**
32
-     * set_hooks - for hooking into EE Core, other modules, etc
33
-     *
34
-     * @return void
35
-     */
36
-    public static function set_hooks()
37
-    {
38
-        add_action('wp_loaded', ['EED_Add_New_State', 'set_definitions'], 2);
39
-        add_action('wp_enqueue_scripts', ['EED_Add_New_State', 'translate_js_strings'], 0);
40
-        add_action('wp_enqueue_scripts', ['EED_Add_New_State', 'wp_enqueue_scripts']);
41
-        add_filter(
42
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
43
-            ['EED_Add_New_State', 'display_add_new_state_micro_form'],
44
-            1
45
-        );
46
-        add_filter(
47
-            'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
48
-            ['EED_Add_New_State', 'display_add_new_state_micro_form'],
49
-            1
50
-        );
51
-        add_filter(
52
-            'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
53
-            ['EED_Add_New_State', 'unset_new_state_request_params']
54
-        );
55
-        add_filter(
56
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
57
-            ['EED_Add_New_State', 'inject_new_reg_state_into_options'],
58
-            10,
59
-            5
60
-        );
61
-        add_filter(
62
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
63
-            ['EED_Add_New_State', 'inject_new_reg_country_into_options'],
64
-            10,
65
-            5
66
-        );
67
-        add_filter(
68
-            'FHEE__EE_State_Select_Input____construct__state_options',
69
-            ['EED_Add_New_State', 'state_options']
70
-        );
71
-        add_filter(
72
-            'FHEE__EE_Country_Select_Input____construct__country_options',
73
-            ['EED_Add_New_State', 'country_options']
74
-        );
75
-    }
76
-
77
-
78
-    /**
79
-     * set_hooks_admin - for hooking into EE Admin Core, other modules, etc
80
-     *
81
-     * @return void
82
-     */
83
-    public static function set_hooks_admin()
84
-    {
85
-        add_action('wp_loaded', ['EED_Add_New_State', 'set_definitions'], 2);
86
-        add_filter(
87
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
88
-            ['EED_Add_New_State', 'display_add_new_state_micro_form'],
89
-            1
90
-        );
91
-        add_filter(
92
-            'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
93
-            ['EED_Add_New_State', 'display_add_new_state_micro_form'],
94
-            1
95
-        );
96
-        add_action('wp_ajax_espresso_add_new_state', ['EED_Add_New_State', 'add_new_state']);
97
-        add_action('wp_ajax_nopriv_espresso_add_new_state', ['EED_Add_New_State', 'add_new_state']);
98
-        add_filter(
99
-            'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
100
-            ['EED_Add_New_State', 'unset_new_state_request_params']
101
-        );
102
-        add_action(
103
-            'AHEE__General_Settings_Admin_Page__update_country_settings__state_saved',
104
-            ['EED_Add_New_State', 'update_country_settings'],
105
-            10,
106
-            3
107
-        );
108
-        add_action(
109
-            'AHEE__General_Settings_Admin_Page__delete_state__state_deleted',
110
-            ['EED_Add_New_State', 'update_country_settings'],
111
-            10,
112
-            3
113
-        );
114
-        add_filter(
115
-            'FHEE__EE_State_Select_Input____construct__state_options',
116
-            ['EED_Add_New_State', 'state_options']
117
-        );
118
-        add_filter(
119
-            'FHEE__EE_Country_Select_Input____construct__country_options',
120
-            ['EED_Add_New_State', 'country_options']
121
-        );
122
-        add_filter(
123
-            'FHEE__EE_Form_Section_Proper__receive_form_submission__request_data',
124
-            ['EED_Add_New_State', 'filter_checkout_request_params']
125
-        );
126
-        add_filter(
127
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
128
-            ['EED_Add_New_State', 'inject_new_reg_state_into_options'],
129
-            10,
130
-            5
131
-        );
132
-        add_filter(
133
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
134
-            ['EED_Add_New_State', 'inject_new_reg_country_into_options'],
135
-            10,
136
-            5
137
-        );
138
-    }
139
-
140
-
141
-    /**
142
-     * @return void
143
-     */
144
-    public static function set_definitions()
145
-    {
146
-        define('ANS_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets/');
147
-        define(
148
-            'ANS_TEMPLATES_PATH',
149
-            str_replace(
150
-                '\\',
151
-                '/',
152
-                plugin_dir_path(__FILE__)
153
-            ) . 'templates/'
154
-        );
155
-    }
156
-
157
-
158
-    /**
159
-     * @param WP $WP
160
-     * @return void
161
-     */
162
-    public function run($WP)
163
-    {
164
-    }
165
-
166
-
167
-    /**
168
-     * @return void
169
-     */
170
-    public static function translate_js_strings()
171
-    {
172
-        EE_Registry::$i18n_js_strings['ans_no_country']        = esc_html__(
173
-            'In order to proceed, you need to select the Country that your State/Province belongs to.',
174
-            'event_espresso'
175
-        );
176
-        EE_Registry::$i18n_js_strings['ans_no_name']           = esc_html__(
177
-            'In order to proceed, you need to enter the name of your State/Province.',
178
-            'event_espresso'
179
-        );
180
-        EE_Registry::$i18n_js_strings['ans_no_abbreviation']   = esc_html__(
181
-            'In order to proceed, you need to enter an abbreviation for the name of your State/Province.',
182
-            'event_espresso'
183
-        );
184
-        EE_Registry::$i18n_js_strings['ans_save_success']      = esc_html__(
185
-            'The new state was successfully saved to the database.',
186
-            'event_espresso'
187
-        );
188
-        EE_Registry::$i18n_js_strings['ans_server_save_error'] = esc_html__(
189
-            'An unknown error has occurred on the server while saving the new state to the database.',
190
-            'event_espresso'
191
-        );
192
-    }
193
-
194
-
195
-    /**
196
-     * @return void
197
-     */
198
-    public static function wp_enqueue_scripts()
199
-    {
200
-        if (apply_filters('EED_Single_Page_Checkout__SPCO_active', false)) {
201
-            wp_register_script(
202
-                'add_new_state',
203
-                ANS_ASSETS_URL . 'add_new_state.js',
204
-                ['espresso_core', 'single_page_checkout'],
205
-                EVENT_ESPRESSO_VERSION,
206
-                true
207
-            );
208
-            wp_enqueue_script('add_new_state');
209
-        }
210
-    }
211
-
212
-
213
-    /**
214
-     * display_add_new_state_micro_form
215
-     *
216
-     * @param EE_Form_Section_Proper $question_group_reg_form
217
-     * @return EE_Form_Section_Proper
218
-     * @throws EE_Error
219
-     * @throws ReflectionException
220
-     */
221
-    public static function display_add_new_state_micro_form(
222
-        EE_Form_Section_Proper $question_group_reg_form
223
-    ): EE_Form_Section_Proper {
224
-        $request = self::getRequest();
225
-        // only add the 'new_state_micro_form' when displaying reg forms,
226
-        // not during processing since we process the 'new_state_micro_form' in its own AJAX request
227
-        $action = $request->getRequestParam('action');
228
-        // is the "state" question in this form section?
229
-        $input = $question_group_reg_form->get_subsection('state');
230
-        if ($action === 'process_reg_step' || $action === 'update_reg_step') {
231
-            // ok then all we need to do is make sure the input's HTML name is consistent
232
-            // by forcing it to set it now, like it did while getting the form for display
233
-            if ($input instanceof EE_State_Select_Input) {
234
-                $input->html_name();
235
-            }
236
-            return $question_group_reg_form;
237
-        }
238
-        // we're only doing this for state select inputs
239
-        if (
240
-            $input instanceof EE_State_Select_Input
241
-            && ! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy
242
-        ) {
243
-            // grab any set values from the request
244
-            $country_name        = str_replace('state', 'nsmf_new_state_country', $input->html_name());
245
-            $state_name          = str_replace('state', 'nsmf_new_state_name', $input->html_name());
246
-            $abbrv_name          = str_replace('state', 'nsmf_new_state_abbrv', $input->html_name());
247
-            $new_state_submit_id = str_replace('state', 'new_state', $input->html_id());
248
-            $country_options     = [];
249
-            $countries           = EEM_Country::instance()->get_all_countries();
250
-            if (! empty($countries)) {
251
-                foreach ($countries as $country) {
252
-                    if ($country instanceof EE_Country) {
253
-                        $country_options[ $country->ID() ] = $country->name();
254
-                    }
255
-                }
256
-            }
257
-            $new_state_micro_form = new EE_Form_Section_Proper(
258
-                [
259
-                    'name'            => 'new_state_micro_form',
260
-                    'html_id'         => 'new_state_micro_form',
261
-                    'layout_strategy' => new EE_Div_Per_Section_Layout(),
262
-                    'subsections'     => [
263
-                        // add hidden input to indicate that a new state is being added
264
-                        'add_new_state'               => new EE_Hidden_Input(
265
-                            [
266
-                                'html_name' => str_replace(
267
-                                    'state',
268
-                                    'nsmf_add_new_state',
269
-                                    $input->html_name()
270
-                                ),
271
-                                'html_id'   => str_replace(
272
-                                    'state',
273
-                                    'nsmf_add_new_state',
274
-                                    $input->html_id()
275
-                                ),
276
-                                'default'   => 0,
277
-                            ]
278
-                        ),
279
-                        // add link for displaying hidden container
280
-                        'click_here_link'             => new EE_Form_Section_HTML(
281
-                            apply_filters(
282
-                                'FHEE__EED_Add_New_State__display_add_new_state_micro_form__click_here_link',
283
-                                EEH_HTML::link(
284
-                                    '',
285
-                                    esc_html__('click here to add a new state/province', 'event_espresso'),
286
-                                    '',
287
-                                    'display-' . $input->html_id(),
288
-                                    'ee-form-add-new-state-lnk display-the-hidden smaller-text hide-if-no-js',
289
-                                    '',
290
-                                    'data-target="' . $input->html_id() . '"'
291
-                                )
292
-                            )
293
-                        ),
294
-                        // add initial html for hidden container
295
-                        'add_new_state_micro_form'    => new EE_Form_Section_HTML(
296
-                            apply_filters(
297
-                                'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_micro_form',
298
-                                EEH_HTML::div(
299
-                                    '',
300
-                                    $input->html_id() . '-dv',
301
-                                    'ee-form-add-new-state-dv',
302
-                                    'display: none;'
303
-                                ) .
304
-                                EEH_HTML::h6(
305
-                                    esc_html__(
306
-                                        'Is your state/province missing from the dropdown menu above? You can add it by completing the following steps:',
307
-                                        'event_espresso'
308
-                                    )
309
-                                ) .
310
-                                EEH_HTML::ul() .
311
-                                EEH_HTML::li(
312
-                                    esc_html__(
313
-                                        'first select the Country that your State/Province belongs to',
314
-                                        'event_espresso'
315
-                                    )
316
-                                ) .
317
-                                EEH_HTML::li(
318
-                                    esc_html__('enter the name of your State/Province', 'event_espresso')
319
-                                ) .
320
-                                EEH_HTML::li(
321
-                                    esc_html__(
322
-                                        'enter a two to six letter abbreviation for the name of your State/Province',
323
-                                        'event_espresso'
324
-                                    )
325
-                                ) .
326
-                                EEH_HTML::li(esc_html__('click the ADD button', 'event_espresso')) .
327
-                                EEH_HTML::ulx()
328
-                            )
329
-                        ),
330
-                        // NEW STATE COUNTRY
331
-                        'new_state_country'           => new EE_Country_Select_Input(
332
-                            $country_options,
333
-                            [
334
-                                'html_name'       => $country_name,
335
-                                'html_id'         => str_replace(
336
-                                    'state',
337
-                                    'nsmf_new_state_country',
338
-                                    $input->html_id()
339
-                                ),
340
-                                'html_class'      => $input->html_class() . ' new-state-country',
341
-                                'html_label_text' => esc_html__('New State/Province Country', 'event_espresso'),
342
-                                'default'         => $request->getRequestParam($country_name),
343
-                                'required'        => false,
344
-                            ]
345
-                        ),
346
-                        // NEW STATE NAME
347
-                        'new_state_name'              => new EE_Text_Input(
348
-                            [
349
-                                'html_name'       => $state_name,
350
-                                'html_id'         => str_replace(
351
-                                    'state',
352
-                                    'nsmf_new_state_name',
353
-                                    $input->html_id()
354
-                                ),
355
-                                'html_class'      => $input->html_class() . ' new-state-state',
356
-                                'html_label_text' => esc_html__(
357
-                                    'New State/Province Name',
358
-                                    'event_espresso'
359
-                                ),
360
-                                'default'         => $request->getRequestParam($state_name),
361
-                                'required'        => false,
362
-                            ]
363
-                        ),
364
-                        // NEW STATE NAME
365
-                        'new_state_abbrv'             => new EE_Text_Input(
366
-                            [
367
-                                'html_name'             => $abbrv_name,
368
-                                'html_id'               => str_replace(
369
-                                    'state',
370
-                                    'nsmf_new_state_abbrv',
371
-                                    $input->html_id()
372
-                                ),
373
-                                'html_class'            => $input->html_class() . ' new-state-abbrv',
374
-                                'html_label_text'       => esc_html__(
375
-                                    'New State/Province Abbreviation',
376
-                                    'event_espresso'
377
-                                ) . ' *',
378
-                                'other_html_attributes' => 'size="24"',
379
-                                'default'               => $request->getRequestParam($abbrv_name),
380
-                                'required'              => false,
381
-                            ]
382
-                        ),
383
-                        // "submit" button
384
-                        'add_new_state_submit_button' => new EE_Form_Section_HTML(
385
-                            apply_filters(
386
-                                'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_submit_button',
387
-                                EEH_HTML::div(
388
-                                    EEH_HTML::button(
389
-                                        esc_html__('ADD', 'event_espresso'),
390
-                                        'ee-form-add-new-state-submit button button--secondary',
391
-                                        '',
392
-                                        'submit-' . $new_state_submit_id,
393
-                                        '',
394
-                                        'data-target="' . $new_state_submit_id . '"'
395
-                                        . ' data-value-field-name="' . $input->valueFieldName() . '"'
396
-                                    ),
397
-                                    '',
398
-                                    'ee-form-add-new-state-submit-dv'
399
-                                )
400
-                            )
401
-                        ),
402
-                        // extra info
403
-                        'add_new_state_extra'         => new EE_Form_Section_HTML(
404
-                            apply_filters(
405
-                                'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_extra',
406
-                                EEH_HTML::br()
407
-                                .
408
-                                EEH_HTML::div('', '', 'small-text')
409
-                                .
410
-                                EEH_HTML::strong(
411
-                                    '* ' .
412
-                                    esc_html__(
413
-                                        'Don\'t know your State/Province Abbreviation?',
414
-                                        'event_espresso'
415
-                                    )
416
-                                )
417
-                                .
418
-                                EEH_HTML::br()
419
-                                .
420
-                                sprintf(
421
-                                    esc_html__(
422
-                                        'You can look here: %s, for a list of Countries and links to their State/Province Abbreviations ("Subdivisions assigned codes" column).',
423
-                                        'event_espresso'
424
-                                    ),
425
-                                    EEH_HTML::link(
426
-                                        'https://en.wikipedia.org/wiki/ISO_3166-2',
427
-                                        'https://en.wikipedia.org/wiki/ISO_3166-2',
428
-                                        '',
429
-                                        '',
430
-                                        'ee-form-add-new-state-wiki-lnk',
431
-                                        '',
432
-                                        'target="_blank"'
433
-                                    )
434
-                                )
435
-                                .
436
-                                EEH_HTML::divx()
437
-                                .
438
-                                EEH_HTML::br()
439
-                                .
440
-                                EEH_HTML::link(
441
-                                    '',
442
-                                    esc_html__('cancel new State/Province', 'event_espresso'),
443
-                                    '',
444
-                                    'hide-' . $input->html_id(),
445
-                                    'ee-form-cancel-new-state-lnk smaller-text',
446
-                                    '',
447
-                                    'data-target="' . $input->html_id() . '"'
448
-                                )
449
-                                .
450
-                                EEH_HTML::divx()
451
-                                .
452
-                                EEH_HTML::br()
453
-                            )
454
-                        ),
455
-                    ],
456
-                ]
457
-            );
458
-            $question_group_reg_form->add_subsections(
459
-                ['new_state_micro_form' => $new_state_micro_form],
460
-                'state',
461
-                false
462
-            );
463
-        }
464
-        return $question_group_reg_form;
465
-    }
466
-
467
-
468
-    /**
469
-     * set_new_state_input_width
470
-     *
471
-     * @return int|string
472
-     * @throws EE_Error
473
-     * @throws InvalidArgumentException
474
-     * @throws InvalidDataTypeException
475
-     * @throws InvalidInterfaceException
476
-     * @throws ReflectionException
477
-     */
478
-    public static function add_new_state()
479
-    {
480
-        $request = self::getRequest();
481
-        if ($request->getRequestParam('nsmf_add_new_state', 0, 'int') === 1) {
482
-            EE_Registry::instance()->load_model('State');
483
-            // grab country ISO code, new state name, and new state abbreviation
484
-            $state_country = $request->getRequestParam('nsmf_new_state_country');
485
-            $state_name    = $request->getRequestParam('nsmf_new_state_name');
486
-            $state_abbr    = $request->getRequestParam('nsmf_new_state_abbrv');
487
-            if ($state_country && $state_name && $state_abbr) {
488
-                $new_state = EED_Add_New_State::save_new_state_to_db(
489
-                    [
490
-                        'CNT_ISO'    => strtoupper($state_country),
491
-                        'STA_abbrev' => strtoupper($state_abbr),
492
-                        'STA_name'   => ucwords($state_name),
493
-                        'STA_active' => false,
494
-                    ]
495
-                );
496
-                if ($new_state instanceof EE_State) {
497
-                    // clean house
498
-                    $request->unSetRequestParams(
499
-                        [
500
-                            'nsmf_add_new_state',
501
-                            'nsmf_new_state_country',
502
-                            'nsmf_new_state_name',
503
-                            'nsmf_new_state_abbrv',
504
-                        ]
505
-                    );
506
-                    // get any existing new states
507
-                    $new_states                     = EE_Registry::instance()->SSN->get_session_data('nsmf_new_states');
508
-                    $new_states[ $new_state->ID() ] = $new_state;
509
-                    EE_Registry::instance()->SSN->set_session_data(
510
-                        ['nsmf_new_states' => $new_states]
511
-                    );
512
-                    if ($request->isAjax()) {
513
-                        echo wp_json_encode(
514
-                            [
515
-                                'success'      => true,
516
-                                'id'           => $new_state->ID(),
517
-                                'name'         => $new_state->name(),
518
-                                'abbrev'       => $new_state->abbrev(),
519
-                                'country_iso'  => $new_state->country_iso(),
520
-                                'country_name' => $new_state->country()->name(),
521
-                            ]
522
-                        );
523
-                        exit();
524
-                    }
525
-                    return $new_state->ID();
526
-                }
527
-            } else {
528
-                $error = esc_html__(
529
-                    'A new State/Province could not be added because invalid or missing data was received.',
530
-                    'event_espresso'
531
-                );
532
-                if ($request->isAjax()) {
533
-                    echo wp_json_encode(['error' => $error]);
534
-                    exit();
535
-                }
536
-                EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
537
-            }
538
-        }
539
-        return false;
540
-    }
541
-
542
-
543
-    /**
544
-     * recursively drills down through request params to remove any that were added by this module
545
-     *
546
-     * @param array $request_params
547
-     * @return array
548
-     */
549
-    public static function filter_checkout_request_params(array $request_params): array
550
-    {
551
-        foreach ($request_params as $form_section) {
552
-            if (is_array($form_section)) {
553
-                EED_Add_New_State::unset_new_state_request_params($form_section);
554
-                EED_Add_New_State::filter_checkout_request_params($form_section);
555
-            }
556
-        }
557
-        return $request_params;
558
-    }
559
-
560
-
561
-    /**
562
-     * @param array $request_params
563
-     * @return array
564
-     */
565
-    public static function unset_new_state_request_params(array $request_params): array
566
-    {
567
-        unset(
568
-            $request_params['new_state_micro_form'],
569
-            $request_params['new_state_micro_add_new_state'],
570
-            $request_params['new_state_micro_new_state_country'],
571
-            $request_params['new_state_micro_new_state_name'],
572
-            $request_params['new_state_micro_new_state_abbrv']
573
-        );
574
-        return $request_params;
575
-    }
576
-
577
-
578
-    /**
579
-     * @param array $props_n_values
580
-     * @return EE_State|null
581
-     * @throws EE_Error
582
-     * @throws ReflectionException
583
-     */
584
-    public static function save_new_state_to_db(array $props_n_values = []): ?EE_State
585
-    {
586
-        /** @var EE_State[] $existing_state */
587
-        $existing_state = EEM_State::instance()->get_all([$props_n_values, 'limit' => 1]);
588
-        if (! empty($existing_state)) {
589
-            return array_pop($existing_state);
590
-        }
591
-        $new_state = EE_State::new_instance($props_n_values);
592
-        if ($new_state instanceof EE_State) {
593
-            $country_settings_url = add_query_arg(
594
-                [
595
-                    'page'    => 'espresso_general_settings',
596
-                    'action'  => 'country_settings',
597
-                    'country' => $new_state->country_iso(),
598
-                ],
599
-                admin_url('admin.php')
600
-            );
601
-            // if not non-ajax admin
602
-            new PersistentAdminNotice(
603
-                'new-state-added-' . $new_state->country_iso() . '-' . $new_state->abbrev(),
604
-                sprintf(
605
-                    esc_html__(
606
-                        'A new State named "%1$s (%2$s)" was dynamically added from an Event Espresso form for the Country of "%3$s".%5$sTo verify, edit, and/or delete this new State, please go to the %4$s and update the States / Provinces section.%5$sCheck "Yes" to have this new State added to dropdown select lists in forms.',
607
-                        'event_espresso'
608
-                    ),
609
-                    '<b>' . $new_state->name() . '</b>',
610
-                    '<b>' . $new_state->abbrev() . '</b>',
611
-                    '<b>' . $new_state->country()->name() . '</b>',
612
-                    '<a href="'
613
-                    . $country_settings_url
614
-                    . '">'
615
-                    . esc_html__(
616
-                        'Event Espresso - General Settings > Countries Tab',
617
-                        'event_espresso'
618
-                    )
619
-                    . '</a>',
620
-                    '<br />'
621
-                )
622
-            );
623
-            $new_state->save();
624
-            EEM_State::instance()->reset_cached_states();
625
-            return $new_state;
626
-        }
627
-        return null;
628
-    }
629
-
630
-
631
-    /**
632
-     * @param string $CNT_ISO
633
-     * @param int    $STA_ID
634
-     * @param array  $cols_n_values
635
-     * @return void
636
-     * @throws DomainException
637
-     * @throws EE_Error
638
-     * @throws InvalidArgumentException
639
-     * @throws InvalidDataTypeException
640
-     * @throws InvalidInterfaceException
641
-     * @throws ReflectionException
642
-     */
643
-    public static function update_country_settings(string $CNT_ISO = '', int $STA_ID = 0, array $cols_n_values = [])
644
-    {
645
-        /** @var EE_Capabilities $capabilities */
646
-        $capabilities = LoaderFactory::getLoader()->getShared(EE_Capabilities::class);
647
-        if (! $capabilities->current_user_can('manage_options', 'update-country-settings')) {
648
-            wp_die(esc_html__('You do not have the required privileges to perform this action', 'event_espresso'));
649
-        }
650
-        if (! $CNT_ISO) {
651
-            EE_Error::add_error(
652
-                esc_html__('An invalid or missing Country ISO Code was received.', 'event_espresso'),
653
-                __FILE__,
654
-                __FUNCTION__,
655
-                __LINE__
656
-            );
657
-        }
658
-        $STA_abbrev = is_array($cols_n_values) && isset($cols_n_values['STA_abbrev']) ? $cols_n_values['STA_abbrev']
659
-            : false;
660
-        if (! $STA_abbrev && ! empty($STA_ID)) {
661
-            $state = EEM_State::instance()->get_one_by_ID($STA_ID);
662
-            if ($state instanceof EE_State) {
663
-                $STA_abbrev = $state->abbrev();
664
-            }
665
-        }
666
-        if (! $STA_abbrev) {
667
-            EE_Error::add_error(
668
-                esc_html__('An invalid or missing State Abbreviation was received.', 'event_espresso'),
669
-                __FILE__,
670
-                __FUNCTION__,
671
-                __LINE__
672
-            );
673
-        }
674
-        /** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
675
-        $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
676
-            'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
677
-        );
678
-        $persistent_admin_notice_manager->dismissNotice($CNT_ISO . '-' . $STA_abbrev, true, true);
679
-    }
680
-
681
-
682
-    /**
683
-     * @param EE_State[]                                              $state_options
684
-     * @param EE_SPCO_Reg_Step_Attendee_Information|StateOptions|null $deprecated
685
-     * @param EE_Registration                                         $registration
686
-     * @param EE_Question|null                                        $question
687
-     * @param EE_Answer|null                                          $answer
688
-     * @return array
689
-     * @throws EE_Error
690
-     * @throws ReflectionException
691
-     */
692
-    public static function inject_new_reg_state_into_options(
693
-        array $state_options,
694
-        $deprecated,
695
-        EE_Registration $registration,
696
-        ?EE_Question $question,
697
-        ?EE_Answer $answer
698
-    ): array {
699
-        if (
700
-            $answer instanceof EE_Answer && $question instanceof EE_Question
701
-            && $question->type() === EEM_Question::QST_type_state
702
-        ) {
703
-            $STA_ID = $answer->value();
704
-            if (! empty($STA_ID)) {
705
-                $state = EEM_State::instance()->get_one_by_ID($STA_ID);
706
-                if ($state instanceof EE_State) {
707
-                    $country = $state->country();
708
-                    if ($country instanceof EE_Country) {
709
-                        if (! isset($state_options[ $country->name() ])) {
710
-                            $state_options[ $country->name() ] = [];
711
-                        }
712
-                        if (! isset($state_options[ $country->name() ][ $STA_ID ])) {
713
-                            $state_options[ $country->name() ][ $STA_ID ] = $state->name();
714
-                        }
715
-                    }
716
-                }
717
-            }
718
-        }
719
-        return $state_options;
720
-    }
721
-
722
-
723
-    /**
724
-     * @param EE_Country[]                                              $country_options
725
-     * @param EE_SPCO_Reg_Step_Attendee_Information|CountryOptions|null $deprecated
726
-     * @param EE_Registration                                           $registration
727
-     * @param EE_Question|null                                          $question
728
-     * @param EE_Answer|null                                            $answer
729
-     * @return array
730
-     * @throws EE_Error
731
-     * @throws InvalidArgumentException
732
-     * @throws InvalidDataTypeException
733
-     * @throws InvalidInterfaceException
734
-     * @throws ReflectionException
735
-     */
736
-    public static function inject_new_reg_country_into_options(
737
-        array $country_options,
738
-        $deprecated,
739
-        EE_Registration $registration,
740
-        ?EE_Question $question,
741
-        ?EE_Answer $answer
742
-    ): array {
743
-        if (
744
-            $answer instanceof EE_Answer
745
-            && $question instanceof EE_Question
746
-            && $question->type() === EEM_Question::QST_type_country
747
-        ) {
748
-            $CNT_ISO = $answer->value();
749
-            if (! empty($CNT_ISO)) {
750
-                $country = EEM_Country::instance()->get_one_by_ID($CNT_ISO);
751
-                if ($country instanceof EE_Country) {
752
-                    if (! isset($country_options[ $CNT_ISO ])) {
753
-                        $country_options[ $CNT_ISO ] = $country->name();
754
-                    }
755
-                }
756
-            }
757
-        }
758
-        return $country_options;
759
-    }
760
-
761
-
762
-    /**
763
-     * @param EE_State[] $state_options
764
-     * @return array
765
-     * @throws EE_Error
766
-     * @throws ReflectionException
767
-     */
768
-    public static function state_options(array $state_options = []): array
769
-    {
770
-        $new_states = EED_Add_New_State::_get_new_states();
771
-        foreach ($new_states as $new_state) {
772
-            if (
773
-                $new_state instanceof EE_State
774
-                && $new_state->country() instanceof EE_Country
775
-            ) {
776
-                $state_options[ $new_state->country()->name() ][ $new_state->ID() ] = $new_state->name();
777
-            }
778
-        }
779
-        return $state_options;
780
-    }
781
-
782
-
783
-    /**
784
-     * @return array
785
-     * @throws InvalidArgumentException
786
-     * @throws InvalidDataTypeException
787
-     * @throws InvalidInterfaceException
788
-     */
789
-    protected static function _get_new_states(): array
790
-    {
791
-        $new_states = [];
792
-        if (EE_Registry::instance()->SSN instanceof EE_Session) {
793
-            $new_states = EE_Registry::instance()->SSN->get_session_data(
794
-                'nsmf_new_states'
795
-            );
796
-        }
797
-        return is_array($new_states) ? $new_states : [];
798
-    }
799
-
800
-
801
-    /**
802
-     * @param EE_Country[] $country_options
803
-     * @return array
804
-     * @throws EE_Error
805
-     * @throws ReflectionException
806
-     */
807
-    public static function country_options(array $country_options = []): array
808
-    {
809
-        $new_states = EED_Add_New_State::_get_new_states();
810
-        foreach ($new_states as $new_state) {
811
-            if (
812
-                $new_state instanceof EE_State
813
-                && $new_state->country() instanceof EE_Country
814
-            ) {
815
-                $country_options[ $new_state->country()->ID() ] = $new_state->country()->name();
816
-            }
817
-        }
818
-        return $country_options;
819
-    }
20
+	/**
21
+	 * @return EED_Add_New_State|EED_Module
22
+	 * @throws EE_Error
23
+	 * @throws ReflectionException
24
+	 */
25
+	public static function instance()
26
+	{
27
+		return parent::get_instance(__CLASS__);
28
+	}
29
+
30
+
31
+	/**
32
+	 * set_hooks - for hooking into EE Core, other modules, etc
33
+	 *
34
+	 * @return void
35
+	 */
36
+	public static function set_hooks()
37
+	{
38
+		add_action('wp_loaded', ['EED_Add_New_State', 'set_definitions'], 2);
39
+		add_action('wp_enqueue_scripts', ['EED_Add_New_State', 'translate_js_strings'], 0);
40
+		add_action('wp_enqueue_scripts', ['EED_Add_New_State', 'wp_enqueue_scripts']);
41
+		add_filter(
42
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
43
+			['EED_Add_New_State', 'display_add_new_state_micro_form'],
44
+			1
45
+		);
46
+		add_filter(
47
+			'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
48
+			['EED_Add_New_State', 'display_add_new_state_micro_form'],
49
+			1
50
+		);
51
+		add_filter(
52
+			'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
53
+			['EED_Add_New_State', 'unset_new_state_request_params']
54
+		);
55
+		add_filter(
56
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
57
+			['EED_Add_New_State', 'inject_new_reg_state_into_options'],
58
+			10,
59
+			5
60
+		);
61
+		add_filter(
62
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
63
+			['EED_Add_New_State', 'inject_new_reg_country_into_options'],
64
+			10,
65
+			5
66
+		);
67
+		add_filter(
68
+			'FHEE__EE_State_Select_Input____construct__state_options',
69
+			['EED_Add_New_State', 'state_options']
70
+		);
71
+		add_filter(
72
+			'FHEE__EE_Country_Select_Input____construct__country_options',
73
+			['EED_Add_New_State', 'country_options']
74
+		);
75
+	}
76
+
77
+
78
+	/**
79
+	 * set_hooks_admin - for hooking into EE Admin Core, other modules, etc
80
+	 *
81
+	 * @return void
82
+	 */
83
+	public static function set_hooks_admin()
84
+	{
85
+		add_action('wp_loaded', ['EED_Add_New_State', 'set_definitions'], 2);
86
+		add_filter(
87
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
88
+			['EED_Add_New_State', 'display_add_new_state_micro_form'],
89
+			1
90
+		);
91
+		add_filter(
92
+			'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
93
+			['EED_Add_New_State', 'display_add_new_state_micro_form'],
94
+			1
95
+		);
96
+		add_action('wp_ajax_espresso_add_new_state', ['EED_Add_New_State', 'add_new_state']);
97
+		add_action('wp_ajax_nopriv_espresso_add_new_state', ['EED_Add_New_State', 'add_new_state']);
98
+		add_filter(
99
+			'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
100
+			['EED_Add_New_State', 'unset_new_state_request_params']
101
+		);
102
+		add_action(
103
+			'AHEE__General_Settings_Admin_Page__update_country_settings__state_saved',
104
+			['EED_Add_New_State', 'update_country_settings'],
105
+			10,
106
+			3
107
+		);
108
+		add_action(
109
+			'AHEE__General_Settings_Admin_Page__delete_state__state_deleted',
110
+			['EED_Add_New_State', 'update_country_settings'],
111
+			10,
112
+			3
113
+		);
114
+		add_filter(
115
+			'FHEE__EE_State_Select_Input____construct__state_options',
116
+			['EED_Add_New_State', 'state_options']
117
+		);
118
+		add_filter(
119
+			'FHEE__EE_Country_Select_Input____construct__country_options',
120
+			['EED_Add_New_State', 'country_options']
121
+		);
122
+		add_filter(
123
+			'FHEE__EE_Form_Section_Proper__receive_form_submission__request_data',
124
+			['EED_Add_New_State', 'filter_checkout_request_params']
125
+		);
126
+		add_filter(
127
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
128
+			['EED_Add_New_State', 'inject_new_reg_state_into_options'],
129
+			10,
130
+			5
131
+		);
132
+		add_filter(
133
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
134
+			['EED_Add_New_State', 'inject_new_reg_country_into_options'],
135
+			10,
136
+			5
137
+		);
138
+	}
139
+
140
+
141
+	/**
142
+	 * @return void
143
+	 */
144
+	public static function set_definitions()
145
+	{
146
+		define('ANS_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets/');
147
+		define(
148
+			'ANS_TEMPLATES_PATH',
149
+			str_replace(
150
+				'\\',
151
+				'/',
152
+				plugin_dir_path(__FILE__)
153
+			) . 'templates/'
154
+		);
155
+	}
156
+
157
+
158
+	/**
159
+	 * @param WP $WP
160
+	 * @return void
161
+	 */
162
+	public function run($WP)
163
+	{
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return void
169
+	 */
170
+	public static function translate_js_strings()
171
+	{
172
+		EE_Registry::$i18n_js_strings['ans_no_country']        = esc_html__(
173
+			'In order to proceed, you need to select the Country that your State/Province belongs to.',
174
+			'event_espresso'
175
+		);
176
+		EE_Registry::$i18n_js_strings['ans_no_name']           = esc_html__(
177
+			'In order to proceed, you need to enter the name of your State/Province.',
178
+			'event_espresso'
179
+		);
180
+		EE_Registry::$i18n_js_strings['ans_no_abbreviation']   = esc_html__(
181
+			'In order to proceed, you need to enter an abbreviation for the name of your State/Province.',
182
+			'event_espresso'
183
+		);
184
+		EE_Registry::$i18n_js_strings['ans_save_success']      = esc_html__(
185
+			'The new state was successfully saved to the database.',
186
+			'event_espresso'
187
+		);
188
+		EE_Registry::$i18n_js_strings['ans_server_save_error'] = esc_html__(
189
+			'An unknown error has occurred on the server while saving the new state to the database.',
190
+			'event_espresso'
191
+		);
192
+	}
193
+
194
+
195
+	/**
196
+	 * @return void
197
+	 */
198
+	public static function wp_enqueue_scripts()
199
+	{
200
+		if (apply_filters('EED_Single_Page_Checkout__SPCO_active', false)) {
201
+			wp_register_script(
202
+				'add_new_state',
203
+				ANS_ASSETS_URL . 'add_new_state.js',
204
+				['espresso_core', 'single_page_checkout'],
205
+				EVENT_ESPRESSO_VERSION,
206
+				true
207
+			);
208
+			wp_enqueue_script('add_new_state');
209
+		}
210
+	}
211
+
212
+
213
+	/**
214
+	 * display_add_new_state_micro_form
215
+	 *
216
+	 * @param EE_Form_Section_Proper $question_group_reg_form
217
+	 * @return EE_Form_Section_Proper
218
+	 * @throws EE_Error
219
+	 * @throws ReflectionException
220
+	 */
221
+	public static function display_add_new_state_micro_form(
222
+		EE_Form_Section_Proper $question_group_reg_form
223
+	): EE_Form_Section_Proper {
224
+		$request = self::getRequest();
225
+		// only add the 'new_state_micro_form' when displaying reg forms,
226
+		// not during processing since we process the 'new_state_micro_form' in its own AJAX request
227
+		$action = $request->getRequestParam('action');
228
+		// is the "state" question in this form section?
229
+		$input = $question_group_reg_form->get_subsection('state');
230
+		if ($action === 'process_reg_step' || $action === 'update_reg_step') {
231
+			// ok then all we need to do is make sure the input's HTML name is consistent
232
+			// by forcing it to set it now, like it did while getting the form for display
233
+			if ($input instanceof EE_State_Select_Input) {
234
+				$input->html_name();
235
+			}
236
+			return $question_group_reg_form;
237
+		}
238
+		// we're only doing this for state select inputs
239
+		if (
240
+			$input instanceof EE_State_Select_Input
241
+			&& ! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy
242
+		) {
243
+			// grab any set values from the request
244
+			$country_name        = str_replace('state', 'nsmf_new_state_country', $input->html_name());
245
+			$state_name          = str_replace('state', 'nsmf_new_state_name', $input->html_name());
246
+			$abbrv_name          = str_replace('state', 'nsmf_new_state_abbrv', $input->html_name());
247
+			$new_state_submit_id = str_replace('state', 'new_state', $input->html_id());
248
+			$country_options     = [];
249
+			$countries           = EEM_Country::instance()->get_all_countries();
250
+			if (! empty($countries)) {
251
+				foreach ($countries as $country) {
252
+					if ($country instanceof EE_Country) {
253
+						$country_options[ $country->ID() ] = $country->name();
254
+					}
255
+				}
256
+			}
257
+			$new_state_micro_form = new EE_Form_Section_Proper(
258
+				[
259
+					'name'            => 'new_state_micro_form',
260
+					'html_id'         => 'new_state_micro_form',
261
+					'layout_strategy' => new EE_Div_Per_Section_Layout(),
262
+					'subsections'     => [
263
+						// add hidden input to indicate that a new state is being added
264
+						'add_new_state'               => new EE_Hidden_Input(
265
+							[
266
+								'html_name' => str_replace(
267
+									'state',
268
+									'nsmf_add_new_state',
269
+									$input->html_name()
270
+								),
271
+								'html_id'   => str_replace(
272
+									'state',
273
+									'nsmf_add_new_state',
274
+									$input->html_id()
275
+								),
276
+								'default'   => 0,
277
+							]
278
+						),
279
+						// add link for displaying hidden container
280
+						'click_here_link'             => new EE_Form_Section_HTML(
281
+							apply_filters(
282
+								'FHEE__EED_Add_New_State__display_add_new_state_micro_form__click_here_link',
283
+								EEH_HTML::link(
284
+									'',
285
+									esc_html__('click here to add a new state/province', 'event_espresso'),
286
+									'',
287
+									'display-' . $input->html_id(),
288
+									'ee-form-add-new-state-lnk display-the-hidden smaller-text hide-if-no-js',
289
+									'',
290
+									'data-target="' . $input->html_id() . '"'
291
+								)
292
+							)
293
+						),
294
+						// add initial html for hidden container
295
+						'add_new_state_micro_form'    => new EE_Form_Section_HTML(
296
+							apply_filters(
297
+								'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_micro_form',
298
+								EEH_HTML::div(
299
+									'',
300
+									$input->html_id() . '-dv',
301
+									'ee-form-add-new-state-dv',
302
+									'display: none;'
303
+								) .
304
+								EEH_HTML::h6(
305
+									esc_html__(
306
+										'Is your state/province missing from the dropdown menu above? You can add it by completing the following steps:',
307
+										'event_espresso'
308
+									)
309
+								) .
310
+								EEH_HTML::ul() .
311
+								EEH_HTML::li(
312
+									esc_html__(
313
+										'first select the Country that your State/Province belongs to',
314
+										'event_espresso'
315
+									)
316
+								) .
317
+								EEH_HTML::li(
318
+									esc_html__('enter the name of your State/Province', 'event_espresso')
319
+								) .
320
+								EEH_HTML::li(
321
+									esc_html__(
322
+										'enter a two to six letter abbreviation for the name of your State/Province',
323
+										'event_espresso'
324
+									)
325
+								) .
326
+								EEH_HTML::li(esc_html__('click the ADD button', 'event_espresso')) .
327
+								EEH_HTML::ulx()
328
+							)
329
+						),
330
+						// NEW STATE COUNTRY
331
+						'new_state_country'           => new EE_Country_Select_Input(
332
+							$country_options,
333
+							[
334
+								'html_name'       => $country_name,
335
+								'html_id'         => str_replace(
336
+									'state',
337
+									'nsmf_new_state_country',
338
+									$input->html_id()
339
+								),
340
+								'html_class'      => $input->html_class() . ' new-state-country',
341
+								'html_label_text' => esc_html__('New State/Province Country', 'event_espresso'),
342
+								'default'         => $request->getRequestParam($country_name),
343
+								'required'        => false,
344
+							]
345
+						),
346
+						// NEW STATE NAME
347
+						'new_state_name'              => new EE_Text_Input(
348
+							[
349
+								'html_name'       => $state_name,
350
+								'html_id'         => str_replace(
351
+									'state',
352
+									'nsmf_new_state_name',
353
+									$input->html_id()
354
+								),
355
+								'html_class'      => $input->html_class() . ' new-state-state',
356
+								'html_label_text' => esc_html__(
357
+									'New State/Province Name',
358
+									'event_espresso'
359
+								),
360
+								'default'         => $request->getRequestParam($state_name),
361
+								'required'        => false,
362
+							]
363
+						),
364
+						// NEW STATE NAME
365
+						'new_state_abbrv'             => new EE_Text_Input(
366
+							[
367
+								'html_name'             => $abbrv_name,
368
+								'html_id'               => str_replace(
369
+									'state',
370
+									'nsmf_new_state_abbrv',
371
+									$input->html_id()
372
+								),
373
+								'html_class'            => $input->html_class() . ' new-state-abbrv',
374
+								'html_label_text'       => esc_html__(
375
+									'New State/Province Abbreviation',
376
+									'event_espresso'
377
+								) . ' *',
378
+								'other_html_attributes' => 'size="24"',
379
+								'default'               => $request->getRequestParam($abbrv_name),
380
+								'required'              => false,
381
+							]
382
+						),
383
+						// "submit" button
384
+						'add_new_state_submit_button' => new EE_Form_Section_HTML(
385
+							apply_filters(
386
+								'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_submit_button',
387
+								EEH_HTML::div(
388
+									EEH_HTML::button(
389
+										esc_html__('ADD', 'event_espresso'),
390
+										'ee-form-add-new-state-submit button button--secondary',
391
+										'',
392
+										'submit-' . $new_state_submit_id,
393
+										'',
394
+										'data-target="' . $new_state_submit_id . '"'
395
+										. ' data-value-field-name="' . $input->valueFieldName() . '"'
396
+									),
397
+									'',
398
+									'ee-form-add-new-state-submit-dv'
399
+								)
400
+							)
401
+						),
402
+						// extra info
403
+						'add_new_state_extra'         => new EE_Form_Section_HTML(
404
+							apply_filters(
405
+								'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_extra',
406
+								EEH_HTML::br()
407
+								.
408
+								EEH_HTML::div('', '', 'small-text')
409
+								.
410
+								EEH_HTML::strong(
411
+									'* ' .
412
+									esc_html__(
413
+										'Don\'t know your State/Province Abbreviation?',
414
+										'event_espresso'
415
+									)
416
+								)
417
+								.
418
+								EEH_HTML::br()
419
+								.
420
+								sprintf(
421
+									esc_html__(
422
+										'You can look here: %s, for a list of Countries and links to their State/Province Abbreviations ("Subdivisions assigned codes" column).',
423
+										'event_espresso'
424
+									),
425
+									EEH_HTML::link(
426
+										'https://en.wikipedia.org/wiki/ISO_3166-2',
427
+										'https://en.wikipedia.org/wiki/ISO_3166-2',
428
+										'',
429
+										'',
430
+										'ee-form-add-new-state-wiki-lnk',
431
+										'',
432
+										'target="_blank"'
433
+									)
434
+								)
435
+								.
436
+								EEH_HTML::divx()
437
+								.
438
+								EEH_HTML::br()
439
+								.
440
+								EEH_HTML::link(
441
+									'',
442
+									esc_html__('cancel new State/Province', 'event_espresso'),
443
+									'',
444
+									'hide-' . $input->html_id(),
445
+									'ee-form-cancel-new-state-lnk smaller-text',
446
+									'',
447
+									'data-target="' . $input->html_id() . '"'
448
+								)
449
+								.
450
+								EEH_HTML::divx()
451
+								.
452
+								EEH_HTML::br()
453
+							)
454
+						),
455
+					],
456
+				]
457
+			);
458
+			$question_group_reg_form->add_subsections(
459
+				['new_state_micro_form' => $new_state_micro_form],
460
+				'state',
461
+				false
462
+			);
463
+		}
464
+		return $question_group_reg_form;
465
+	}
466
+
467
+
468
+	/**
469
+	 * set_new_state_input_width
470
+	 *
471
+	 * @return int|string
472
+	 * @throws EE_Error
473
+	 * @throws InvalidArgumentException
474
+	 * @throws InvalidDataTypeException
475
+	 * @throws InvalidInterfaceException
476
+	 * @throws ReflectionException
477
+	 */
478
+	public static function add_new_state()
479
+	{
480
+		$request = self::getRequest();
481
+		if ($request->getRequestParam('nsmf_add_new_state', 0, 'int') === 1) {
482
+			EE_Registry::instance()->load_model('State');
483
+			// grab country ISO code, new state name, and new state abbreviation
484
+			$state_country = $request->getRequestParam('nsmf_new_state_country');
485
+			$state_name    = $request->getRequestParam('nsmf_new_state_name');
486
+			$state_abbr    = $request->getRequestParam('nsmf_new_state_abbrv');
487
+			if ($state_country && $state_name && $state_abbr) {
488
+				$new_state = EED_Add_New_State::save_new_state_to_db(
489
+					[
490
+						'CNT_ISO'    => strtoupper($state_country),
491
+						'STA_abbrev' => strtoupper($state_abbr),
492
+						'STA_name'   => ucwords($state_name),
493
+						'STA_active' => false,
494
+					]
495
+				);
496
+				if ($new_state instanceof EE_State) {
497
+					// clean house
498
+					$request->unSetRequestParams(
499
+						[
500
+							'nsmf_add_new_state',
501
+							'nsmf_new_state_country',
502
+							'nsmf_new_state_name',
503
+							'nsmf_new_state_abbrv',
504
+						]
505
+					);
506
+					// get any existing new states
507
+					$new_states                     = EE_Registry::instance()->SSN->get_session_data('nsmf_new_states');
508
+					$new_states[ $new_state->ID() ] = $new_state;
509
+					EE_Registry::instance()->SSN->set_session_data(
510
+						['nsmf_new_states' => $new_states]
511
+					);
512
+					if ($request->isAjax()) {
513
+						echo wp_json_encode(
514
+							[
515
+								'success'      => true,
516
+								'id'           => $new_state->ID(),
517
+								'name'         => $new_state->name(),
518
+								'abbrev'       => $new_state->abbrev(),
519
+								'country_iso'  => $new_state->country_iso(),
520
+								'country_name' => $new_state->country()->name(),
521
+							]
522
+						);
523
+						exit();
524
+					}
525
+					return $new_state->ID();
526
+				}
527
+			} else {
528
+				$error = esc_html__(
529
+					'A new State/Province could not be added because invalid or missing data was received.',
530
+					'event_espresso'
531
+				);
532
+				if ($request->isAjax()) {
533
+					echo wp_json_encode(['error' => $error]);
534
+					exit();
535
+				}
536
+				EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
537
+			}
538
+		}
539
+		return false;
540
+	}
541
+
542
+
543
+	/**
544
+	 * recursively drills down through request params to remove any that were added by this module
545
+	 *
546
+	 * @param array $request_params
547
+	 * @return array
548
+	 */
549
+	public static function filter_checkout_request_params(array $request_params): array
550
+	{
551
+		foreach ($request_params as $form_section) {
552
+			if (is_array($form_section)) {
553
+				EED_Add_New_State::unset_new_state_request_params($form_section);
554
+				EED_Add_New_State::filter_checkout_request_params($form_section);
555
+			}
556
+		}
557
+		return $request_params;
558
+	}
559
+
560
+
561
+	/**
562
+	 * @param array $request_params
563
+	 * @return array
564
+	 */
565
+	public static function unset_new_state_request_params(array $request_params): array
566
+	{
567
+		unset(
568
+			$request_params['new_state_micro_form'],
569
+			$request_params['new_state_micro_add_new_state'],
570
+			$request_params['new_state_micro_new_state_country'],
571
+			$request_params['new_state_micro_new_state_name'],
572
+			$request_params['new_state_micro_new_state_abbrv']
573
+		);
574
+		return $request_params;
575
+	}
576
+
577
+
578
+	/**
579
+	 * @param array $props_n_values
580
+	 * @return EE_State|null
581
+	 * @throws EE_Error
582
+	 * @throws ReflectionException
583
+	 */
584
+	public static function save_new_state_to_db(array $props_n_values = []): ?EE_State
585
+	{
586
+		/** @var EE_State[] $existing_state */
587
+		$existing_state = EEM_State::instance()->get_all([$props_n_values, 'limit' => 1]);
588
+		if (! empty($existing_state)) {
589
+			return array_pop($existing_state);
590
+		}
591
+		$new_state = EE_State::new_instance($props_n_values);
592
+		if ($new_state instanceof EE_State) {
593
+			$country_settings_url = add_query_arg(
594
+				[
595
+					'page'    => 'espresso_general_settings',
596
+					'action'  => 'country_settings',
597
+					'country' => $new_state->country_iso(),
598
+				],
599
+				admin_url('admin.php')
600
+			);
601
+			// if not non-ajax admin
602
+			new PersistentAdminNotice(
603
+				'new-state-added-' . $new_state->country_iso() . '-' . $new_state->abbrev(),
604
+				sprintf(
605
+					esc_html__(
606
+						'A new State named "%1$s (%2$s)" was dynamically added from an Event Espresso form for the Country of "%3$s".%5$sTo verify, edit, and/or delete this new State, please go to the %4$s and update the States / Provinces section.%5$sCheck "Yes" to have this new State added to dropdown select lists in forms.',
607
+						'event_espresso'
608
+					),
609
+					'<b>' . $new_state->name() . '</b>',
610
+					'<b>' . $new_state->abbrev() . '</b>',
611
+					'<b>' . $new_state->country()->name() . '</b>',
612
+					'<a href="'
613
+					. $country_settings_url
614
+					. '">'
615
+					. esc_html__(
616
+						'Event Espresso - General Settings > Countries Tab',
617
+						'event_espresso'
618
+					)
619
+					. '</a>',
620
+					'<br />'
621
+				)
622
+			);
623
+			$new_state->save();
624
+			EEM_State::instance()->reset_cached_states();
625
+			return $new_state;
626
+		}
627
+		return null;
628
+	}
629
+
630
+
631
+	/**
632
+	 * @param string $CNT_ISO
633
+	 * @param int    $STA_ID
634
+	 * @param array  $cols_n_values
635
+	 * @return void
636
+	 * @throws DomainException
637
+	 * @throws EE_Error
638
+	 * @throws InvalidArgumentException
639
+	 * @throws InvalidDataTypeException
640
+	 * @throws InvalidInterfaceException
641
+	 * @throws ReflectionException
642
+	 */
643
+	public static function update_country_settings(string $CNT_ISO = '', int $STA_ID = 0, array $cols_n_values = [])
644
+	{
645
+		/** @var EE_Capabilities $capabilities */
646
+		$capabilities = LoaderFactory::getLoader()->getShared(EE_Capabilities::class);
647
+		if (! $capabilities->current_user_can('manage_options', 'update-country-settings')) {
648
+			wp_die(esc_html__('You do not have the required privileges to perform this action', 'event_espresso'));
649
+		}
650
+		if (! $CNT_ISO) {
651
+			EE_Error::add_error(
652
+				esc_html__('An invalid or missing Country ISO Code was received.', 'event_espresso'),
653
+				__FILE__,
654
+				__FUNCTION__,
655
+				__LINE__
656
+			);
657
+		}
658
+		$STA_abbrev = is_array($cols_n_values) && isset($cols_n_values['STA_abbrev']) ? $cols_n_values['STA_abbrev']
659
+			: false;
660
+		if (! $STA_abbrev && ! empty($STA_ID)) {
661
+			$state = EEM_State::instance()->get_one_by_ID($STA_ID);
662
+			if ($state instanceof EE_State) {
663
+				$STA_abbrev = $state->abbrev();
664
+			}
665
+		}
666
+		if (! $STA_abbrev) {
667
+			EE_Error::add_error(
668
+				esc_html__('An invalid or missing State Abbreviation was received.', 'event_espresso'),
669
+				__FILE__,
670
+				__FUNCTION__,
671
+				__LINE__
672
+			);
673
+		}
674
+		/** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
675
+		$persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
676
+			'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
677
+		);
678
+		$persistent_admin_notice_manager->dismissNotice($CNT_ISO . '-' . $STA_abbrev, true, true);
679
+	}
680
+
681
+
682
+	/**
683
+	 * @param EE_State[]                                              $state_options
684
+	 * @param EE_SPCO_Reg_Step_Attendee_Information|StateOptions|null $deprecated
685
+	 * @param EE_Registration                                         $registration
686
+	 * @param EE_Question|null                                        $question
687
+	 * @param EE_Answer|null                                          $answer
688
+	 * @return array
689
+	 * @throws EE_Error
690
+	 * @throws ReflectionException
691
+	 */
692
+	public static function inject_new_reg_state_into_options(
693
+		array $state_options,
694
+		$deprecated,
695
+		EE_Registration $registration,
696
+		?EE_Question $question,
697
+		?EE_Answer $answer
698
+	): array {
699
+		if (
700
+			$answer instanceof EE_Answer && $question instanceof EE_Question
701
+			&& $question->type() === EEM_Question::QST_type_state
702
+		) {
703
+			$STA_ID = $answer->value();
704
+			if (! empty($STA_ID)) {
705
+				$state = EEM_State::instance()->get_one_by_ID($STA_ID);
706
+				if ($state instanceof EE_State) {
707
+					$country = $state->country();
708
+					if ($country instanceof EE_Country) {
709
+						if (! isset($state_options[ $country->name() ])) {
710
+							$state_options[ $country->name() ] = [];
711
+						}
712
+						if (! isset($state_options[ $country->name() ][ $STA_ID ])) {
713
+							$state_options[ $country->name() ][ $STA_ID ] = $state->name();
714
+						}
715
+					}
716
+				}
717
+			}
718
+		}
719
+		return $state_options;
720
+	}
721
+
722
+
723
+	/**
724
+	 * @param EE_Country[]                                              $country_options
725
+	 * @param EE_SPCO_Reg_Step_Attendee_Information|CountryOptions|null $deprecated
726
+	 * @param EE_Registration                                           $registration
727
+	 * @param EE_Question|null                                          $question
728
+	 * @param EE_Answer|null                                            $answer
729
+	 * @return array
730
+	 * @throws EE_Error
731
+	 * @throws InvalidArgumentException
732
+	 * @throws InvalidDataTypeException
733
+	 * @throws InvalidInterfaceException
734
+	 * @throws ReflectionException
735
+	 */
736
+	public static function inject_new_reg_country_into_options(
737
+		array $country_options,
738
+		$deprecated,
739
+		EE_Registration $registration,
740
+		?EE_Question $question,
741
+		?EE_Answer $answer
742
+	): array {
743
+		if (
744
+			$answer instanceof EE_Answer
745
+			&& $question instanceof EE_Question
746
+			&& $question->type() === EEM_Question::QST_type_country
747
+		) {
748
+			$CNT_ISO = $answer->value();
749
+			if (! empty($CNT_ISO)) {
750
+				$country = EEM_Country::instance()->get_one_by_ID($CNT_ISO);
751
+				if ($country instanceof EE_Country) {
752
+					if (! isset($country_options[ $CNT_ISO ])) {
753
+						$country_options[ $CNT_ISO ] = $country->name();
754
+					}
755
+				}
756
+			}
757
+		}
758
+		return $country_options;
759
+	}
760
+
761
+
762
+	/**
763
+	 * @param EE_State[] $state_options
764
+	 * @return array
765
+	 * @throws EE_Error
766
+	 * @throws ReflectionException
767
+	 */
768
+	public static function state_options(array $state_options = []): array
769
+	{
770
+		$new_states = EED_Add_New_State::_get_new_states();
771
+		foreach ($new_states as $new_state) {
772
+			if (
773
+				$new_state instanceof EE_State
774
+				&& $new_state->country() instanceof EE_Country
775
+			) {
776
+				$state_options[ $new_state->country()->name() ][ $new_state->ID() ] = $new_state->name();
777
+			}
778
+		}
779
+		return $state_options;
780
+	}
781
+
782
+
783
+	/**
784
+	 * @return array
785
+	 * @throws InvalidArgumentException
786
+	 * @throws InvalidDataTypeException
787
+	 * @throws InvalidInterfaceException
788
+	 */
789
+	protected static function _get_new_states(): array
790
+	{
791
+		$new_states = [];
792
+		if (EE_Registry::instance()->SSN instanceof EE_Session) {
793
+			$new_states = EE_Registry::instance()->SSN->get_session_data(
794
+				'nsmf_new_states'
795
+			);
796
+		}
797
+		return is_array($new_states) ? $new_states : [];
798
+	}
799
+
800
+
801
+	/**
802
+	 * @param EE_Country[] $country_options
803
+	 * @return array
804
+	 * @throws EE_Error
805
+	 * @throws ReflectionException
806
+	 */
807
+	public static function country_options(array $country_options = []): array
808
+	{
809
+		$new_states = EED_Add_New_State::_get_new_states();
810
+		foreach ($new_states as $new_state) {
811
+			if (
812
+				$new_state instanceof EE_State
813
+				&& $new_state->country() instanceof EE_Country
814
+			) {
815
+				$country_options[ $new_state->country()->ID() ] = $new_state->country()->name();
816
+			}
817
+		}
818
+		return $country_options;
819
+	}
820 820
 }
Please login to merge, or discard this patch.
Spacing   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -143,14 +143,14 @@  discard block
 block discarded – undo
143 143
      */
144 144
     public static function set_definitions()
145 145
     {
146
-        define('ANS_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets/');
146
+        define('ANS_ASSETS_URL', plugin_dir_url(__FILE__).'assets/');
147 147
         define(
148 148
             'ANS_TEMPLATES_PATH',
149 149
             str_replace(
150 150
                 '\\',
151 151
                 '/',
152 152
                 plugin_dir_path(__FILE__)
153
-            ) . 'templates/'
153
+            ).'templates/'
154 154
         );
155 155
     }
156 156
 
@@ -169,19 +169,19 @@  discard block
 block discarded – undo
169 169
      */
170 170
     public static function translate_js_strings()
171 171
     {
172
-        EE_Registry::$i18n_js_strings['ans_no_country']        = esc_html__(
172
+        EE_Registry::$i18n_js_strings['ans_no_country'] = esc_html__(
173 173
             'In order to proceed, you need to select the Country that your State/Province belongs to.',
174 174
             'event_espresso'
175 175
         );
176
-        EE_Registry::$i18n_js_strings['ans_no_name']           = esc_html__(
176
+        EE_Registry::$i18n_js_strings['ans_no_name'] = esc_html__(
177 177
             'In order to proceed, you need to enter the name of your State/Province.',
178 178
             'event_espresso'
179 179
         );
180
-        EE_Registry::$i18n_js_strings['ans_no_abbreviation']   = esc_html__(
180
+        EE_Registry::$i18n_js_strings['ans_no_abbreviation'] = esc_html__(
181 181
             'In order to proceed, you need to enter an abbreviation for the name of your State/Province.',
182 182
             'event_espresso'
183 183
         );
184
-        EE_Registry::$i18n_js_strings['ans_save_success']      = esc_html__(
184
+        EE_Registry::$i18n_js_strings['ans_save_success'] = esc_html__(
185 185
             'The new state was successfully saved to the database.',
186 186
             'event_espresso'
187 187
         );
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
         if (apply_filters('EED_Single_Page_Checkout__SPCO_active', false)) {
201 201
             wp_register_script(
202 202
                 'add_new_state',
203
-                ANS_ASSETS_URL . 'add_new_state.js',
203
+                ANS_ASSETS_URL.'add_new_state.js',
204 204
                 ['espresso_core', 'single_page_checkout'],
205 205
                 EVENT_ESPRESSO_VERSION,
206 206
                 true
@@ -247,10 +247,10 @@  discard block
 block discarded – undo
247 247
             $new_state_submit_id = str_replace('state', 'new_state', $input->html_id());
248 248
             $country_options     = [];
249 249
             $countries           = EEM_Country::instance()->get_all_countries();
250
-            if (! empty($countries)) {
250
+            if ( ! empty($countries)) {
251 251
                 foreach ($countries as $country) {
252 252
                     if ($country instanceof EE_Country) {
253
-                        $country_options[ $country->ID() ] = $country->name();
253
+                        $country_options[$country->ID()] = $country->name();
254 254
                     }
255 255
                 }
256 256
             }
@@ -284,10 +284,10 @@  discard block
 block discarded – undo
284 284
                                     '',
285 285
                                     esc_html__('click here to add a new state/province', 'event_espresso'),
286 286
                                     '',
287
-                                    'display-' . $input->html_id(),
287
+                                    'display-'.$input->html_id(),
288 288
                                     'ee-form-add-new-state-lnk display-the-hidden smaller-text hide-if-no-js',
289 289
                                     '',
290
-                                    'data-target="' . $input->html_id() . '"'
290
+                                    'data-target="'.$input->html_id().'"'
291 291
                                 )
292 292
                             )
293 293
                         ),
@@ -297,33 +297,33 @@  discard block
 block discarded – undo
297 297
                                 'FHEE__EED_Add_New_State__display_add_new_state_micro_form__add_new_state_micro_form',
298 298
                                 EEH_HTML::div(
299 299
                                     '',
300
-                                    $input->html_id() . '-dv',
300
+                                    $input->html_id().'-dv',
301 301
                                     'ee-form-add-new-state-dv',
302 302
                                     'display: none;'
303
-                                ) .
303
+                                ).
304 304
                                 EEH_HTML::h6(
305 305
                                     esc_html__(
306 306
                                         'Is your state/province missing from the dropdown menu above? You can add it by completing the following steps:',
307 307
                                         'event_espresso'
308 308
                                     )
309
-                                ) .
310
-                                EEH_HTML::ul() .
309
+                                ).
310
+                                EEH_HTML::ul().
311 311
                                 EEH_HTML::li(
312 312
                                     esc_html__(
313 313
                                         'first select the Country that your State/Province belongs to',
314 314
                                         'event_espresso'
315 315
                                     )
316
-                                ) .
316
+                                ).
317 317
                                 EEH_HTML::li(
318 318
                                     esc_html__('enter the name of your State/Province', 'event_espresso')
319
-                                ) .
319
+                                ).
320 320
                                 EEH_HTML::li(
321 321
                                     esc_html__(
322 322
                                         'enter a two to six letter abbreviation for the name of your State/Province',
323 323
                                         'event_espresso'
324 324
                                     )
325
-                                ) .
326
-                                EEH_HTML::li(esc_html__('click the ADD button', 'event_espresso')) .
325
+                                ).
326
+                                EEH_HTML::li(esc_html__('click the ADD button', 'event_espresso')).
327 327
                                 EEH_HTML::ulx()
328 328
                             )
329 329
                         ),
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
                                     'nsmf_new_state_country',
338 338
                                     $input->html_id()
339 339
                                 ),
340
-                                'html_class'      => $input->html_class() . ' new-state-country',
340
+                                'html_class'      => $input->html_class().' new-state-country',
341 341
                                 'html_label_text' => esc_html__('New State/Province Country', 'event_espresso'),
342 342
                                 'default'         => $request->getRequestParam($country_name),
343 343
                                 'required'        => false,
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
                                     'nsmf_new_state_name',
353 353
                                     $input->html_id()
354 354
                                 ),
355
-                                'html_class'      => $input->html_class() . ' new-state-state',
355
+                                'html_class'      => $input->html_class().' new-state-state',
356 356
                                 'html_label_text' => esc_html__(
357 357
                                     'New State/Province Name',
358 358
                                     'event_espresso'
@@ -370,11 +370,11 @@  discard block
 block discarded – undo
370 370
                                     'nsmf_new_state_abbrv',
371 371
                                     $input->html_id()
372 372
                                 ),
373
-                                'html_class'            => $input->html_class() . ' new-state-abbrv',
373
+                                'html_class'            => $input->html_class().' new-state-abbrv',
374 374
                                 'html_label_text'       => esc_html__(
375 375
                                     'New State/Province Abbreviation',
376 376
                                     'event_espresso'
377
-                                ) . ' *',
377
+                                ).' *',
378 378
                                 'other_html_attributes' => 'size="24"',
379 379
                                 'default'               => $request->getRequestParam($abbrv_name),
380 380
                                 'required'              => false,
@@ -389,10 +389,10 @@  discard block
 block discarded – undo
389 389
                                         esc_html__('ADD', 'event_espresso'),
390 390
                                         'ee-form-add-new-state-submit button button--secondary',
391 391
                                         '',
392
-                                        'submit-' . $new_state_submit_id,
392
+                                        'submit-'.$new_state_submit_id,
393 393
                                         '',
394
-                                        'data-target="' . $new_state_submit_id . '"'
395
-                                        . ' data-value-field-name="' . $input->valueFieldName() . '"'
394
+                                        'data-target="'.$new_state_submit_id.'"'
395
+                                        . ' data-value-field-name="'.$input->valueFieldName().'"'
396 396
                                     ),
397 397
                                     '',
398 398
                                     'ee-form-add-new-state-submit-dv'
@@ -408,7 +408,7 @@  discard block
 block discarded – undo
408 408
                                 EEH_HTML::div('', '', 'small-text')
409 409
                                 .
410 410
                                 EEH_HTML::strong(
411
-                                    '* ' .
411
+                                    '* '.
412 412
                                     esc_html__(
413 413
                                         'Don\'t know your State/Province Abbreviation?',
414 414
                                         'event_espresso'
@@ -441,10 +441,10 @@  discard block
 block discarded – undo
441 441
                                     '',
442 442
                                     esc_html__('cancel new State/Province', 'event_espresso'),
443 443
                                     '',
444
-                                    'hide-' . $input->html_id(),
444
+                                    'hide-'.$input->html_id(),
445 445
                                     'ee-form-cancel-new-state-lnk smaller-text',
446 446
                                     '',
447
-                                    'data-target="' . $input->html_id() . '"'
447
+                                    'data-target="'.$input->html_id().'"'
448 448
                                 )
449 449
                                 .
450 450
                                 EEH_HTML::divx()
@@ -505,7 +505,7 @@  discard block
 block discarded – undo
505 505
                     );
506 506
                     // get any existing new states
507 507
                     $new_states                     = EE_Registry::instance()->SSN->get_session_data('nsmf_new_states');
508
-                    $new_states[ $new_state->ID() ] = $new_state;
508
+                    $new_states[$new_state->ID()] = $new_state;
509 509
                     EE_Registry::instance()->SSN->set_session_data(
510 510
                         ['nsmf_new_states' => $new_states]
511 511
                     );
@@ -585,7 +585,7 @@  discard block
 block discarded – undo
585 585
     {
586 586
         /** @var EE_State[] $existing_state */
587 587
         $existing_state = EEM_State::instance()->get_all([$props_n_values, 'limit' => 1]);
588
-        if (! empty($existing_state)) {
588
+        if ( ! empty($existing_state)) {
589 589
             return array_pop($existing_state);
590 590
         }
591 591
         $new_state = EE_State::new_instance($props_n_values);
@@ -600,15 +600,15 @@  discard block
 block discarded – undo
600 600
             );
601 601
             // if not non-ajax admin
602 602
             new PersistentAdminNotice(
603
-                'new-state-added-' . $new_state->country_iso() . '-' . $new_state->abbrev(),
603
+                'new-state-added-'.$new_state->country_iso().'-'.$new_state->abbrev(),
604 604
                 sprintf(
605 605
                     esc_html__(
606 606
                         'A new State named "%1$s (%2$s)" was dynamically added from an Event Espresso form for the Country of "%3$s".%5$sTo verify, edit, and/or delete this new State, please go to the %4$s and update the States / Provinces section.%5$sCheck "Yes" to have this new State added to dropdown select lists in forms.',
607 607
                         'event_espresso'
608 608
                     ),
609
-                    '<b>' . $new_state->name() . '</b>',
610
-                    '<b>' . $new_state->abbrev() . '</b>',
611
-                    '<b>' . $new_state->country()->name() . '</b>',
609
+                    '<b>'.$new_state->name().'</b>',
610
+                    '<b>'.$new_state->abbrev().'</b>',
611
+                    '<b>'.$new_state->country()->name().'</b>',
612 612
                     '<a href="'
613 613
                     . $country_settings_url
614 614
                     . '">'
@@ -644,10 +644,10 @@  discard block
 block discarded – undo
644 644
     {
645 645
         /** @var EE_Capabilities $capabilities */
646 646
         $capabilities = LoaderFactory::getLoader()->getShared(EE_Capabilities::class);
647
-        if (! $capabilities->current_user_can('manage_options', 'update-country-settings')) {
647
+        if ( ! $capabilities->current_user_can('manage_options', 'update-country-settings')) {
648 648
             wp_die(esc_html__('You do not have the required privileges to perform this action', 'event_espresso'));
649 649
         }
650
-        if (! $CNT_ISO) {
650
+        if ( ! $CNT_ISO) {
651 651
             EE_Error::add_error(
652 652
                 esc_html__('An invalid or missing Country ISO Code was received.', 'event_espresso'),
653 653
                 __FILE__,
@@ -657,13 +657,13 @@  discard block
 block discarded – undo
657 657
         }
658 658
         $STA_abbrev = is_array($cols_n_values) && isset($cols_n_values['STA_abbrev']) ? $cols_n_values['STA_abbrev']
659 659
             : false;
660
-        if (! $STA_abbrev && ! empty($STA_ID)) {
660
+        if ( ! $STA_abbrev && ! empty($STA_ID)) {
661 661
             $state = EEM_State::instance()->get_one_by_ID($STA_ID);
662 662
             if ($state instanceof EE_State) {
663 663
                 $STA_abbrev = $state->abbrev();
664 664
             }
665 665
         }
666
-        if (! $STA_abbrev) {
666
+        if ( ! $STA_abbrev) {
667 667
             EE_Error::add_error(
668 668
                 esc_html__('An invalid or missing State Abbreviation was received.', 'event_espresso'),
669 669
                 __FILE__,
@@ -675,7 +675,7 @@  discard block
 block discarded – undo
675 675
         $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
676 676
             'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
677 677
         );
678
-        $persistent_admin_notice_manager->dismissNotice($CNT_ISO . '-' . $STA_abbrev, true, true);
678
+        $persistent_admin_notice_manager->dismissNotice($CNT_ISO.'-'.$STA_abbrev, true, true);
679 679
     }
680 680
 
681 681
 
@@ -701,16 +701,16 @@  discard block
 block discarded – undo
701 701
             && $question->type() === EEM_Question::QST_type_state
702 702
         ) {
703 703
             $STA_ID = $answer->value();
704
-            if (! empty($STA_ID)) {
704
+            if ( ! empty($STA_ID)) {
705 705
                 $state = EEM_State::instance()->get_one_by_ID($STA_ID);
706 706
                 if ($state instanceof EE_State) {
707 707
                     $country = $state->country();
708 708
                     if ($country instanceof EE_Country) {
709
-                        if (! isset($state_options[ $country->name() ])) {
710
-                            $state_options[ $country->name() ] = [];
709
+                        if ( ! isset($state_options[$country->name()])) {
710
+                            $state_options[$country->name()] = [];
711 711
                         }
712
-                        if (! isset($state_options[ $country->name() ][ $STA_ID ])) {
713
-                            $state_options[ $country->name() ][ $STA_ID ] = $state->name();
712
+                        if ( ! isset($state_options[$country->name()][$STA_ID])) {
713
+                            $state_options[$country->name()][$STA_ID] = $state->name();
714 714
                         }
715 715
                     }
716 716
                 }
@@ -746,11 +746,11 @@  discard block
 block discarded – undo
746 746
             && $question->type() === EEM_Question::QST_type_country
747 747
         ) {
748 748
             $CNT_ISO = $answer->value();
749
-            if (! empty($CNT_ISO)) {
749
+            if ( ! empty($CNT_ISO)) {
750 750
                 $country = EEM_Country::instance()->get_one_by_ID($CNT_ISO);
751 751
                 if ($country instanceof EE_Country) {
752
-                    if (! isset($country_options[ $CNT_ISO ])) {
753
-                        $country_options[ $CNT_ISO ] = $country->name();
752
+                    if ( ! isset($country_options[$CNT_ISO])) {
753
+                        $country_options[$CNT_ISO] = $country->name();
754 754
                     }
755 755
                 }
756 756
             }
@@ -773,7 +773,7 @@  discard block
 block discarded – undo
773 773
                 $new_state instanceof EE_State
774 774
                 && $new_state->country() instanceof EE_Country
775 775
             ) {
776
-                $state_options[ $new_state->country()->name() ][ $new_state->ID() ] = $new_state->name();
776
+                $state_options[$new_state->country()->name()][$new_state->ID()] = $new_state->name();
777 777
             }
778 778
         }
779 779
         return $state_options;
@@ -812,7 +812,7 @@  discard block
 block discarded – undo
812 812
                 $new_state instanceof EE_State
813 813
                 && $new_state->country() instanceof EE_Country
814 814
             ) {
815
-                $country_options[ $new_state->country()->ID() ] = $new_state->country()->name();
815
+                $country_options[$new_state->country()->ID()] = $new_state->country()->name();
816 816
             }
817 817
         }
818 818
         return $country_options;
Please login to merge, or discard this patch.