diff --git a/CHANGELOG.md b/CHANGELOG.md index 32549065df0..dea5261e148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Releases +### [5.0.13] + +#### Fixed + - [Set Defaults and Types for EE_Gateway Properties (#989)](https://github.com/eventespresso/cafe/pull/989) + - [Fix Escaping for [ESPRESSO_MY_EVENTS] Shortcode (#975)](https://github.com/eventespresso/cafe/pull/975) + - [Fix Messages Settings Admin Toggle Switch Display (#996)](https://github.com/eventespresso/cafe/pull/996) + - [Fix Event Registrations Report CSV (#988)](https://github.com/eventespresso/cafe/pull/988) + - [Relax Property Types in EE_Template_Config (#1007)](https://github.com/eventespresso/cafe/pull/1007) + - [Fix EE_Line_Item::desc() Return Type (#1009)](https://github.com/eventespresso/cafe/pull/1009) + - [Fix Country Settings Is EU Option Not Saving (#1017)](https://github.com/eventespresso/cafe/pull/1017) + - [Fix Registration List Table View Links When Filtered by Event (#1016)](https://github.com/eventespresso/cafe/pull/1016) + - [Fix Event List Registration Links (#1014)](https://github.com/eventespresso/cafe/pull/1014) + - [Fix Session Reset Keys Data Type (#1023)](https://github.com/eventespresso/cafe/pull/1023) + - [Fix Ticket Total with Taxes (#1018)](https://github.com/eventespresso/cafe/pull/1018) + - [Fix Undefined Constant and Uninitialized Properties (#1030)](https://github.com/eventespresso/cafe/pull/1030) + - [ES. Fix missing thank you page session (#1032)](https://github.com/eventespresso/cafe/pull/1032) + - [Fix Model Path for DTT_ID in addWhereParamsForFilters() (#1039)](https://github.com/eventespresso/cafe/pull/1039) + - [Fix RSS Feeds and UI Tweaks (#1045)](https://github.com/eventespresso/cafe/pull/1045) + - [Fix fatals/deprecated notices thrown during migrations (#1063)](https://github.com/eventespresso/cafe/pull/1063) + +#### Changed + - [BM 5.0.12.p changes (#977)](https://github.com/eventespresso/cafe/pull/977) + - [Refactor How Database Table Indexes are Added (#983)](https://github.com/eventespresso/cafe/pull/983) + - [Check for Multiple @ Sign in Email Addresses (#973)](https://github.com/eventespresso/cafe/pull/973) + - [Move Venue Sorting into useVenues() Hook (Barista#1276) (#1000)](https://github.com/eventespresso/cafe/pull/1000) + - [PPC. Third party integration (#807)](https://github.com/eventespresso/cafe/pull/807) + - [Make M-Mode Changes Backwards Compatible (#979)](https://github.com/eventespresso/cafe/pull/979) + - [Dont Use Links to Close Notices (#1036)](https://github.com/eventespresso/cafe/pull/1036) + - [PPC. Fix not ACDC eligible behaviour (#1040)](https://github.com/eventespresso/cafe/pull/1040) + - [Protect Ticket Assignments Manager Layout from Other Plugin CSS (#1065)](https://github.com/eventespresso/cafe/pull/1065) + + ### [5.0.12] #### Added @@ -83,7 +115,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Changed - [Build Machine 5.0.9 changes (#828)](https://github.com/eventespresso/cafe/pull/828) - - [PayPal Commerce. Fix double opening clicks on SPCO](https://github.com/eventespresso/cafe/pull/827) + - [PayPal Commerce. Fix double](https://github.com/eventespresso/cafe/pull/827) - [Don't apply payments to cancelled payments by default (#844)](https://github.com/eventespresso/cafe/pull/844) - [Verify Core Config Loaded Before Showing Maintenance Mode Notice (#843)](https://github.com/eventespresso/cafe/pull/843) diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php index 829e54ea7c9..0b93ba7f7b0 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/EEG_PayPalCheckout.gateway.php @@ -51,7 +51,7 @@ class EEG_PayPalCheckout extends EE_Onsite_Gateway /** * @param EE_Payment|null $payment - * @param array|null $billing_info + * @param array|null $billing_info * @return EE_Payment * @throws EE_Error * @throws ReflectionException @@ -59,13 +59,14 @@ class EEG_PayPalCheckout extends EE_Onsite_Gateway public function do_direct_payment($payment, $billing_info = null) { $failed_status = $this->_pay_model->failed_status(); - $request = LoaderFactory::getLoader()->getShared(RequestInterface::class); + $request = LoaderFactory::getLoader()->getShared(RequestInterface::class); // Check the payment. $payment_valid = $this->validateThisPayment($payment, $request); if ($payment_valid->details() === 'error' && $payment_valid->status() === $failed_status) { return $payment_valid; } - $payment_method = $payment->transaction()->payment_method(); + $transaction = $payment->transaction(); + $payment_method = $transaction->payment_method(); // Get saved order details. try { @@ -87,7 +88,7 @@ public function do_direct_payment($payment, $billing_info = null) // Remove the saved order data. PayPalExtraMetaManager::deletePmOption($payment_method, Domain::META_KEY_LAST_ORDER); // Looks like all is good. Do a payment success. - return $this->setPaymentSuccess($payment, $order); + return $this->setPaymentSuccess($payment, $transaction, $order); } @@ -98,6 +99,7 @@ public function do_direct_payment($payment, $billing_info = null) * @param RequestInterface $request * @return EE_Payment * @throws EE_Error + * @throws ReflectionException */ public function validateThisPayment(?EE_Payment $payment, RequestInterface $request): EE_Payment { @@ -131,7 +133,7 @@ public function validateThisPayment(?EE_Payment $payment, RequestInterface $requ * Validate the Order. * * @param string $provided_order_id - * @param $order + * @param $order * @return string string if error and empty if valid. */ public function orderInvalid(string $provided_order_id, $order): string @@ -161,13 +163,13 @@ public function orderInvalid(string $provided_order_id, $order): string * @param array|string $response_data * @param string $err_message * @return EE_Payment - * @throws EE_Error + * @throws EE_Error|ReflectionException */ public function setPaymentFailure( EE_Payment $payment, - string $status, - $response_data, - string $err_message = '' + string $status, + $response_data, + string $err_message = '' ): EE_Payment { $this->log(['Error request data:' => $response_data], $payment); $payment->set_status($status); @@ -180,19 +182,76 @@ public function setPaymentFailure( /** * Set the payment success. * - * @param EE_Payment $payment - * @param array $order + * @param EE_Payment $payment + * @param EE_Transaction $transaction + * @param array $order * @return EE_Payment - * @throws EE_Error + * @throws EE_Error|ReflectionException */ - public function setPaymentSuccess(EE_Payment $payment, array $order): EE_Payment + public function setPaymentSuccess(EE_Payment $payment, EE_Transaction $transaction, array $order): EE_Payment { - $amount = $order['purchase_units'][0]['payments']['captures'][0]['amount']['value']; + $amount = $order['purchase_units'][0]['payments']['captures'][0]['amount']['value'] ?? 0; + if (! $amount) { + $this->log(['Success order but amount is 0 !' => $order], $payment); + } $payment->set_status(EEM_Payment::status_id_approved); $payment->set_amount((float) $amount); - $payment->set_txn_id_chq_nmbr($order['purchase_units'][0]['payments']['captures'][0]['id']); - $payment->set_details($order['payer']); - $payment->set_gateway_response($order['status']); + $payment->set_txn_id_chq_nmbr($order['purchase_units'][0]['payments']['captures'][0]['id'] ?? $order['id']); + $payment->set_gateway_response($order['status'] ?? 'success'); + $this->saveBillingDetails($payment, $transaction, $order); return $payment; } + + + /** + * Save some transaction details, like billing information. + * + * @param EE_Payment $payment + * @param EE_Transaction $transaction + * @param array $order + * @return void + * @throws EE_Error|ReflectionException + */ + public function saveBillingDetails(EE_Payment $payment, EE_Transaction $transaction, array $order): void + { + $input_values = []; + $primary_reg = $transaction->primary_registration(); + $attendee = $primary_reg instanceof EE_Registration ? $primary_reg->attendee() : null; + $transaction = $payment->transaction(); + $payment_method = $transaction->payment_method(); + $postmeta_name = $payment_method->type_obj() instanceof EE_PMT_Base + ? 'billing_info_' . $payment_method->type_obj()->system_name() + : ''; + if (empty($order['payment_source']) || ! $attendee instanceof EE_Attendee) { + // I guess we are done here then. Just save what we have. + $payment->set_details($order); + return; + } + // Do we have order information from the express checkout (PayPal button) ? + if (! empty($order['payment_source']['paypal'])) { + $payer = $order['payment_source']['paypal']; + $payer_name = $payer['name'] ?? ''; + $input_values['first_name'] = $payer_name['given_name'] ?? $attendee->fname(); + $input_values['last_name'] = $payer_name['surname'] ?? $attendee->lname(); + $input_values['email'] = $payer_name['email_address'] ?? $attendee->email(); + $input_values['address'] = $payer_name['address_line_1'] ?? $attendee->address(); + $input_values['address2'] = $payer_name['address_line_2'] ?? $attendee->address2(); + $input_values['city'] = $payer_name['admin_area_2'] ?? $attendee->city(); + $input_values['country'] = $payer_name['country_code'] ?? $attendee->country(); + $input_values['zip'] = $payer_name['postal_code'] ?? $attendee->zip(); + } + // Or card information from ACDC ? + if (! empty($order['payment_source']['card'])) { + $payer_card = $order['payment_source']['card']; + if (! empty($payer_card['name'])) { + $full_name = explode(' ', $payer_card['name']); + // Don't need to save each field because others should be populated from the billing form. + $input_values['credit_card'] = $payer_card['last_digits'] ?? ''; + $input_values['first_name'] = $full_name[0] ?? $attendee->fname(); + $input_values['last_name'] = $full_name[1] ?? $attendee->lname(); + } + } + update_post_meta($attendee->ID(), $postmeta_name, $input_values); + $attendee->save(); + } } diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/EE_PMT_PayPalCheckout.pm.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/EE_PMT_PayPalCheckout.pm.php index 3bfc141982f..b3de0dec7ba 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/EE_PMT_PayPalCheckout.pm.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/EE_PMT_PayPalCheckout.pm.php @@ -2,6 +2,7 @@ use EventEspresso\core\services\loaders\LoaderFactory; use EventEspresso\core\services\request\DataType; +use EventEspresso\core\services\request\Request; use EventEspresso\core\services\request\RequestInterface; use EventEspresso\PaymentMethods\PayPalCommerce\PayPalCheckout\forms\BillingForm; use EventEspresso\PaymentMethods\PayPalCommerce\PayPalCheckout\forms\SettingsForm; @@ -58,7 +59,6 @@ public function generate_new_settings_form() { // Settings form. $settings_form = new SettingsForm($this, $this->_pm_instance); - // Filter the form contents. return apply_filters( 'FHEE__EE_PMT_PayPalCheckout__generate_new_settings_form__form_filtering', @@ -78,10 +78,26 @@ public function generate_new_settings_form() * @throws EE_Error * @throws ReflectionException */ - public function generate_new_billing_form(EE_Transaction $transaction = null, $extra_args = []) + public function generate_new_billing_form(EE_Transaction $transaction = null, ?array $extra_args = []) { + $request = LoaderFactory::getShared(Request::class); + $request_params = $request->requestParams(); + // Return the default billing form for the postbox if this is a WP admin transaction info page. + if (! empty($request_params['page']) && $request_params['page'] === 'espresso_transactions') { + $default_form = new EE_Billing_Attendee_Info_Form($this->_pm_instance, $extra_args); + $default_form->add_subsections(['credit_card' => new EE_Credit_Card_Input()]); + return $default_form; + } + // Just in case this is used on other admin pages. + if (empty($transaction) && ! empty($request_params['TXN_ID'])) { + $txn_instance = EEM_Transaction::instance()->get_one_by_ID($request_params['TXN_ID']); + $transaction = $txn_instance instanceof EE_Transaction ? $txn_instance : null; + } $options = array_merge( - ['transaction' => $transaction, 'template_path' => $this->_template_path], + [ + 'transaction' => $transaction, + 'template_path' => $this->_template_path, + ], $extra_args ); return new BillingForm($this->_pm_instance, $options); @@ -94,7 +110,7 @@ public function generate_new_billing_form(EE_Transaction $transaction = null, $e * @return array * @see EE_PMT_Base::help_tabs_config() */ - public function help_tabs_config() + public function help_tabs_config(): array { return [ $this->get_help_tab_name() => [ @@ -130,7 +146,7 @@ public static function refundNotice(bool $can_edit_payments) } // Try loading the template. EE_Registry::instance()->load_helper('Template'); - } catch (EE_Error | ReflectionException $e) { + } catch (EE_Error|ReflectionException $e) { // Just return, adding nothing. return; } diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php index f41614d6f55..972828ba3f6 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/BillingForm.php @@ -3,6 +3,7 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\PayPalCheckout\forms; use EE_Billing_Attendee_Info_Form; +use EE_Billing_Info_Form; use EE_Error; use EE_Form_Section_Base; use EE_Form_Section_HTML; @@ -12,16 +13,19 @@ use EE_Payment_Method; use EE_PMT_PayPalCheckout; use EE_Registry; +use EE_Submit_Input; use EE_Template_Layout; +use EE_Text_Input; use EE_Transaction; +use EED_PayPalCommerce; +use EEH_HTML; use EEM_Payment_Method; -use EventEspresso\core\exceptions\InvalidDataTypeException; -use EventEspresso\core\exceptions\InvalidInterfaceException; +use EventEspresso\core\services\loaders\LoaderFactory; +use EventEspresso\core\services\request\Request; use EventEspresso\PaymentMethods\PayPalCommerce\domain\Domain; use EventEspresso\PaymentMethods\PayPalCommerce\tools\currency\CurrencyManager; use EventEspresso\PaymentMethods\PayPalCommerce\tools\extra_meta\PayPalExtraMetaManager; use Exception; -use InvalidArgumentException; use ReflectionException; /** @@ -51,38 +55,35 @@ class BillingForm extends EE_Billing_Attendee_Info_Form */ protected $paypal_pmt; + /** + * @var string + */ + protected $checkout_type; + /** * Class constructor. * * @param EE_Payment_Method $payment_method * @param array $options - * @throws InvalidDataTypeException - * @throws InvalidInterfaceException - * @throws InvalidArgumentException * @throws EE_Error + * @throws ReflectionException */ public function __construct(EE_Payment_Method $payment_method, array $options = []) { - // Don't initiate if there's no transaction. - if (isset($options['transaction']) && $options['transaction'] instanceof EE_Transaction) { - if (! isset($options['template_path'])) { - $this->throwError('template_path'); - } - $this->paypal_pmt = $payment_method; - $this->transaction = $options['transaction']; - $this->template_path = $options['template_path']; - } - $pm_slug = $payment_method->slug(); - $parameters = array_replace_recursive( + $this->paypal_pmt = $payment_method; + // Can't be too careful. + $this->transaction = $options['transaction'] ?? null; + $this->template_path = $options['template_path'] ?? ''; + $this->checkout_type = $payment_method->get_extra_meta(Domain::META_KEY_CHECKOUT_TYPE, true, ''); + $pm_slug = $payment_method->slug(); + $parameters = array_replace_recursive( $options, [ 'name' => 'PayPalCommerceBillingForm', - 'html_id' => 'pp-' . $payment_method->slug() . '-billing-form', + 'html_id' => 'pp-' . $pm_slug . '-billing-form', 'html_class' => 'pp_commerce_billing_form', 'subsections' => [ - 'debug_content' => $this->addDebugContent($payment_method), - 'paypal_commerce_pm_form' => $this->addPaymentButtons(), 'eea_paypal_commerce_token' => new EE_Hidden_Input( [ 'html_id' => 'eea-paypal-commerce-token', @@ -90,28 +91,28 @@ public function __construct(EE_Payment_Method $payment_method, array $options = 'default' => '', ] ), - 'pp_order_nonce' => new EE_Hidden_Input( + 'pp_order_nonce' => new EE_Hidden_Input( [ 'html_id' => 'eea-' . $pm_slug . '-order-nonce', 'html_name' => 'pp_order_nonce', 'default' => '', ] ), - 'pp_order_id' => new EE_Hidden_Input( + 'pp_order_id' => new EE_Hidden_Input( [ 'html_id' => 'eea-' . $pm_slug . '-order-id', 'html_name' => 'pp_order_id', 'default' => '', ] ), - 'pp_order_status' => new EE_Hidden_Input( + 'pp_order_status' => new EE_Hidden_Input( [ 'html_id' => 'eea-' . $pm_slug . '-order-status', 'html_name' => 'pp_order_status', 'default' => '', ] ), - 'pp_order_amount' => new EE_Hidden_Input( + 'pp_order_amount' => new EE_Hidden_Input( [ 'html_id' => 'eea-' . $pm_slug . '-order-amount', 'html_name' => 'pp_order_amount', @@ -121,8 +122,255 @@ public function __construct(EE_Payment_Method $payment_method, array $options = ], ] ); - + // Add data tags to the PP script. + add_filter('script_loader_tag', [$this, 'addDataTagsToScript'], 10, 2); parent::__construct($payment_method, $parameters); + // Add and exclude other sections. + $this->addPaymentSections(); + // Additional actions and/or filters. + $this->loadActionsAndFilters(); + } + + + /** + * Add PayPal payment sections. + * + * @return void + * @throws EE_Error + * @throws ReflectionException + */ + public function addPaymentSections(): void + { + // Exclude the default billing form fields. + $this->exclude([ + 'first_name', + 'last_name', + 'email', + ]); + // Add PayPal Hosted Fields. + if ($this->checkout_type !== 'express_checkout') { + $this->addAdvancedCardFields(); + } + // Add payment types separator, if both are enabled. + if ($this->checkout_type === 'all') { + $this->addTypesSeparator(); + } + // Add PayPal Buttons section. + if ($this->checkout_type !== 'ppcp') { + $this->add_subsections( + [ + 'paypal_commerce_pm_form' => $this->addPayPalCheckout(), + ] + ); + } + // Exclude the rest billing form fields if the payment type is express checkout. + if ($this->checkout_type === 'express_checkout') { + $this->exclude([ + 'address', + 'address2', + 'state', + 'phone', + 'city', + 'country', + 'zip', + ]); + // Remove the Info subsection. + add_filter('FHEE__EE_Form_Section_Proper___construct__options_array', [$this, 'excludeInfoSubsection']); + } + $this->add_subsections( + [ + 'debug_content' => $this->addDebugContent($this->paypal_pmt), + ] + ); + } + + + /** + * Additional actions and/or filters. + * + * @return void + */ + public function loadActionsAndFilters(): void + { + add_filter( + 'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form', + [__CLASS__, 'excludeBillingFormFields'], + 10, + 2 + ); + } + + + /** + * Filter out billing form fields if pay button was used. + * + * @param EE_Billing_Info_Form $billing_form + * @param EE_Payment_Method $payment_method + * @return EE_Billing_Info_Form + */ + public static function excludeBillingFormFields( + EE_Billing_Info_Form $billing_form, + EE_Payment_Method $payment_method + ): EE_Billing_Info_Form { + $request = LoaderFactory::getShared(Request::class); + $request_params = $request->requestParams(); + if ( + // Only the PPC billing form. + $billing_form instanceof BillingForm + && ! empty($request_params['process_form_submission']) + && $request_params['process_form_submission'] === '1' + && ! empty($request_params['eep_ppc_skip_form_validation']) + ) { + // Hide card info fields. + $billing_form->exclude([ + 'pp_name_on_card', + 'address', + 'address2', + 'state', + 'phone', + 'city', + 'country', + 'zip', + ]); + } + return $billing_form; + } + + + /** + * Add advanced card & debit card fields. + * + * @return void + * @throws EE_Error|ReflectionException + */ + public function addAdvancedCardFields(): void + { + $pm_slug = $this->paypal_pmt->slug(); + $this->add_subsections( + [ + 'pp_card_number' => new EE_Form_Section_HTML( + EEH_HTML::label( + esc_html__('Card Number', 'event_espresso'), + "$pm_slug-card-number-lbl", + "$pm_slug-card-fields", + "", + 'for="' . $pm_slug . '-card-number"' + ) . + EEH_HTML::p( + "", + "$pm_slug-card-number", + "card_field $pm_slug-card-fields" + ) + ), + 'pp_expiration_date' => new EE_Form_Section_HTML( + EEH_HTML::label( + esc_html__('Expiration Date', 'event_espresso'), + "$pm_slug-expiration-date-lbl", + "$pm_slug-card-fields", + "", + 'for="' . $pm_slug . '-expiration-date"' + ) . + EEH_HTML::p( + "", + "$pm_slug-expiration-date", + "card_field $pm_slug-card-fields" + ) + ), + 'pp_card_cvv' => new EE_Form_Section_HTML( + EEH_HTML::label( + esc_html__('CVV', 'event_espresso'), + "$pm_slug-cvv-lbl", + "$pm_slug-card-fields", + "", + 'for="' . $pm_slug . '-cvv"' + ) . + EEH_HTML::p( + "", + "$pm_slug-cvv", + "card_field $pm_slug-card-fields" + ) + ), + 'pp_name_on_card' => new EE_Text_Input( + [ + 'html_label_text' => esc_html__('Name On Card', 'event_espresso'), + 'html_id' => $pm_slug . '-card-holder-name', + 'html_name' => 'card-holder-name', + 'html_class' => '', + 'required' => true, + ] + ), + ] + ); + // Add the submit button at the end. + $this->add_subsections( + [ + 'pp_cc_submit' => new EE_Submit_Input( + [ + 'html_label_text' => esc_html__('Submit', 'event_espresso'), + 'html_id' => $pm_slug, + 'html_class' => 'eep-ppc-btn', + ] + ), + ], + 'phone', + false + ); + } + + + /** + * Add advanced card & debit card fields. + * + * @return void + * @throws EE_Error|ReflectionException + */ + public function addTypesSeparator(): void + { + $this->add_subsections( + [ + 'pp_payment_types_separator' => new EE_Form_Section_HTML( + EEH_HTML::div( + EEH_HTML::div( + ' ', + 'eep-' . $this->paypal_pmt->slug() . '-payments-separator', + 'eep-ppc-separator-line eep-left-floating' + ) . + EEH_HTML::div( + esc_html__(' or ', 'event_espresso'), + 'eep-' . $this->paypal_pmt->slug() . '-separator-text', + 'eep-ppc-separator-text1 eep-mid-floating' + ) . EEH_HTML::div( + ' ', + 'eep-' . $this->paypal_pmt->slug() . '-payments-separator', + 'eep-ppc-separator-line eep-right-floating' + ), + 'eep-ppc-separator-holder', + 'eep-ppc-separator-holder' + ) + ), + ] + ); + } + + + /** + * Exclude the info subsection from the PPC checkout form. + * + * @param array $options_array + * @return array + * @throws EE_Error + * @throws ReflectionException + */ + public function excludeInfoSubsection(array $options_array): array + { + if (! empty($options_array['html_id']) + && $options_array['html_id'] === 'spco-payment-method-info-' . $this->paypal_pmt->slug() + ) { + if (! empty($options_array['subsections']) && isset($options_array['subsections']['info'])) { + unset($options_array['subsections']['info']); + } + } + return $options_array; } @@ -131,7 +379,7 @@ public function __construct(EE_Payment_Method $payment_method, array $options = * * @param EE_Payment_Method $paypal_pm * @return EE_Form_Section_Base - * @throws EE_Error + * @throws EE_Error|ReflectionException */ public function addDebugContent(EE_Payment_Method $paypal_pm): EE_Form_Section_Base { @@ -157,11 +405,9 @@ public function addDebugContent(EE_Payment_Method $paypal_pm): EE_Form_Section_B * @return EE_Form_Section_Proper * @throws EE_Error */ - public function addPaymentButtons(): EE_Form_Section_Proper + public function addPayPalCheckout(): EE_Form_Section_Proper { - $template_args = [ - // 'pm_slug' => $this->_pm_instance->slug(), - ]; + $template_args['pm_slug'] = $this->paypal_pmt->slug(); return new EE_Form_Section_Proper( [ 'layout_strategy' => new EE_Template_Layout( @@ -175,6 +421,29 @@ public function addPaymentButtons(): EE_Form_Section_Proper } + /** + * Load scripts and localize data needed for this form. + * + * @param $tag + * @param $handle + * @return string + */ + public function addDataTagsToScript($tag, $handle): string + { + if ($handle === 'eea_paypal_commerce_js_lib') { + $bn_code = PayPalExtraMetaManager::getPmOption($this->_pm_instance, Domain::META_KEY_BN_CODE); + $response = EED_PayPalCommerce::requestClientToken($this->paypal_pmt); + if (empty($response['client_token'])) { + return $tag; + } + $client_token = $response['client_token']; + $attributes = " data-partner-attribution-id=\"$bn_code\" data-client-token=\"$client_token\""; + $tag = str_replace('>', $attributes . '>', $tag); + } + return $tag; + } + + /** * Load scripts and localize data needed for this form. * @@ -183,22 +452,40 @@ public function addPaymentButtons(): EE_Form_Section_Proper * @throws ReflectionException * @throws Exception */ - public function enqueue_js() + public function enqueue_js(): void { - $client_id = PayPalExtraMetaManager::getPmOption($this->_pm_instance, Domain::META_KEY_CLIENT_ID); - $currency = CurrencyManager::currencyCode(); + $third_party = EED_PayPalCommerce::isThirdParty($this->_pm_instance); + $currency = CurrencyManager::currencyCode(); + $client_id_key = Domain::META_KEY_CLIENT_ID; // Scripts. - $scripts_src = 'https://www.paypal.com/sdk/js?&client-id=' . $client_id . '¤cy=' . $currency; + $scripts_src = 'https://www.paypal.com/sdk/js?components=buttons,hosted-fields&intent=capture'; + if ($third_party) { + $client_id_key = Domain::META_KEY_PARTNER_CLIENT_ID; + $merchant_id = PayPalExtraMetaManager::getPmOption( + $this->_pm_instance, + Domain::META_KEY_SELLER_MERCHANT_ID + ); + $scripts_src .= "&merchant-id=$merchant_id"; + } + $client_id = PayPalExtraMetaManager::getPmOption($this->_pm_instance, $client_id_key); + $scripts_src .= "&client-id=$client_id¤cy=$currency"; wp_enqueue_script('eea_paypal_commerce_js_lib', $scripts_src, [], null); wp_enqueue_script( 'eea_paypal_commerce_js', EEP_PAYPAL_COMMERCE_URL . 'assets/js/paypal-commerce-payments.js', - [], - filemtime(EEP_PAYPAL_COMMERCE_DIR . 'assets/js/paypal-commerce-payments.js'), + ['eea_paypal_commerce_js_lib'], + EVENT_ESPRESSO_VERSION, true ); + // Styles. + wp_enqueue_style( + 'eea_paypal_checkout_form_styles', + EEP_PAYPAL_COMMERCE_URL . 'assets' . DS . 'css' . DS . 'eea-paypal-checkout.css', + [], + EVENT_ESPRESSO_VERSION + ); // Localize the script with our transaction data. - $parameters = $this->localizeParameters($client_id); + $parameters = $this->localizeParameters(); wp_localize_script('eea_paypal_commerce_js', 'eeaPPCommerceParameters', $parameters); parent::enqueue_js(); } @@ -230,13 +517,12 @@ public function getPpOrderId(string $transaction_id): string /** * Form and return PayPal commerce parameters for script localization. * - * @param string $client_id * @return array * @throws EE_Error * @throws ReflectionException * @throws Exception */ - public function localizeParameters(string $client_id): array + public function localizeParameters(): array { // Also tell the script about each instance of this PM. $pm_versions = []; @@ -253,16 +539,15 @@ public function localizeParameters(string $client_id): array // Convert money for a display format. $decimal_places = CurrencyManager::getDecimalPlaces(); $org_country = isset(EE_Registry::instance()->CFG->organization) - && EE_Registry::instance()->CFG->organization instanceof EE_Organization_Config + && EE_Registry::instance()->CFG->organization instanceof EE_Organization_Config ? EE_Registry::instance()->CFG->organization->CNT_ISO : 'US'; $transaction_id = $this->transaction instanceof EE_Transaction ? $this->transaction->ID() : 0; $currency_code = CurrencyManager::currencyCode(); - return [ 'pm_versions' => $pm_versions, - 'client_id' => $client_id, 'payment_currency' => $currency_code, + 'checkout_type' => $this->checkout_type, 'currency_sign' => EE_Registry::instance()->CFG->currency->sign, 'pp_order_id' => $this->getPpOrderId($transaction_id), 'pp_order_nonce' => wp_create_nonce(Domain::CAPTURE_ORDER_NONCE_NAME), @@ -271,6 +556,7 @@ public function localizeParameters(string $client_id): array 'org_country' => $org_country, 'decimal_places' => $decimal_places, 'site_name' => get_bloginfo('name'), + 'active_states' => EED_PayPalCommerce::getActiveStates(), 'no_spco_error' => esc_html__( 'It appears the SDK script was not loaded properly! Please refresh the page and try again or contact support.', 'event_espresso' @@ -296,29 +582,9 @@ public function localizeParameters(string $client_id): array ), 'no_order_id' => esc_html__('No Order ID found.', 'event_espresso'), 'general_pp_error' => esc_html__('PayPal form threw an error.', 'event_espresso'), + 'hf_render_error' => esc_html__('Hosted fields could not be rendered!', 'event_espresso'), + 'pm_capture_error' => esc_html__('Payment could not be captured!', 'event_espresso'), + 'not_acdc_eligible' => esc_html__('This merchant is not eligible for Advanced Card Fields checkout type.', 'event_espresso'), ]; } - - - /** - * Throw an EE error. - * - * @param string $error_name - * @throws EE_Error - */ - private function throwError(string $error_name) - { - if ($error_name === 'template_path') { - throw new EE_Error( - sprintf( - esc_html__( - '%1$s instantiated without the required template_path. Please provide it in $2$s', - 'event_espresso' - ), - __CLASS__, - '$options[\'template_path\']' - ) - ); - } - } } diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingForm.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingForm.php index 8517c29ecbb..4c7a8dd2004 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingForm.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingForm.php @@ -2,20 +2,15 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\PayPalCheckout\forms; -use EE_Form_Section_Base; use EED_PayPalOnboard; use EEM_Payment_Method; use EE_Admin_Two_Column_Layout; use EE_Error; -use EE_Form_Section_HTML; use EE_Form_Section_Proper; use EE_Payment_Method; use EE_PMT_Base; use EE_Simple_HTML_Validation_Strategy; -use EEH_HTML; -use EventEspresso\PaymentMethods\PayPalCommerce\domain\Domain; -use EventEspresso\PaymentMethods\PayPalCommerce\tools\extra_meta\PayPalExtraMetaManager; -use Exception; +use ReflectionException; /** * Class OnboardingForm @@ -29,45 +24,24 @@ class OnboardingForm extends EE_Form_Section_Proper /** * Payment method. * - * @var EE_PMT_Base + * @var EE_PMT_Base|null */ protected $payment_method = null; /** * Payment method instance. * - * @var EE_PMT_Base + * @var EE_PMT_Base|null */ protected $pm_instance = null; /** * Payment method slug. * - * @var EE_PMT_Base + * @var EE_PMT_Base|null */ protected $pm_slug = null; - /** - * PayPal Onboarding button text. - * - * @var string - */ - protected $onboard_btn_text = ''; - - /** - * PayPal Onboarding button in sandbox mode text. - * - * @var string - */ - protected $sandbox_btn_text = ''; - - /** - * PayPal Onboarding section sandbox mode text. - * - * @var string - */ - protected $authed_sandbox_text = ''; - /** * Options field header. * @@ -82,6 +56,13 @@ class OnboardingForm extends EE_Form_Section_Proper */ protected $onboarding_url = ''; + /** + * Onboarding form html entities object. + * + * @var OnboardingFormHtml|null + */ + protected $form_html = null; + /** * Class constructor. @@ -95,18 +76,9 @@ public function __construct(EE_PMT_Base $pmt, EE_Payment_Method $payment_method) $this->payment_method = $pmt; $this->pm_instance = $payment_method; $this->pm_slug = $this->pm_instance->slug(); - $this->onboard_btn_text = esc_html__('Connect with PayPal', 'event_espresso'); - $this->sandbox_btn_text = esc_html__('Connect with PayPal (sandbox)', 'event_espresso'); - $this->authed_sandbox_text = esc_html__('(using sandbox credentials)', 'event_espresso'); + $this->form_html = new OnboardingFormHtml($pmt, $payment_method); // Help tab link as icon. - $this->option_heading = EEH_HTML::th( - sprintf( - esc_html__('PayPal Onboarding: %1$s', 'event_espresso'), - $this->payment_method->get_help_tab_link() - ), - 'eea_paypal_onboard_heading_' . $this->pm_slug, - 'eea-paypal-onboard-heading' - ); + $this->option_heading = $this->form_html->getHeader(); $options = [ 'html_id' => $this->pm_slug . '_pp_commerce_form', 'layout_strategy' => new EE_Admin_Two_Column_Layout(), @@ -121,206 +93,15 @@ public function __construct(EE_PMT_Base $pmt, EE_Payment_Method $payment_method) * Add the onboarding options section. * * @return array - * @throws EE_Error */ public function onboardSectionContents(): array { $subsections = []; // Get the Onboarding status. $is_onboard = EED_PayPalOnboard::isOnboard($this->pm_instance); - $subsections = $this->addOnboardButton($subsections, $is_onboard); - $subsections = $this->addOffboardButton($subsections, $is_onboard); - $subsections = $this->addPmSlugHolder($subsections); - return $subsections; - } - - - /** - * Add the onboarding button. - * - * @param array $subsections - * @param bool $is_onboard - * @return array - * @throws EE_Error - */ - public function addOnboardButton(array $subsections, bool $is_onboard): array - { - // Prep the redirect link for the merchant if he is not onboard yet. - $onboard_url = ''; - if (! $is_onboard) { - $onboard_url = EED_PayPalOnboard::getSignUpLink($this->pm_instance); - } - $this->onboarding_url = $onboard_url ? $onboard_url . '?&displayMode=minibrowser' : '#'; - // Section to be displayed if not onboard. - $subsections['paypal_onboard_btn'] = new EE_Form_Section_HTML( - EEH_HTML::tr( - $this->option_heading . - EEH_HTML::td( - EEH_HTML::link( - $this->onboarding_url, - EEH_HTML::span($this->onboard_btn_text), - '', - 'eea_paypal_onboard_btn_' . $this->pm_slug, - 'eea-paypal-onboard-btn button button--primary', - '', - 'target="_blank" data-paypal-onboard-complete="onboardedCallback" data-paypal-button="true"' - . ' data-ee-pm-slug=' . $this->pm_slug - ) - ), - 'eea_paypal_onboard_section_' . $this->pm_slug, - 'eea-onboard-section-' . $this->pm_slug, - // Are we onboard ? - $is_onboard ? 'display:none;' : '' - ), - ['required' => true] - ); - return $subsections; - } - - - /** - * Get the sandbox onboarding section contents. - * - * @return string - */ - public function getOnboardSandboxSection(): string - { - // Is this a test onboarding ? - $sandbox_mode_text = $this->pm_instance->debug_mode() ? $this->authed_sandbox_text : ''; - return ' ' . EEH_HTML::strong( - $sandbox_mode_text, - 'eea_paypal_onboard_test_txt_' . $this->pm_slug, - 'eea-paypal-onboard-test-txt' - ); - } - - - /** - * Get the seller merchant ID section contents. - * - * @return string - */ - public function getSellerIdSection(): string - { - try { - $payer_id = PayPalExtraMetaManager::getPmOption($this->pm_instance, Domain::META_KEY_PAYER_ID) ?? '--'; - } catch (Exception $e) { - $payer_id = '--'; - } - return ' ' . EEH_HTML::strong( - sprintf(esc_html__('Linked account ID: %1$s', 'event_espresso'), $payer_id), - 'eea_paypal_seller_id_' . $this->pm_slug, - 'eea-paypal-seller-id' - ); - } - - - /** - * Add the offboarding (deauthorize) button. - * - * @param array $subsections - * @param bool $is_onboard - * @return array - */ - public function addOffboardButton(array $subsections, bool $is_onboard): array - { - $onboard_sandbox_section = $this->getOnboardSandboxSection(); - // If we are connected, display the seller merchant ID. - $seller_id_section = $this->getSellerIdSection(); - // Section to be displayed when onboard. - $subsections['paypal_offboard_btn'] = new EE_Form_Section_HTML( - EEH_HTML::tr( - $this->option_heading - . EEH_HTML::td( - EEH_HTML::img( - EEP_PAYPAL_COMMERCE_URL . 'assets' . DS . 'lib' . DS . 'paypal-onboard.png', - '', - 'eea_paypal_offboard_ico', - 'eea-paypal-offboard-ico' - ) - . EEH_HTML::strong( - esc_html__('Connected.', 'event_espresso'), - 'eea_paypal_offboard_txt_' . $this->pm_slug, - 'eea-paypal-offboard-txt' - ) - . $onboard_sandbox_section - . $seller_id_section - . EEH_HTML::link( - '#', - EEH_HTML::span(esc_html__('Disconnect', 'event_espresso')), - '', - 'eea_paypal_offboard_btn_' . $this->pm_slug, - 'eea-paypal-onboard-btn button button--primary' - ) - ), - 'eea_paypal_offboard_section_' . $this->pm_slug, - 'eea-offboard-section-' . $this->pm_slug, - // Are we onboard ? - ! $is_onboard ? 'display:none;' : '' - ), - ['required' => true] - ); - return $subsections; - } - - - /** - * Add the PM slug holder. - * - * @param array $subsections - * @return array - */ - public function addPmSlugHolder(array $subsections): array - { - $subsections['paypal_pm_slug_holder'] = new EE_Form_Section_HTML( - EEH_HTML::span( - '', - 'eea_paypal_pm_slug', - 'eea-paypal-pm-slug', - 'display:none;' - ) - ); - return $subsections; - } - - - /** - * HTML for the disconnect warning dialog. - * - * @return EE_Form_Section_Base - */ - public function disconnectDialogHtml(): EE_Form_Section_Base - { - $message = esc_html__( - 'Disconnecting your PayPal account will prevent you from offering PayPal services and products on your website.', - 'event_espresso' - ); - return new EE_Form_Section_HTML( - EEH_HTML::tr( - EEH_HTML::td( - EEH_HTML::strong( - $message, - 'eea_paypal_dialog_txt_' . $this->pm_slug, - 'eea-paypal-dialog-txt' - ) . - EEH_HTML::link( - '', - 'Cancel', - 'cancel, go back', - 'eea_paypal_dialog_cancel_' . $this->pm_slug, - 'eea-paypal-dialog-cancel button button--secondary' - ) . - EEH_HTML::link( - '', - 'Disconnect', - 'ok, continue', - 'eea_paypal_dialog_ok_' . $this->pm_slug, - 'eea-paypal-dialog-ok button button--primary-alt' - ) - ), - 'eea_paypal_disconnect_dialog_' . $this->pm_slug - ) - ); + $subsections = $this->form_html->addOnboardButton($subsections, $is_onboard); + $subsections = $this->form_html->addOffboardButton($subsections, $is_onboard); + return $this->form_html->addPmSlugHolder($subsections); } @@ -330,6 +111,7 @@ public function disconnectDialogHtml(): EE_Form_Section_Base * * @return void * @throws EE_Error + * @throws ReflectionException */ public function enqueue_js() { @@ -344,14 +126,18 @@ public function enqueue_js() 'pm_slug' => $payment_method->slug(), ]; } - + $countries_iso = ["US", "AU", "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", + "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE"]; $parameters = [ - 'onboard_btn_text' => $this->onboard_btn_text, - 'sandbox_btn_text' => $this->sandbox_btn_text, - 'sandbox_text' => $this->authed_sandbox_text, + 'onboard_btn_text' => $this->form_html->onboard_btn_text, + 'sandbox_btn_text' => $this->form_html->sandbox_btn_text, + 'sandbox_text' => $this->form_html->authed_sandbox_text, 'pm_versions' => $pm_versions, 'onboarding_url' => $this->onboarding_url, - 'disconnect_dialog' => $this->disconnectDialogHtml()->get_html(), + 'supported_countries' => $countries_iso, + 'connect_dialog' => $this->form_html->connectDialog()->get_html(), + 'disconnect_dialog' => $this->form_html->disconnectDialog()->get_html(), + 'processing_mask' => $this->form_html->processingMask()->get_html(), 'ee_default_styles' => EE_ADMIN_URL . 'assets/ee-admin-page.css', 'wp_stylesheet' => includes_url('css/dashicons.min.css'), 'can_disable_input' => method_exists('EE_Form_Input_Base', 'isDisabled'), @@ -362,7 +148,7 @@ public function enqueue_js() 'event_espresso' ), 'unknown_container' => esc_html__('Could not specify the parent form.', 'event_espresso'), - 'pm_nice_name' => esc_html__('PayPal Payments', 'event_espresso'), + 'pm_nice_name' => esc_html__('PayPal Commerce', 'event_espresso'), 'blocked_popup_notice' => esc_html__( 'The authentication process could not be executed. Please allow window pop-ups in your browser for this website in order to process a successful authentication.', 'event_espresso' @@ -380,21 +166,19 @@ public function enqueue_js() 'event_espresso' ), ]; - // Styles. wp_enqueue_style( 'eea_paypal_onboard_form_styles', EEP_PAYPAL_COMMERCE_URL . 'assets' . DS . 'css' . DS . 'eea-paypal-onboard.css', [], - filemtime(EEP_PAYPAL_COMMERCE_DIR . 'assets' . DS . 'css' . DS . 'eea-paypal-onboard.css') + EVENT_ESPRESSO_VERSION ); - // Scripts. wp_enqueue_script( 'eea_paypal_onboard_form_scripts', EEP_PAYPAL_COMMERCE_URL . 'assets' . DS . 'js' . DS . 'eea-paypal-onboarding.js', [], - filemtime(EEP_PAYPAL_COMMERCE_DIR . 'assets' . DS . 'js' . DS . 'eea-paypal-onboarding.js') + EVENT_ESPRESSO_VERSION ); wp_enqueue_script( 'eea_paypal_partner_script', @@ -402,10 +186,8 @@ public function enqueue_js() [], EVENT_ESPRESSO_VERSION ); - // Localize the script with some extra data. wp_localize_script('eea_paypal_onboard_form_scripts', 'eeaPPOnboardParameters', $parameters); - parent::enqueue_js(); } } diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingFormHtml.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingFormHtml.php new file mode 100644 index 00000000000..b59dd002793 --- /dev/null +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/OnboardingFormHtml.php @@ -0,0 +1,476 @@ +payment_method = $pmt; + $this->pm_instance = $payment_method; + $this->pm_slug = $this->pm_instance->slug(); + $this->onboard_btn_text = esc_html__('Connect with PayPal', 'event_espresso'); + $this->sandbox_btn_text = esc_html__('Connect with PayPal (sandbox)', 'event_espresso'); + $this->authed_sandbox_text = esc_html__('(using sandbox credentials)', 'event_espresso'); + } + + + /** + * Form a heading. + * + * @param string $text + * @return string + */ + public function getHeader(string $text = ''): string + { + $text = $text ?: esc_html__('PayPal Onboarding: %1$s', 'event_espresso'); + return EEH_HTML::th( + sprintf( + $text, + $this->payment_method->get_help_tab_link() + ), + 'eea_paypal_onboard_heading_' . $this->pm_slug, + 'eea-paypal-onboard-heading' + ); + } + + + /** + * Add the onboarding button. + * + * @param array $subsections + * @param bool $is_onboard + * @return array + */ + public function addOnboardButton(array $subsections, bool $is_onboard): array + { + // Section to be displayed if not onboard. + $subsections['paypal_onboard_btn'] = new EE_Form_Section_HTML( + EEH_HTML::tr( + $this->getHeader() . + EEH_HTML::td( + EEH_HTML::button( + $this->onboard_btn_text, + 'eea-paypal-onboard-btn button button--primary', + '', + 'eea_paypal_onboard_btn_' . $this->pm_slug, + ) + . EEH_HTML::link( + '#', + '', + '', + 'eea_paypal_onboard_trigger_btn_' . $this->pm_slug, + 'eea-paypal-onboard-trigger-btn', + '', + 'data-ee-pm-slug="' . $this->pm_slug . '" data-paypal-button="true"' + ) + ), + 'eea_paypal_onboard_section_' . $this->pm_slug, + 'eea-onboard-section eea-onboard-section-' . $this->pm_slug, + // Are we onboard ? + $is_onboard ? 'display:none;' : '' + ), + ['required' => true] + ); + return $subsections; + } + + + /** + * Add the offboarding (deauthorize) button. + * + * @param array $subsections + * @param bool $is_onboard + * @return array + */ + public function addOffboardButton(array $subsections, bool $is_onboard): array + { + $onboard_sandbox_section = $this->getOnboardSandboxSection(); + // If we are connected, display the seller merchant ID. + $seller_id_section = $this->getSellerIdSection(); + // Section to be displayed when onboard. + $subsections['paypal_offboard_btn'] = new EE_Form_Section_HTML( + EEH_HTML::tr( + $this->getHeader() + . EEH_HTML::td( + EEH_HTML::img( + EEP_PAYPAL_COMMERCE_URL . 'assets' . DS . 'lib' . DS . 'paypal-onboard.png', + '', + 'eea_paypal_offboard_ico', + 'eea-paypal-offboard-ico' + ) + . EEH_HTML::strong( + esc_html__('Connected.', 'event_espresso'), + 'eea_paypal_offboard_txt_' . $this->pm_slug, + 'eea-paypal-offboard-txt' + ) + . $onboard_sandbox_section + . $seller_id_section + . EEH_HTML::link( + '#', + EEH_HTML::span(esc_html__('Disconnect', 'event_espresso')), + '', + 'eea_paypal_offboard_btn_' . $this->pm_slug, + 'eea-paypal-onboard-btn button button--primary' + ) + ), + 'eea_paypal_offboard_section_' . $this->pm_slug, + 'eea-offboard-section-' . $this->pm_slug, + // Are we onboard ? + ! $is_onboard ? 'display:none;' : '' + ), + ['required' => true] + ); + return $subsections; + } + + + /** + * Get the sandbox onboarding section contents. + * + * @return string + */ + public function getOnboardSandboxSection(): string + { + // Is this a test onboarding ? + $sandbox_mode_text = $this->pm_instance->debug_mode() ? $this->authed_sandbox_text : ''; + return ' ' . EEH_HTML::strong( + $sandbox_mode_text, + 'eea_paypal_onboard_test_txt_' . $this->pm_slug, + 'eea-paypal-onboard-test-txt' + ); + } + + + /** + * Get the seller merchant ID section contents. + * + * @return string + */ + public function getSellerIdSection(): string + { + try { + $is_third_party = EED_PayPalCommerce::isThirdParty($this->pm_instance); + $meta_key = $is_third_party ? Domain::META_KEY_SELLER_MERCHANT_ID : Domain::META_KEY_PAYER_ID; + $payer_id = PayPalExtraMetaManager::getPmOption($this->pm_instance, $meta_key) ?: '--'; + } catch (Exception $e) { + $payer_id = '--'; + } + return ' ' . EEH_HTML::strong( + sprintf(esc_html__('Linked account ID: %1$s', 'event_espresso'), $payer_id), + 'eea_paypal_seller_id_' . $this->pm_slug, + 'eea-paypal-seller-id' + ); + } + + + /** + * Add the PM slug holder. + * + * @param array $subsections + * @return array + */ + public function addPmSlugHolder(array $subsections): array + { + $subsections['paypal_pm_slug_holder'] = new EE_Form_Section_HTML( + EEH_HTML::span( + '', + 'eea_paypal_pm_slug', + 'eea-paypal-pm-slug', + 'display:none;' + ) + ); + return $subsections; + } + + + /** + * HTML for onboarding settings dialog. + * + * @return EE_Form_Section_Base + */ + public function connectDialog(): EE_Form_Section_Base + { + return new EE_Form_Section_HTML( + EEH_HTML::div( + $this->countriesSelectHtml() . + $this->paymentOptionsHtml() . + EEH_HTML::div( + EEH_HTML::link( + '', + 'Cancel', + 'cancel, go back', + 'eea_ppc_connect_cancel_' . $this->pm_slug, + 'eea-ppc-connect-cancel button button--secondary' + ) . + EEH_HTML::link( + '', + 'Continue', + 'ok, continue', + 'eea_ppc_connect_ok_' . $this->pm_slug, + 'eea-ppc-connect-ok button button--primary-alt' + ), + '', + 'eep-ppc-dialog-yes-no' + ), + 'eea_paypal_connect_dialog_' . $this->pm_slug + ) + ); + } + + + /** + * Form a countries select and return as html. + * + * @return string + */ + public function countriesSelectHtml(): string + { + $countries = []; + $countries_select = EEH_HTML::strong( + esc_html__('Please select a country where your PayPal account is registered:', 'event_espresso'), + 'eep_ppc_country_txt_' . $this->pm_slug, + 'eep-ppc-country-txt' + ); + try { + // Get all countries list. + $countries_iso = EEM_Country::instance()->get_all_countries(); + foreach ($countries_iso as $iso => $country) { + if ($country instanceof EE_Country) { + $countries [ $iso ] = $country->get('CNT_name'); + } + } + // Now build the select input. + $select_input = new EE_Select_Input( + $countries, + [ + 'html_name' => 'eep_ppc_country_' . $this->pm_slug, + 'html_id' => 'eep_ppc_country_' . $this->pm_slug, + 'html_class' => 'eep-ppc-country-' . $this->pm_slug, + 'html_label_text' => esc_html__('Select country', 'event_espresso'), + 'required' => true, + 'default' => EE_Config::instance()->organization->CNT_ISO ?? 'US' + ] + ); + $countries_select .= $select_input->get_html_for_input(); + } catch (Exception $e) { + // Countries list set at this point, so just continue. + } + return $countries_select; + } + + + /** + * Form the payment options checkboxes and return as html. + * + * @return string + */ + public function paymentOptionsHtml(): string + { + $options_lbl = EEH_HTML::strong( + esc_html__('Select a product you\'d like to allow at checkout:', 'event_espresso'), + 'eea_ppc_product_txt_' . $this->pm_slug, + 'eea-ppc-product-txt' + ); + $express_checkout_lbl = EEH_HTML::label( + esc_html__('Accept PayPal ', 'event_espresso') + . EEH_HTML::img( + 'https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png', + '', + 'eea_ppc_express_checkout_img_' . $this->pm_slug, + 'eea-ppc-express-checkout-img' + ), + '', + '', + '', + 'for="' . 'eep_ppc_checkout_type_' . $this->pm_slug .'-express_checkout"' + ); + $ppcp_lbl = EEH_HTML::label( + esc_html__('Accept credit and debit card payments with PayPal ', 'event_espresso') + . EEH_HTML::img( + 'https://www.paypalobjects.com/digitalassets/c/website/marketing/apac/C2/logos-buttons/optimize/Full_Online_Tray_RGB.png', + '', + 'eea_ppc_ppcp_img_' . $this->pm_slug, + 'eea-ppc-ppcp-img' + ), + '', + '', + '', + 'for="' . 'eep_ppc_checkout_type_' . $this->pm_slug .'-ppcp"' + ); + $checkbox = new EE_Checkbox_Multi_Input( + [ + 'express_checkout' => $express_checkout_lbl, + 'ppcp' => $ppcp_lbl, + ], + [ + 'html_id' => 'eep_ppc_checkout_type_' . $this->pm_slug, + 'html_label_text' => esc_html__( + 'Select a product you\'d like to allow at checkout.', + 'event_espresso' + ), + 'html_help_text' => esc_html__( + 'Select a product you\'d like to allow at checkout.', + 'event_espresso' + ), + 'default' => ['express_checkout', 'ppcp'] + ] + ); + try { + return EEH_HTML::div( + $options_lbl + . $checkbox->get_html_for_input(), + 'eep_ppc_checkout_types_' . $this->pm_slug, + 'eep-ppc-checkout-types' + ); + } catch (Exception $e) { + return ''; + } + } + + + /** + * HTML for onboarding settings dialog. + * + * @return EE_Form_Section_Base + */ + public function processingMask(): EE_Form_Section_Base + { + return new EE_Form_Section_HTML( + EEH_HTML::div( + EEH_HTML::div(' ', 'eep_ppc_processing_mask', 'eep-ppc-processing-mask') . + EEH_HTML::div(' ', 'eep_ppc_processing_spinner', 'ee-spinner ee-spin') . + EEH_HTML::div( + esc_html__('The login window should be open. If you don\'t see a new PayPal login window you might need to enable pop-ups in your browser in order to continue.') . + EEH_HTML::div( + EEH_HTML::link( + '#', + esc_html__('Focus window'), + esc_html__('Focus window'), + 'eep_ppc_window_focus_' . $this->pm_slug, + 'eep-ppc-window-focus' + ) + ), + 'eep_ppc_processing_message', + 'eep-ppc-processing-message' + ), + 'eep_ppc_processing_' . $this->pm_slug, + 'eep-ppc-processing' + ) + ); + } + + + /** + * HTML for the disconnect warning dialog. + * + * @return EE_Form_Section_Base + */ + public function disconnectDialog(): EE_Form_Section_Base + { + $message = esc_html__( + 'Disconnecting your PayPal account will prevent you from offering PayPal services and products on your website.', + 'event_espresso' + ); + return new EE_Form_Section_HTML( + EEH_HTML::div( + EEH_HTML::strong( + $message, + 'eep_ppc_disconnect_dialog_txt_' . $this->pm_slug, + 'eep-ppc-disconnect-dialog-txt' + ) . + EEH_HTML::div( + EEH_HTML::link( + '', + 'Cancel', + 'cancel, go back', + 'eep_ppc_disconnect_cancel_' . $this->pm_slug, + 'eep-ppc-disconnect-cancel button button--secondary' + ) . + EEH_HTML::link( + '', + 'Disconnect', + 'ok, continue', + 'eep_ppc_disconnect_ok_' . $this->pm_slug, + 'eep-ppc-disconnect-ok button button--primary-alt' + ), + '', + 'eep-ppc-dialog-yes-no' + ), + 'eep_paypal_disconnect_dialog_' . $this->pm_slug + ) + ); + } +} diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php index 572c02013cb..7c4b7fb19cf 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/forms/SettingsForm.php @@ -3,12 +3,17 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\PayPalCheckout\forms; use EE_Payment_Method_Form; +use EE_PMT_Base; use EE_PMT_PayPalCheckout; use EE_Payment_Method; use EE_Error; use EE_Form_Section_HTML; +use EE_Select_Input; use EED_PayPalOnboard; use EEH_HTML; +use EventEspresso\PaymentMethods\PayPalCommerce\domain\Domain; +use EventEspresso\PaymentMethods\PayPalCommerce\tools\extra_meta\PayPalExtraMetaManager; +use ReflectionException; /** * Class SettingsForm @@ -19,22 +24,47 @@ */ class SettingsForm extends EE_Payment_Method_Form { + /** + * Payment method. + * + * @var EE_PMT_Base|null + */ + protected $payment_method = null; + + /** + * Payment method instance. + * + * @var EE_PMT_Base|null + */ + protected $pm_instance = null; + + /** * Class constructor. * * @param EE_PMT_PayPalCheckout $payment_method * @param EE_Payment_Method $pm_instance + * @throws EE_Error + * @throws ReflectionException */ public function __construct(EE_PMT_PayPalCheckout $payment_method, EE_Payment_Method $pm_instance) { - $form_parameters = []; + $form_parameters = []; + $this->payment_method = $payment_method; + $this->pm_instance = $pm_instance; + // Allow Advanced Card Checkout if PPCP checkout type was possible and selected. + $allowed_type = PayPalExtraMetaManager::getPmOption($pm_instance, Domain::META_KEY_ALLOWED_CHECKOUT_TYPE); + $is_onboard = EED_PayPalOnboard::isOnboard($pm_instance); + if ($is_onboard && ($allowed_type === 'ppcp' || $allowed_type === 'all')) { + $form_parameters = $this->addCheckoutTypeSelect($form_parameters); + } // Build the PM form. parent::__construct($form_parameters); // Add a form for PayPal Onboard. $this->addOnboardingForm($payment_method, $pm_instance); // Add the clear data button. $this->clearMetadataButton($pm_instance); - // Disable inputs if needed + // Disable inputs if needed. $this->toggleSubsections($pm_instance); } @@ -46,14 +76,12 @@ public function __construct(EE_PMT_PayPalCheckout $payment_method, EE_Payment_Me * @param EE_Payment_Method $pm_instance * @return void */ - public function addOnboardingForm(EE_PMT_PayPalCheckout $payment_method, EE_Payment_Method $pm_instance) + public function addOnboardingForm(EE_PMT_PayPalCheckout $payment_method, EE_Payment_Method $pm_instance): void { // Add the connect button before the app id field. try { - // $template = new OnboardingForm($payment_method, $pm_instance); $this->add_subsections( [ - // 'paypal_onboard' => new EE_Form_Section_HTML($template->get_html_and_js()), 'paypal_onboard' => new OnboardingForm($payment_method, $pm_instance), ], 'PMD_debug_mode', @@ -65,20 +93,53 @@ public function addOnboardingForm(EE_PMT_PayPalCheckout $payment_method, EE_Paym } + /** + * Add a checkout type select. + * + * @param array $form_parameters + * @return array + */ + public function addCheckoutTypeSelect(array $form_parameters): array + { + $pm_slug = $this->pm_instance->slug(); + $allowed_checkout_type = PayPalExtraMetaManager::getPmOption( + $this->pm_instance, + Domain::META_KEY_ALLOWED_CHECKOUT_TYPE + ); + // Section to be displayed if onboard. + $form_parameters['extra_meta_inputs'] = [ + Domain::META_KEY_CHECKOUT_TYPE => new EE_Select_Input( + [ + 'express_checkout' => esc_html__('Express Checkout', 'event_espresso'), + 'ppcp' => esc_html__('Advanced Credit and Debit Card payments', 'event_espresso'), + 'all' => esc_html__('Both, ACDC and Express Checkout', 'event_espresso'), + ], + [ + 'html_name' => 'eep_checkout_type_option_' . $pm_slug, + 'html_id' => 'eep_checkout_type_option_' . $pm_slug, + 'html_class' => 'eep-checkout-type-option-' . $pm_slug, + 'required' => true, + 'default' => $allowed_checkout_type, + ] + ), + ]; + return $form_parameters; + } + + /** * Adds a button for clearing the PM metadata. * - * @param EE_Payment_Method $pm_instance + * @param EE_Payment_Method $pm_instance * @return void */ - public function clearMetadataButton(EE_Payment_Method $pm_instance) + public function clearMetadataButton(EE_Payment_Method $pm_instance): void { try { $is_onboard = EED_PayPalOnboard::isOnboard($pm_instance); if ($is_onboard) { return; } - $button_text = sprintf( esc_html__('Clear %1$s metadata', 'event_espresso'), $pm_instance->admin_name() @@ -86,20 +147,18 @@ public function clearMetadataButton(EE_Payment_Method $pm_instance) $this->add_subsections( [ 'clear_pm_metadata' => new EE_Form_Section_HTML( - // EEH_HTML::table( EEH_HTML::tr( EEH_HTML::th(esc_html__('Clear PM metadata ?', 'event_espresso')) . - EEH_HTML::td( - EEH_HTML::link( - '', - $button_text, - $button_text, - 'eea_clear_metadata_' . $pm_instance->slug(), - 'espresso-button button button--secondary' - ) + EEH_HTML::td( + EEH_HTML::link( + '', + $button_text, + $button_text, + 'eea_clear_metadata_' . $pm_instance->slug(), + 'espresso-button button button--secondary' ) + ) ) - // ) ), ], 'PMD_order', @@ -118,7 +177,7 @@ public function clearMetadataButton(EE_Payment_Method $pm_instance) * @return void * @throws EE_Error */ - private function toggleSubsections(EE_Payment_Method $pm_instance) + private function toggleSubsections(EE_Payment_Method $pm_instance): void { $onboard = EED_PayPalOnboard::isOnboard($pm_instance); // Don't allow changing the debug mode setting while connected. diff --git a/PaymentMethods/PayPalCommerce/PayPalCheckout/templates/paymentButtons.template.php b/PaymentMethods/PayPalCommerce/PayPalCheckout/templates/paymentButtons.template.php index c6403c2a304..60bf845ed8f 100644 --- a/PaymentMethods/PayPalCommerce/PayPalCheckout/templates/paymentButtons.template.php +++ b/PaymentMethods/PayPalCommerce/PayPalCheckout/templates/paymentButtons.template.php @@ -5,7 +5,10 @@ * * @package Event Espresso * @subpackage eea-paypal-commerce + * + * Template arguments: + * @type bool $pm_slug The payment method slug. */ ?> -
+ diff --git a/PaymentMethods/PayPalCommerce/api/FirstPartyPayPalApi.php b/PaymentMethods/PayPalCommerce/api/FirstPartyPayPalApi.php new file mode 100644 index 00000000000..f6ae15764b3 --- /dev/null +++ b/PaymentMethods/PayPalCommerce/api/FirstPartyPayPalApi.php @@ -0,0 +1,127 @@ +client_id = $client_id; + $this->client_secret = $client_secret; + $this->bn_code = $bn_code; + } + + + /** + * Send an API request. + * + * @param array $body_parameters + * @param string $endpoint + * @param string $method + * @param array $headers + * @return Object|array + */ + public function sendRequest(array $body_parameters, string $endpoint, string $method = 'POST', array $headers = []) + { + $request_parameters = $this->getRequestParameters($body_parameters, $method, $headers); + return $this->response($endpoint, $request_parameters); + } + + + /** + * Build the request parameters. + * + * @param array $body_parameters + * @param string $method + * @param array $headers + * @return array + */ + private function getRequestParameters(array $body_parameters, string $method, array $headers): array + { + $request_parameters = [ + 'method' => $method, + 'timeout' => 60, + 'redirection' => 5, + 'blocking' => true, + ]; + $default_headers = [ + 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), + 'PayPal-Partner-Attribution-Id' => $this->bn_code, + 'Content-Type' => 'application/json', + 'Authorization' => 'Basic ' . base64_encode( + $this->client_id . ':' . $this->client_secret + ), + ]; + $request_parameters['headers'] = array_merge($default_headers, $headers); + // Add body if this is a POST request. + if ($body_parameters && ($method === 'POST' || $method === 'PUT')) { + $request_parameters['body'] = json_encode($body_parameters); + } + return $request_parameters; + } + + + /** + * @return string + */ + public function clientId(): string + { + return $this->client_id; + } + + + /** + * @return string + */ + public function clientSecret(): string + { + return $this->client_secret; + } + + + /** + * @return string + */ + public function bnCode(): string + { + return $this->bn_code; + } +} diff --git a/PaymentMethods/PayPalCommerce/api/PayPalApi.php b/PaymentMethods/PayPalCommerce/api/PayPalApi.php index 7676b3490a5..eb34d3572d7 100644 --- a/PaymentMethods/PayPalCommerce/api/PayPalApi.php +++ b/PaymentMethods/PayPalCommerce/api/PayPalApi.php @@ -2,92 +2,59 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\api; -use EE_Error; use EventEspresso\PaymentMethods\PayPalCommerce\tools\logging\PayPalLogger; /** * Class PayPalApi * - * A base class for all PayPal API components used in this add-on. + * Abstract parent class for all PayPal API implementations. * * @package Event Espresso * @subpackage eea-paypal-commerce * @author Nazar Kolivoshka */ -class PayPalApi +abstract class PayPalApi { /** - * Client ID. Used to process payments. - * - * @var string - */ - protected $client_id = ''; - - /** - * Client secret. Used to process payments. - * - * @var string + * @var bool Debug mode enabled ? */ - protected $client_secret = ''; + protected bool $sandbox_mode; /** - * BN Code. Partner-Attribution-Id. + * API request/response validation helper. * - * @var string + * @var ResponseInspector */ - protected $bn_code = ''; + protected ResponseInspector $inspector; /** * @var string PayPal API endpoint. */ - protected $api_endpoint = ''; - - /** - * @var bool Debug mode enabled ? - */ - protected $sandbox_mode; - - /** - * API request/response validation helper. - * - * @var ResponseInspector - */ - protected $inspector; + protected string $api_endpoint = ''; /** - * @param string $client_id - * @param string $client_secret - * @param string $bn_code - * @param bool $sandbox_mode + * @param bool $sandbox_mode */ - public function __construct(string $client_id, string $client_secret, string $bn_code, bool $sandbox_mode = true) - { - $this->client_id = $client_id; - $this->client_secret = $client_secret; - $this->sandbox_mode = $sandbox_mode; - $this->bn_code = $bn_code; + public function __construct(bool $sandbox_mode = true) { + $this->sandbox_mode = $sandbox_mode; // Is this a sandbox request. $this->api_endpoint = $this->sandbox_mode ? 'https://api-m.sandbox.paypal.com/v2/' : 'https://api-m.paypal.com/v2/'; - $this->inspector = new ResponseInspector(); + $this->inspector = new ResponseInspector(); } /** * Send an API request. * - * @param array $body_parameters * @param string $endpoint - * @param string $method - * @param array $headers + * @param array $request_parameters * @return Object|array - * @throws EE_Error */ - public function sendRequest(array $body_parameters, string $endpoint, string $method = 'POST', array $headers = []) + public function response(string $endpoint, array $request_parameters) { - $request_parameters = $this->getRequestParameters($body_parameters, $method, $headers); // Sent the API request. $response = wp_remote_request($endpoint, $request_parameters); // Validate the response. @@ -109,39 +76,6 @@ public function sendRequest(array $body_parameters, string $endpoint, string $me } - /** - * Build the request parameters. - * - * @param array $body_parameters - * @param string $method - * @param array $headers - * @return array - */ - private function getRequestParameters(array $body_parameters, string $method, array $headers): array - { - $request_parameters = [ - 'method' => $method, - 'timeout' => 60, - 'redirection' => 5, - 'blocking' => true, - ]; - $default_headers = [ - 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), - 'PayPal-Partner-Attribution-Id' => $this->bn_code, - 'Content-Type' => 'application/json', - 'Authorization' => 'Basic ' . base64_encode( - $this->client_id . ':' . $this->client_secret - ), - ]; - $request_parameters['headers'] = array_merge($default_headers, $headers); - // Add body if this is a POST request. - if ($body_parameters && ($method === 'POST' || $method === 'PUT')) { - $request_parameters['body'] = json_encode($body_parameters); - } - return $request_parameters; - } - - /** * @return string */ @@ -149,31 +83,4 @@ public function apiEndpoint(): string { return $this->api_endpoint; } - - - /** - * @return string - */ - public function clientId(): string - { - return $this->client_id; - } - - - /** - * @return string - */ - public function clientSecret(): string - { - return $this->client_secret; - } - - - /** - * @return string - */ - public function bnCode(): string - { - return $this->bn_code; - } } diff --git a/PaymentMethods/PayPalCommerce/api/ResponseInspector.php b/PaymentMethods/PayPalCommerce/api/ResponseInspector.php index 8089cc0703f..6f1d6f18fe4 100644 --- a/PaymentMethods/PayPalCommerce/api/ResponseInspector.php +++ b/PaymentMethods/PayPalCommerce/api/ResponseInspector.php @@ -95,7 +95,7 @@ public function validateResponse($response): bool 'error' => $response->get_error_code(), 'message' => sprintf( esc_html__('Response error. Message: %1$s.', 'event_espresso'), - $response->get_error_messages() + $response->get_error_message() ), ] ); diff --git a/PaymentMethods/PayPalCommerce/api/ThirdPartyPayPalApi.php b/PaymentMethods/PayPalCommerce/api/ThirdPartyPayPalApi.php new file mode 100644 index 00000000000..936d63c3984 --- /dev/null +++ b/PaymentMethods/PayPalCommerce/api/ThirdPartyPayPalApi.php @@ -0,0 +1,139 @@ +access_token = $access_token; + $this->partner_client_id = $partner_client_id; + $this->payer_id = $payer_id; + $this->bn_code = $bn_code; + } + + + /** + * Send an API request. + * + * @param array $body_parameters + * @param string $endpoint + * @param string $method + * @param array $headers + * @return Object|array + */ + public function sendRequest(array $body_parameters, string $endpoint, string $method = 'POST', array $headers = []) + { + $request_parameters = $this->getRequestParameters($body_parameters, $method, $headers); + return $this->response($endpoint, $request_parameters); + } + + + /** + * Build the request parameters. + * + * @param array $body_parameters + * @param string $method + * @param array $headers + * @return array + */ + private function getRequestParameters(array $body_parameters, string $method, array $headers): array + { + $request_parameters = [ + 'method' => $method, + ]; + $default_headers = [ + 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), + 'PayPal-Partner-Attribution-Id' => $this->bnCode(), + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $this->accessToken(), + ]; + // If we have merchant credentials then we are onboard and can do requests on behalf of the seller. + if ($this->partner_client_id && $this->payer_id) { + $assertion1 = [ + 'alg' => 'none', + ]; + $assertion2 = [ + 'iss' => $this->partner_client_id, + 'payer_id' => $this->payer_id, + ]; + $default_headers['PayPal-Auth-Assertion'] = base64_encode(json_encode($assertion1, JSON_HEX_APOS)) . '.' . + base64_encode(json_encode($assertion2, JSON_HEX_APOS)) . '.'; + } + $request_parameters['headers'] = array_merge($default_headers, $headers); + // Add body if this is a POST request. + if ($body_parameters && ($method === 'POST' || $method === 'PUT')) { + $request_parameters['body'] = json_encode($body_parameters); + } + return $request_parameters; + } + + + /** + * @return string + */ + public function accessToken(): string + { + return $this->access_token; + } + + + /** + * @return string + */ + public function bnCode(): string + { + return $this->bn_code; + } +} diff --git a/PaymentMethods/PayPalCommerce/api/clients/ClientToken.php b/PaymentMethods/PayPalCommerce/api/clients/ClientToken.php new file mode 100644 index 00000000000..b046407ff4d --- /dev/null +++ b/PaymentMethods/PayPalCommerce/api/clients/ClientToken.php @@ -0,0 +1,76 @@ +request_url = $this->request_url . "/generate-token"; + } + + + /** + * Get the onboarding status and validate it. + * + * @return array + */ + public function getToken(): array + { + // Send GET request. + $response = $this->api->sendRequest([], $this->request_url); + return $this->validateResponse($response); + } + + + /** + * Makes sure that we have received the client token. Returns an error as array if not. + * + * @param array $response + * @return array + */ + public function validateResponse(array $response): array + { + // Could this be an error ? + if (! empty($response['error'])) { + return $response; + } + if (! empty($response['name']) && ! empty($response['message'])) { + return ['error' => $response['name'], 'message' => $response['message']]; + } + // Check the data we received. + if (empty($response[ Domain::API_KEY_CLIENT_TOKEN ])) { + $err_msg = esc_html__('No client token was found in the Client Token request response.', 'event_espresso'); + PayPalLogger::errorLog($err_msg, $response); + return ['error' => 'ONBOARDING_MISSING_CLIENT_TOKEN', 'message' => $err_msg]; + } + return [ + 'valid' => true, + 'client_token' => $response[ Domain::API_KEY_CLIENT_TOKEN ], + 'expires_in' => $response[ Domain::API_KEY_EXPIRES_IN ] + ]; + } +} diff --git a/PaymentMethods/PayPalCommerce/api/clients/ClientsApi.php b/PaymentMethods/PayPalCommerce/api/clients/ClientsApi.php new file mode 100644 index 00000000000..93adefe5c5e --- /dev/null +++ b/PaymentMethods/PayPalCommerce/api/clients/ClientsApi.php @@ -0,0 +1,45 @@ +api = $api; + // Is this a sandbox request. + $api_endpoint = $sandbox_mode + ? 'https://api-m.sandbox.paypal.com/' + : 'https://api-m.paypal.com/'; + $this->request_url = $api_endpoint . 'v1/identity'; + } +} diff --git a/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php b/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php index 7a7d6893ca8..481ab14bab2 100644 --- a/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php +++ b/PaymentMethods/PayPalCommerce/api/orders/CaptureOrder.php @@ -25,14 +25,14 @@ class CaptureOrder extends OrdersApi * * @var string */ - protected $currency_code; + protected string $currency_code; /** * Transaction this order is for. * * @var EE_Transaction */ - protected $transaction; + protected EE_Transaction $transaction; /** @@ -55,7 +55,6 @@ public function __construct(PayPalApi $api, EE_Transaction $transaction, string * Capture payment for PayPal Order. * * @return array - * @throws EE_Error */ public function capture(): array { diff --git a/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php b/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php index b1eabac6b5d..6ccc4ce86ca 100644 --- a/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php +++ b/PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php @@ -5,7 +5,6 @@ use EE_Error; use EE_Line_Item; use EE_Transaction; -use EEM_State; use EventEspresso\core\domain\services\validation\email\strategies\Basic; use EventEspresso\core\services\request\sanitizers\RequestSanitizer; use EventEspresso\PaymentMethods\PayPalCommerce\api\PayPalApi; @@ -15,6 +14,7 @@ /** * Class CreateOrder + * * Generates and sends a Create Order request using PayPal API. * * @package Event Espresso @@ -49,21 +49,21 @@ class CreateOrder extends OrdersApi * * @var string */ - protected $currency_code; + protected string $currency_code; /** * Billing info. * * @var array */ - protected $billing_info; + protected array $billing_info; /** * Transaction this order is for. * * @var EE_Transaction */ - protected $transaction; + protected EE_Transaction $transaction; /** @@ -88,10 +88,12 @@ public function __construct(PayPalApi $api, EE_Transaction $transaction, array $ * @param array $billing_info * @return void */ - public function sanitizeRequestParameters(array $billing_info) + public function sanitizeRequestParameters(array $billing_info): void { - $sanitizer = new RequestSanitizer(new Basic()); - foreach ($billing_info as $item => $value) { + $email_validator = new Basic(); + $sanitizer = new RequestSanitizer($email_validator); + foreach ($billing_info as $item => $value) + { $this->billing_info[ $item ] = $sanitizer->clean($value); } } @@ -123,16 +125,13 @@ public function create(): array protected function getParameters(): array { $registrant = $this->transaction->primary_registration(); + $attendee = $registrant->attendee(); $event = $registrant->event(); - $description = $event->name() ?? esc_html__('Tickets for an event', 'event_espresso'); - $state = EEM_State::instance()->get_col( - [ - ['STA_ID' => $this->billing_info['bill_state']], - 'limit' => 1, - ], - 'STA_abbrev' - )[0]; - $params = [ + $description = $event->name() ?: sprintf( + esc_html__('Tickets for an event at %1$s', 'event_espresso'), + get_bloginfo('name') + ); + return [ 'intent' => 'CAPTURE', 'purchase_units' => [ [ @@ -151,28 +150,14 @@ protected function getParameters(): array 'user_action' => 'PAY_NOW', ], 'payer' => [ - 'email_address' => $this->billing_info['bill_email'], + 'email_address' => $attendee->email(), 'name' => [ - 'given_name' => substr($this->billing_info['bill_first_name'], 0, 139), - 'surname' => substr($this->billing_info['bill_last_name'], 0, 139), + 'given_name' => $attendee->fname(), + 'surname' => $attendee->lname(), ], - 'address' => [ - 'country_code' => $this->billing_info['bill_country'], - 'address_line_1' => substr($this->billing_info['bill_address'], 0, 299), - 'admin_area_1' => $state ?? '', - 'admin_area_2' => substr($this->billing_info['bill_city'], 0, 119), - 'postal_code' => substr($this->billing_info['bill_zip'], 0, 59), - ], ], ]; - if (isset($this->billing_info['bill_address_2']) && $this->billing_info['bill_address_2']) { - $params['payer']['address']['address_line_2'] = substr($this->billing_info['bill_address_2'], 0, 299); - } - if (isset($this->billing_info['bill_phone']) && $this->billing_info['bill_phone']) { - $params['payer']['phone_number'] = $this->billing_info['bill_phone']; - } - return $params; } @@ -192,7 +177,7 @@ protected function getLineItems(): array if ($line_item instanceof EE_Line_Item && $line_item->OBJ_type() !== 'Promotion') { $item_money = $line_item->unit_price(); $li_description = $line_item->desc() ?? esc_html__('Event Ticket', 'event_espresso'); - $line_items [] = [ + $line_items [] = [ 'name' => substr(wp_strip_all_tags($line_item->name()), 0, 126), 'quantity' => $line_item->quantity(), 'description' => substr(wp_strip_all_tags($li_description), 0, 125), @@ -223,7 +208,7 @@ protected function getLineItems(): array * @return void * @throws EE_Error|ReflectionException */ - protected function countTaxTotal() + protected function countTaxTotal(): void { // List taxes. $this->tax_total = 0; @@ -278,7 +263,7 @@ public function validateOrder($response, $parameters): array [$this->request_url, $parameters, $response], $this->transaction->payment_method() ); - } catch (EE_Error|ReflectionException $e) { + } catch (EE_Error | ReflectionException $e) { // Just continue. } return [ diff --git a/PaymentMethods/PayPalCommerce/api/orders/OrdersApi.php b/PaymentMethods/PayPalCommerce/api/orders/OrdersApi.php index 06712f1548c..284cbc2d812 100644 --- a/PaymentMethods/PayPalCommerce/api/orders/OrdersApi.php +++ b/PaymentMethods/PayPalCommerce/api/orders/OrdersApi.php @@ -18,12 +18,12 @@ abstract class OrdersApi /** * @var PayPalApi */ - protected $api; + protected PayPalApi $api; /** * @var string */ - protected $request_url; + protected string $request_url; /** diff --git a/PaymentMethods/PayPalCommerce/api/partners/TrackSellerOnboarding.php b/PaymentMethods/PayPalCommerce/api/partners/TrackSellerOnboarding.php index 250b3fd09c3..60a633ae949 100644 --- a/PaymentMethods/PayPalCommerce/api/partners/TrackSellerOnboarding.php +++ b/PaymentMethods/PayPalCommerce/api/partners/TrackSellerOnboarding.php @@ -2,8 +2,8 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\api\partners; -use EE_Error; use EventEspresso\PaymentMethods\PayPalCommerce\api\PayPalApi; +use EventEspresso\PaymentMethods\PayPalCommerce\domain\Domain; use EventEspresso\PaymentMethods\PayPalCommerce\tools\logging\PayPalLogger; /** @@ -17,26 +17,19 @@ */ class TrackSellerOnboarding extends PartnersApi { - /** - * Partner access token. - * - * @var int - */ - protected $access_token; - /** * Partner ID. * - * @var int + * @var string */ - protected $partner_id; + protected string $partner_id; /** * Seller ID. * - * @var int + * @var string */ - protected $seller_id; + protected string $seller_id; /** @@ -64,7 +57,6 @@ public function __construct( * Get the onboarding status and validate it. * * @return array - * @throws EE_Error */ public function isValid(): array { @@ -79,25 +71,27 @@ public function isValid(): array * * @param array $response * @return array - * @throws EE_Error */ public function validateStatus(array $response): array { if (! empty($response['error'])) { return $response; } + if (! empty($response['name']) && ! empty($response['message'])) { + return ['error' => $response['name'], 'message' => $response['message']]; + } // Check the data we received. if ( - empty($response['merchant_id']) - || ! isset($response['payments_receivable']) - || ! isset($response['primary_email_confirmed']) + empty($response[ Domain::API_PARAM_TRACK_MERCHANT_ID ]) + || ! isset($response[ Domain::API_PARAM_PAYMENTS_RECEIVABLE ]) + || ! isset($response[ Domain::API_PARAM_PRIM_EMAIL_CONFIRMED ]) ) { $err_msg = esc_html__('Missing required data for validating the onboarding status.', 'event_espresso'); PayPalLogger::errorLog($err_msg, $response); return ['error' => 'ONBOARDING_MISSING_REQUIRED_DATA', 'message' => $err_msg]; } // Now validate the onboarding status. - if (! $response['payments_receivable']) { + if (! $response[ Domain::API_PARAM_PAYMENTS_RECEIVABLE ]) { $err_msg = esc_html__( 'Your Account has been limited by PayPal. Please check your PayPal account inbox for an email from PayPal to determine the next steps for this.', 'event_espresso' @@ -105,11 +99,14 @@ public function validateStatus(array $response): array PayPalLogger::errorLog($err_msg, $response); return ['error' => 'ONBOARDING_LIMITED_BY_PAYPAL', 'message' => $err_msg]; } - if (! $response['primary_email_confirmed']) { + if (! $response[ Domain::API_PARAM_PRIM_EMAIL_CONFIRMED ]) { $err_msg = esc_html__('Email address not confirmed. Please confirm your email address.', 'event_espresso'); PayPalLogger::errorLog($err_msg, $response); return ['error' => 'ONBOARDING_CONFIRM_EMAIL', 'message' => $err_msg]; } - return ['valid' => true]; + return [ + 'valid' => true, + 'response' => $response, + ]; } } diff --git a/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-checkout.css b/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-checkout.css new file mode 100644 index 00000000000..acca4cd13cb --- /dev/null +++ b/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-checkout.css @@ -0,0 +1,174 @@ +.paypal-button-container { + border-radius: 5px; + background-color: #FFFFFF; + padding: 20px; + max-width: 760px; + width: 100%; + margin: 0 auto; +} +.card_container { + border-radius: 5px; + background-color: #FFFFFF; + padding: 20px; + max-width: 760px; + width: 100%; + margin: 0 auto; +} +.card_field{ + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + height:40px; + background:white; + font-size:17px; + color:#3a3a3a; + font-family:helvetica, tahoma, calibri, sans-serif; +} +.card_field_50{ + width: 50%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + height:40px; + background:white; + font-size:17px; + color:#3a3a3a; + font-family:helvetica, tahoma, calibri, sans-serif; +} +.card_field_75{ + width: 75%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + height:40px; + background:white; + font-size:17px; + color:#3a3a3a; + font-family:helvetica, tahoma, calibri, sans-serif; +} +.row { + display: -ms-flexbox; /* IE10 */ + display: flex; + -ms-flex-wrap: wrap; /* IE10 */ + flex-wrap: wrap; + margin: 0 -16px; +} +.col-25 { + -ms-flex: 25%; /* IE10 */ + flex: 25%; +} +.col-50 { + -ms-flex: 50%; /* IE10 */ + flex: 50%; +} +input[type=text], select, textarea { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + height:40px; + background:white; + font-size:17px; + color:#3a3a3a; + font-family:helvetica, tahoma, calibri, sans-serif; +} +input[type=submit] { + background-color: #4CAF50; + color: white; + padding: 12px 20px; + border: none; + border-radius: 4px; + cursor: pointer; +} +.message_container { + border-radius: 5px; + background: #FFFFFF; + font-size: 13px; + font-family: monospace; + padding: 20px; +} +#loading { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: fixed; + display: block; + opacity: 0.7; + background-color: #fff; + z-index: 99; + text-align: center; +} +#loading-image { + position: absolute; + z-index: 15; + top: 50%; + left: 50%; + margin: -100px 0 0 -150px; +} +.spinner { + position: fixed; + top: 50%; + left: 50%; + margin-left: -50px; /* half width of the spinner gif */ + margin-top: -50px; /* half height of the spinner gif */ + text-align:center; + z-index: 1234; + overflow: auto; + width: 100px; /* width of the spinner gif */ + height: 102px; /* height of the spinner gif +2px to fix IE8 issue */ +} +.eep-ppc-btn-submit-dv { + text-align: right; + margin: 30px 20px; +} +.test-credit-cards-info-pg { + padding-bottom: 30px; +} +.eep-ppc-payment-buttons { + padding-top: 30px; +} +.eep-ppc-separator-holder { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 30px; +} +.eep-ppc-separator-line { + background: #c7c7c7; + height: 1px; +} +.eep-ppc-separator-text { + text-align: center; + text-align: -webkit-center; +} +.eep-left-floating { + float: left; + width: 45%; +} +.eep-mid-floating { + float: left; + margin: 0 auto; +} +.eep-right-floating { + float: right; + width: 45%; +} diff --git a/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-onboard.css b/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-onboard.css index 7421a7a717d..377ce71755a 100644 --- a/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-onboard.css +++ b/PaymentMethods/PayPalCommerce/assets/css/eea-paypal-onboard.css @@ -29,15 +29,15 @@ background-size: 22px 22px; } - +.eea-onboard-section .eea-paypal-onboard-trigger-btn { + display: none !important; +} /** PayPal Commerce images **/ .eea-paypal-onboard-btn span:before { background-image: url('https://www.paypalobjects.com/paypal-ui/logos/svg/paypal-mark-color.svg'); } - - /** Connection information text **/ .eea-paypal-offboard-ico { height: 25px; @@ -56,9 +56,61 @@ font-weight: bold; } - /** Disconnect dialog **/ .ee-admin-dialog-container .eea-paypal-dialog-cancel, .ee-admin-dialog-container .eea-paypal-dialog-ok { margin: 20px 10px 0 10px; float: right; } + +/** Processing Mask **/ +.eep-ppc-processing-mask { + z-index: 9999990; + position: fixed; + margin: auto; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + background-color: black; + opacity: 0.82; +} + +.eep-ppc-processing-message { + z-index: 9999991; + position: fixed; + align-self: center; + width: 20em; + height: 6em; + margin: auto; + top: 0; + left: 0; + bottom: 0; + right: 0; + color: #ADB3B6FF; + text-align: center; + background: rgb(17 17 17 / 36%); + padding: 20px; + border-radius: 10px; +} + +.eep-ppc-pm-types { + margin: 10px 0 10px; +} + +.eep-ppc-pm-types .ee-checkbox-label-after, +.eea-ppc-express-checkout-img .ee-checkbox-label-after { + margin-block-start: 0 !important; +} + +.eea-ppc-ppcp-img { + width: 140px; +} +.eea-ppc-ppcp-img, .eea-ppc-express-checkout-img { + padding: 10px; +} + +.eep-ppc-dialog-yes-no { + text-align: right; +} diff --git a/PaymentMethods/PayPalCommerce/assets/js/eea-paypal-onboarding.js b/PaymentMethods/PayPalCommerce/assets/js/eea-paypal-onboarding.js index 8005d90abb5..0c27d13e795 100644 --- a/PaymentMethods/PayPalCommerce/assets/js/eea-paypal-onboarding.js +++ b/PaymentMethods/PayPalCommerce/assets/js/eea-paypal-onboarding.js @@ -8,15 +8,23 @@ jQuery(document).ready(function ($) { * signup_link: string, * localized: array, * onboard_btn: object, + * onboard_trigger_btn: object, * offboard_btn: object, * form: object, + * onboard_window: object, * onboard_section: object, * offboard_section: object, * sandbox_select: object, + * onboard_sandbox_section: object, * processing_icon_name: string, * processing_icon: object, * clear_metadata_btn: object, + * pp_pm_slug_holder: object, * pp_seller_section: object, + * connect_ok_id: string, + * connect_cancel_id: string, + * disconnect_ok_id: string, + * disconnect_cancel_id: string, * }} * * @namespace eeaPPOnboardParameters @@ -38,14 +46,19 @@ jQuery(document).ready(function ($) { * debug_is_on_notice: string, * debug_is_off_notice: boolean, * refresh_alert: string, + * connect_dialog: string, * disconnect_dialog: string, + * processing_mask: string, + * supported_countries: array, * }} */ function EeaPayPalOnboarding(pm_slug) { this.slug = pm_slug; this.signup_link = ''; + this.onboard_window = null; this.processing_icon_name = 'espresso-ajax-loading'; this.onboard_btn = $('#eea_paypal_onboard_btn_' + pm_slug); + this.onboard_trigger_btn = $('#eea_paypal_onboard_trigger_btn_' + pm_slug); this.offboard_btn = $('#eea_paypal_offboard_btn_' + pm_slug); this.onboard_section = $('#eea_paypal_onboard_section_' + pm_slug); this.offboard_section = $('#eea_paypal_offboard_section_' + pm_slug); @@ -55,6 +68,15 @@ jQuery(document).ready(function ($) { this.pp_pm_slug_holder = $('#eea_paypal_pm_slug'); this.clear_metadata_btn = $('#eea_clear_metadata_' + pm_slug); this.pp_seller_section = $('#eea_paypal_seller_id_' + pm_slug); + this.connect_ok_id = '#eea_ppc_connect_ok_' + pm_slug; + this.connect_cancel_id = '#eea_ppc_connect_cancel_' + pm_slug; + this.disconnect_ok_id = '#eep_ppc_disconnect_ok_' + pm_slug; + this.disconnect_cancel_id = '#eep_ppc_disconnect_cancel_' + pm_slug; + this.pp_country_select_id = '#eep_ppc_country_' + pm_slug; + this.checkout_type_select_id = '#eep_ppc_checkout_type_' + pm_slug; + this.express_checkout_type_id = this.checkout_type_select_id + '-express_checkout'; + this.ppcp_checkout_type_id = this.checkout_type_select_id + '-ppcp'; + this.ppcp_checkout_type_lbl = this.ppcp_checkout_type_id + '-lbl'; /** @@ -103,15 +125,10 @@ jQuery(document).ready(function ($) { const this_pm = this; // Update button text on page load, depending on the PM sandbox mode. this_pm.toggleBtnText(this_pm, this_pm.sandbox_select.val()); - // Listen for the sandbox mode change. - this.sandbox_select.on('change', function () { + this_pm.sandbox_select.on('change', function () { const sandbox_mode = this_pm.sandbox_select.val(); - // Disable the Connect button for the time the URL is being updated. Save current URL. - this_pm.signup_link = this_pm.onboard_btn.attr('href'); - this_pm.onboard_btn.removeAttr('href'); // Update the onboarding URL if the debug mode was changed. - this_pm.sendRequest('eeaPpGetOnboardingUrl', {}, this_pm.updateOnboardingUrl, false); this_pm.toggleBtnText(this_pm, sandbox_mode); }); }; @@ -127,48 +144,102 @@ jQuery(document).ready(function ($) { this.onboard_btn.on('click', function (event) { this_pm.pp_pm_slug_holder.text(this_pm.slug); this_pm.sandbox_mode = this_pm.sandbox_select.val(); + this_pm.preOnboardingForm(); }); }; /** - * Hide the EE dialog. - * @return {void} + * Shows the pre onboarding/options form. + * @function */ - this.hideDialog = function () { - overlay.fadeOut('fast'); - eedialog.fadeOut('fast'); - }; + this.preOnboardingForm = function () { + position_overlay(true); + position_dialog(2, false); + dialogHelper.addContent(eeaPPOnboardParameters.connect_dialog); + // Pre onboarding form setup. + this.preOnboardingFormSetup(); + // Onboarding form listeners. + this.onboardFormListeners(); + } /** - * Show a dialog with the disconnect warning. - * @return {boolean} + * Set up the pre-onboarding form. + * @function */ - this.confirmDisconnect = function () { - const this_pm = this; - position_overlay(false); - position_dialog(4, false); - dialogHelper.addContent(eeaPPOnboardParameters.disconnect_dialog); - $('#eea_paypal_dialog_ok_' + this_pm.slug).on('click', function() { - this_pm.hideDialog(); - const btn_container = $(this).closest('tr'); - if (btn_container) { - this_pm.sandbox_mode = this_pm.sandbox_select.val(); - this_pm.sendRequest('eeaPpOffboard', {}, this_pm.reloadPage, false); - } else { - console.error(eeaPPOnboardParameters.unknown_container); - } - return true; - }); - $('#eea_paypal_dialog_cancel_' + this_pm.slug).on('click', function() { + this.preOnboardingFormSetup = function () { + $(this.express_checkout_type_id).attr('disabled', true); + } + + + /** + * Onboard/options form listeners. + * @function + */ + this.onboardFormListeners = function () { + let this_pm = this; + // Cancel. + $(this.connect_cancel_id).on('click', function() { this_pm.hideDialog(); return false; }); - overlay.on('click', function() { - this_pm.hideDialog(); - return false; + // Continue the onboarding. + $(this.connect_ok_id).on('click', function() { + this_pm.startOnboarding(); }); + // Change country select. + $(this.pp_country_select_id).on('change', function() { + this_pm.validateCountry(); + }); + } + + + /** + * Start the onboarding process. + * @return {void} + */ + this.startOnboarding = function () { + let selected_country = $(this.pp_country_select_id).val(); + let checkout_type = 'EXPRESS_CHECKOUT'; + if ($(this.ppcp_checkout_type_id).is(':checked')) { + checkout_type = 'PPCP'; + } + // Request the onboarding URL and then initiate PayPal onboarding flow. + const url_parameters = new URLSearchParams(window.location.search); + const request_params = { + wp_nonce: url_parameters.get('_wpnonce'), + country: selected_country, + checkout_type: checkout_type + }; + this.sendRequest('eeaPpGetOnboardingUrl', request_params, this.initiatePayPalOnboarding, false); + }; + + + /** + * Validate the selected country. + * @return {void} + */ + this.validateCountry = function () { + let selected_country = $(this.pp_country_select_id).val(); + if ($.inArray(selected_country, eeaPPOnboardParameters.supported_countries) === -1) { + // Do not allow PPCP pm to be selected if there is no support for selected country. + $(this.ppcp_checkout_type_id).prop('checked', false); + $(this.ppcp_checkout_type_id).attr('disabled', true); + $(this.ppcp_checkout_type_lbl).fadeOut('fast'); + } else { + $(this.ppcp_checkout_type_lbl).show(); + $(this.ppcp_checkout_type_id).removeAttr('disabled'); + } + } + + + /** + * Hide the EE dialog. + * @return {void} + */ + this.hideDialog = function () { + dialogHelper.closeModal(); }; @@ -199,33 +270,32 @@ jQuery(document).ready(function ($) { /** - * Simply reloads the page. + * Trigger a click to initiate PayPal. * @param this_pm * @param response * @function */ - this.reloadPage = function (this_pm, response) { - window.do_before_admin_page_ajax(); - // Reload the page when disconnected. - // Seems to be required for PP scripts. If not refreshed the signup link is opened in a new window or tab. - location.reload(); - }; + this.initiatePayPalOnboarding = function (this_pm, response) { + if (typeof response.signup_link !== 'undefined') { + this_pm.onboard_trigger_btn.attr('href', response.signup_link); + this_pm.hideDialog(); + this_pm.processing_icon.fadeOut('fast'); + this_pm.onboard_trigger_btn[0].click(); + } + } /** - * Change the onboarding URL, sandbox vs live. + * Simply reloads the page. * @param this_pm * @param response * @function */ - this.updateOnboardingUrl = function (this_pm, response) { + this.reloadPage = function (this_pm, response) { window.do_before_admin_page_ajax(); - let signup_link = this_pm.signup_link; - if (typeof response.signup_link !== 'undefined') { - signup_link = response.signup_link; - } - this_pm.onboard_btn.attr('href', signup_link); - this_pm.processing_icon.fadeOut('fast'); + // Reload the page when disconnected. + // Seems to be required for PP scripts. If not refreshed the signup link is opened in a new window or tab. + location.reload(); }; @@ -282,7 +352,7 @@ jQuery(document).ready(function ($) { }, success: function (response) { ppc_onboarding_processing = false; - const is_valid = this_pm.checkForErrors(response); + const is_valid = this_pm.checkForErrors(response, request_action === 'eeaPpGetOnboardingUrl'); if (is_valid && typeof callback !== 'undefined' && callback) { // Run the callback if there are no errors. callback(this_pm, response); @@ -293,7 +363,7 @@ jQuery(document).ready(function ($) { }, error: function (jqXHR, details, error) { ppc_onboarding_processing = false; - this_pm.requestError(jqXHR, error, details, this_pm); + this_pm.requestError(jqXHR, error, details, this_pm, request_action === 'eeaPpGetOnboardingUrl'); }, }); }; @@ -304,21 +374,48 @@ jQuery(document).ready(function ($) { * @function */ this.toggleBtnText = function (this_pm, sandbox_mode) { - const btn_text_span = this_pm.onboard_btn.find('span')[0]; - // Change button text. - if (btn_text_span) { - if (sandbox_mode === '1') { - $(btn_text_span).text(eeaPPOnboardParameters.sandbox_btn_text); - } else { - $(btn_text_span).text(eeaPPOnboardParameters.onboard_btn_text); - } + if (sandbox_mode === '1') { + this_pm.onboard_btn.text(eeaPPOnboardParameters.sandbox_btn_text); + } else { + this_pm.onboard_btn.text(eeaPPOnboardParameters.onboard_btn_text); } // Also update sandbox text. this.updateConnectionInfo(this_pm); }; + /** + * Show a dialog with the disconnect warning. + * @return {boolean} + */ + this.confirmDisconnect = function () { + const this_pm = this; + position_overlay(false); + position_dialog(3, false); + dialogHelper.addContent(eeaPPOnboardParameters.disconnect_dialog); + $(this_pm.disconnect_ok_id).on('click', function() { + this_pm.hideDialog(); + const btn_container = $(this).closest('tr'); + if (btn_container) { + this_pm.sandbox_mode = this_pm.sandbox_select.val(); + this_pm.sendRequest('eeaPpOffboard', {}, this_pm.reloadPage, false); + } else { + console.error(eeaPPOnboardParameters.unknown_container); + } + return true; + }); + $(this.disconnect_cancel_id).on('click', function() { + this_pm.hideDialog(); + return false; + }); + overlay.on('click', function() { + this_pm.hideDialog(); + return false; + }); + }; + + /** * Show a dialog with message. * @param error @@ -327,7 +424,7 @@ jQuery(document).ready(function ($) { this.showAlert = function (error) { const this_pm = this; position_overlay(false); - position_dialog(4, false); + position_dialog(3, false); dialogHelper.addContent('Error: ' + error); overlay.on('click', function() { this_pm.hideDialog(); @@ -338,16 +435,21 @@ jQuery(document).ready(function ($) { /** * Check for errors in the response. * @param response + * @param close_window * @return {boolean} */ - this.checkForErrors = function (response) { + this.checkForErrors = function (response, close_window) { if (response === null || response.error) { + if (close_window) { + this.onboard_window.close(); + } let error = eeaPPOnboardParameters.request_error; if (response !== null && response.message) { error = response.message; } console.error(error); this.showAlert(error); + this.processing_icon.fadeOut('fast'); return false; } return true; @@ -431,7 +533,7 @@ jQuery(document).ready(function ($) { * Display or log the error. * @function */ - this.requestError = function (response, err, details, this_pm) { + this.requestError = function (response, err, details, this_pm, close_window) { this_pm.processing_icon.fadeOut('fast'); let error = eeaPPOnboardParameters.error_response; if (details) { @@ -439,6 +541,9 @@ jQuery(document).ready(function ($) { } console.error(error); this_pm.showAlert(error); + if (close_window) { + this_pm.onboard_window.close(); + } }; } @@ -448,22 +553,3 @@ jQuery(document).ready(function ($) { paypal_pms[slug].initialize(); } }); - -// Default callback for PayPal to trigger when onboarding was a success. -function onboardedCallback(authCode, sharedId) { - // Prevent double callback triggers (which did happen frequently). - if (ppc_onboarding_processing) { - return; - } - ppc_onboarding_processing = true; - const pm_slug = document.getElementById('eea_paypal_pm_slug').textContent; - const request_data = { - authCode: authCode, - sharedId: sharedId - }; - paypal_pms[pm_slug].sendRequest('eeaPpGetSellerAccessToken', request_data, false, true); - // Close the window. - if (typeof PAYPAL !== 'undefined') { - PAYPAL.apps.Signup.MiniBrowser.closeFlow(); - } -} diff --git a/PaymentMethods/PayPalCommerce/assets/js/paypal-commerce-payments.js b/PaymentMethods/PayPalCommerce/assets/js/paypal-commerce-payments.js index 1587208aa8c..a91a0606cd8 100644 --- a/PaymentMethods/PayPalCommerce/assets/js/paypal-commerce-payments.js +++ b/PaymentMethods/PayPalCommerce/assets/js/paypal-commerce-payments.js @@ -18,12 +18,14 @@ jQuery(document).ready(function ($) { * @namespace paypal * @type {{ * paypal: object, + * hostedFields: Object, * }} + * * @namespace eeaPPCommerceParameters * @type {{ * pm_versions: array, - * client_id: string, * payment_currency: string, + * checkout_type: string, * currency_sign: string, * pp_order_id: string, * pp_order_nonce: string, @@ -31,6 +33,7 @@ jQuery(document).ready(function ($) { * org_country: string, * decimal_places: int, * site_name: string, + * active_states: array, * no_spco_error: string, * no_pm_error: string, * browser_not_supported: string, @@ -41,6 +44,9 @@ jQuery(document).ready(function ($) { * payment_error: string, * no_order_id: string, * general_pp_error: string, + * hf_render_error: string, + * pm_capture_error: string, + * not_acdc_eligible: string, * }} */ function EeaPayPalCheckout(pm_slug) { @@ -49,7 +55,6 @@ jQuery(document).ready(function ($) { this.transaction = {}; this.spco = window.SPCO || null; - /** * Initial setup. * @function @@ -59,13 +64,13 @@ jQuery(document).ready(function ($) { // Ensure that the SPCO has loaded. if (typeof this.spco == null) { this.hidePm(); - this.throwError(eeaPPCommerceParameters.no_spco_error, '', false); + this.throwError(eeaPPCommerceParameters.no_spco_error, '', this.slug, false); return false; } // Ensure that PayPal scripts loaded. if (typeof paypal === 'undefined') { this.hidePm(); - this.throwError(eeaPPCommerceParameters.no_pm_error, ''); + this.throwError(eeaPPCommerceParameters.no_pm_error, '', this.slug); return false; } // Prevent loading on registration page. @@ -74,7 +79,13 @@ jQuery(document).ready(function ($) { } // PayPal components will require re-setup even if PM already initialized. if (this.initialized) { - this.setupPayPal(); + // PayPal buttons or Hosted Fields ? + if (eeaPPCommerceParameters.checkout_type !== 'express_checkout') { + this.setupHostedFields(); + } + if (eeaPPCommerceParameters.checkout_type !== 'ppcp') { + this.setupPayPalButtons(); + } this.disableSubmitButtons(); return true; } @@ -96,42 +107,35 @@ jQuery(document).ready(function ($) { if (! this.pp_order_id || this.pp_order_id.length < 1) { this.pp_order_id = eeaPPCommerceParameters.pp_order_id; } - // this.button_container_id = 'eea-' + pm_slug + '-payment-buttons'; - this.button_container_id = 'eea-paypal-commerce-payment-buttons'; - this.payment_button_container = $('#' + this.button_container_id); + this.button_container_id = '#eep-' + pm_slug + '-payment-buttons'; this.payment_method_selector = $('#ee-available-payment-method-inputs'); this.payment_method_select_lbl = $('#ee-available-payment-method-inputs-' + pm_slug + '-lbl'); this.payment_method_info_div = $('#spco-payment-method-info-' + pm_slug); this.billing_form = $('#pp-' + pm_slug + '-billing-form'); - this.payment_form = this.payment_button_container.parents('form:first'); // PP order details this.order_nonce_input = $('#eea-' + pm_slug + '-order-nonce'); this.order_id_input = $('#eea-' + pm_slug + '-order-id'); this.order_status_input = $('#eea-' + pm_slug + '-order-status'); this.order_amount_input = $('#eea-' + pm_slug + '-order-amount'); - // Billing data. - if (typeof this.billing_form !== 'undefined') { - this.bill_first_name = this.billing_form.find( - 'input[id*="billing-form-first-name"]:visible'); - this.bill_last_name = this.billing_form.find( - 'input[id*="billing-form-last-name"]:visible'); - this.bill_email = this.billing_form.find( - 'input[id*="billing-form-email"]:visible'); - this.bill_address = this.billing_form.find( - 'input[id*="billing-form-address"]:visible'); - this.bill_address_2 = this.billing_form.find( - 'input[id*="billing-form-address2"]:visible'); - this.bill_city = this.billing_form.find( - 'input[id*="billing-form-city"]:visible'); - this.bill_state = this.billing_form.find( - 'select[id*="billing-form-state"]:visible'); - this.bill_country = this.billing_form.find( - 'select[id*="billing-form-country"]:visible'); - this.bill_zip = this.billing_form.find( - 'input[id*="billing-form-zip"]:visible'); - this.bill_phone = this.billing_form.find( - 'input[id*="billing-form-phone"]:visible'); - } + // PP credit card fields + this.card_input_id = '#' + this.slug + '-card-number'; + this.cvv_input_id = '#' + this.slug + '-cvv'; + this.expiration_date_input_id = '#' + this.slug + '-expiration-date'; + this.card_holder_name_input_id = '#' + this.slug + '-card-holder-name'; + this.type_separator_id = '#eep-ppc-separator-holder'; + this.card_fields_class = '.' + this.slug + '-card-fields'; + this.payment_form = this.order_nonce_input.parents('form:first'); + this.new_state_form_id = '#new_state_micro_form'; + this.acdc_submit_btn_dv = '#' + this.slug + '-submit-dv'; + // Billing inputs. + this.bill_address = $('#pp-' + this.slug + '-billing-form-address'); + this.bill_address_2 = $('#pp-' + this.slug + '-billing-form-address2'); + this.bill_city = $('#pp-' + this.slug + '-billing-form-city'); + this.bill_state = $('#pp-' + this.slug + '-billing-form-state'); + this.bill_country = $('#pp-' + this.slug + '-billing-form-country'); + this.bill_zip = $('#pp-' + this.slug + '-billing-form-zip'); + this.bill_phone = $('#pp-' + this.slug + '-billing-form-phone'); + this.add_new_state = $('#pp-' + this.slug + '-billing-form-nsmf_add_new_state'); } @@ -170,8 +174,13 @@ jQuery(document).ready(function ($) { } // Save transaction data. this_pm.transaction = response; - // Now we can set up PayPal buttons. - this_pm.setupPayPal(); + // Now we can set up PayPal. PayPal buttons or Hosted Fields ? Or both ? + if (eeaPPCommerceParameters.checkout_type !== 'express_checkout') { + this_pm.setupHostedFields(); + } + if (eeaPPCommerceParameters.checkout_type !== 'ppcp') { + this_pm.setupPayPalButtons(); + } this_pm.spco.end_ajax(); return true; }, @@ -188,7 +197,7 @@ jQuery(document).ready(function ($) { * Sets up PayPal payment button. * @function */ - this.setupPayPal = function () { + this.setupPayPalButtons = function () { const this_pm = this; this_pm.spco.do_before_sending_ajax(); paypal.Buttons({ @@ -196,95 +205,189 @@ jQuery(document).ready(function ($) { layout: 'vertical', color: 'blue', shape: 'rect', - label: 'paypal' - }, - onInit: function (data, actions) { - // actions.disable(); - }, - onClick: function () { - console.log('-- PP onClick:'); - const form_valid = this_pm.payment_form.valid(); - if (!form_valid) { - this_pm.throwError(eeaPPCommerceParameters.form_validation_notice, ''); - return false; - } + label: 'pay' }, // Sets up the transaction when a payment button is clicked. createOrder: function (data, actions) { - this_pm.spco.do_before_sending_ajax(); - console.log('-- PP createOrder:', data, actions); return this_pm.createOrder(); }, // Finalize the transaction after payer approval onApprove: function (data, actions) { - this_pm.spco.do_before_sending_ajax(); - console.log('-- PP onApprove:', data, actions); - return this_pm.captureOrder(data, actions); + this_pm.hideACDCForm(); + return this_pm.captureOrder(data); }, onError: function (error) { - console.log('-- PP onError:', error); - console.error(eeaPPCommerceParameters.general_pp_error); + console.error(eeaPPCommerceParameters.general_pp_error, error); if (String(error).includes('PAYMENT_ALREADY_DONE')) { // Hide any return to cart buttons, etc. $('.hide-me-after-successful-payment-js').hide(); // Save order ID. - const order_data = { - pp_order_id: this_pm.getOrderId(), - pp_order_nonce: this_pm.getOrderNonce(), - }; - this_pm.saveOrderData(order_data); + this_pm.saveOrderInDom(); + this_pm.hideACDCForm(); // Trigger click event on SPCO "Proceed to Next Step" button. this_pm.spco.enable_submit_buttons(); - this_pm.payment_button_container.parents('form:first').find('.spco-next-step-btn').trigger('click'); + this_pm.order_nonce_input.parents('form:first').find('.spco-next-step-btn').trigger('click'); } else { - let message = eeaPPCommerceParameters.payment_error + '; ' + error; - this_pm.throwError(message, this_pm.slug); + let message = eeaPPCommerceParameters.payment_error; + this_pm.throwError(message, error, this_pm.slug); } } - }).render('#' + this.button_container_id); + }).render(this.button_container_id); this_pm.spco.end_ajax(); }; + /** + * Sets up PayPal Hosted Fields. + * @function + */ + this.setupHostedFields = function () { + const this_pm = this; + this_pm.spco.do_before_sending_ajax(); + if (paypal.HostedFields.isEligible()) { + // Renders card fields + paypal.HostedFields.render({ + // Call your server to set up the transaction + createOrder: function () { + return this_pm.createOrder(); + }, + styles: { + '.valid': { + 'color': 'green' + }, + '.invalid': { + 'color': 'red' + } + }, + fields: { + number: { + selector: this_pm.card_input_id, + placeholder: "4111 1111 1111 1111" + }, + cvv: { + selector: this_pm.cvv_input_id, + placeholder: "123" + }, + expirationDate: { + selector: this_pm.expiration_date_input_id, + placeholder: "MM/YY" + } + } + }).then(function (cardFields) { + this_pm.submitCardFieldsListener(cardFields); + }).catch(function (orderData) { + this_pm.throwError( + eeaPPCommerceParameters.hf_render_error + ' ' + JSON.stringify(orderData), + JSON.stringify(orderData), + this_pm.slug + ); + }); + } else { + // Hides card fields if the merchant isn't eligible + this_pm.hideACDCForm(); + this_pm.displayError( + eeaPPCommerceParameters.not_acdc_eligible, + $(this.button_container_id) + ); + // And allow Express checkout. + if (eeaPPCommerceParameters.checkout_type === 'ppcp') { + this_pm.setupPayPalButtons(); + } + } + this_pm.spco.end_ajax(); + } + + + /** + * Listener for the card fields submit action. + * @function + */ + this.submitCardFieldsListener = function (cardFields) { + const this_pm = this; + $("#" + this_pm.slug + "-submit").on('click', (event) => { + event.preventDefault(); + // Validate the form. + let state = cardFields.getState(); + let form_valid = this_pm.payment_form.valid(); + Object.keys(state.fields).forEach(function callback(value) { + let field_valid = state.fields[value].isValid; + // Highlight the problem field. + if (!field_valid) { + form_valid = false; + $(state.fields[value].container).addClass('ee-needs-value'); + } else { + $(state.fields[value].container).removeClass('ee-needs-value'); + } + }); + if (form_valid) { + let bill_state = ''; + let bill_state_id = this.bill_state.val(); + for (const key in eeaPPCommerceParameters.active_states) { + if (bill_state_id === key) { + bill_state = eeaPPCommerceParameters.active_states[key]; + } + } + + let bill_address_2 = this.bill_address_2.val(); + if (!bill_address_2) { + bill_address_2 = ''; + } + cardFields.submit({ + // Cardholder's first and last name + cardholderName: $(this_pm.card_holder_name_input_id).val(), + // Billing Address + billingAddress: { + // Street address, line 1 + streetAddress: this.bill_address.val(), + // Street address, line 2 (Ex: Unit, Apartment, etc.) + extendedAddress: bill_address_2, + // State + region: bill_state, + // City + locality: this.bill_city.val(), + // Postal Code + postalCode: this.bill_zip.val(), + // Country Code + countryCodeAlpha2: this.bill_country.val() + } + }).then(function () { + return this_pm.captureOrder([]); + }).catch(function (err) { + this_pm.throwError( + eeaPPCommerceParameters.pm_capture_error + ' ' + JSON.stringify(err), + JSON.stringify(err), + this_pm.slug + ); + }); + } else { + this_pm.displayError( + eeaPPCommerceParameters.form_validation_notice, + $(this_pm.card_input_id) + ); + } + }); + } + + /** * Send a request to the server side to create an Order through the PayPal API. * @function */ - this.createOrder = function () { - console.log('-- createOrder:'); + this.createOrder = function (billing_info) { + console.log('function createOrder()'); + this.spco.do_before_sending_ajax(); // Do we already have an order created ? if (this.pp_order_id.length > 0) { - console.log('-- createOrder return:', this.pp_order_id); this.spco.end_ajax(); return this.pp_order_id; } // Create a new order. const this_pm = this; const request_data = new FormData(); - const billing_info = { - bill_first_name: this.bill_first_name.val(), - bill_last_name: this.bill_last_name.val(), - bill_address: this.bill_address.val(), - bill_city: this.bill_city.val(), - bill_state: this.bill_state.val(), - bill_country: this.bill_country.val(), - bill_zip: this.bill_zip.val(), - bill_email: this.bill_email.val() - }; - let bill_address_2 = this.bill_address_2.val(); - if (bill_address_2) { - billing_info.bill_address_2 = bill_address_2; - } - let phone = this.bill_phone.val(); - if (phone) { - billing_info.bill_phone = phone; - } - console.log('-- billing_info:', billing_info); - request_data.append('action', 'eeaPpCreateOrder'); + request_data.append('action', 'eeaPPCCreateOrder'); request_data.append('txn_id', eeaPPCommerceParameters.txn_id); request_data.append('payment_method', this.slug); request_data.append('billing_info', JSON.stringify(billing_info)); - // Do a request to create an Order. return fetch(eei18n.ajax_url, { method: 'POST', @@ -302,14 +405,14 @@ jQuery(document).ready(function ($) { if (typeof response_data.error !== 'undefined') { message = response_data.error; } - this_pm.throwError(message, this_pm.slug, false); - console.log('-- createOrder return false:'); + this_pm.throwError(message, response_data, this_pm.slug, false); + console.log('-- createOrder return false!'); this_pm.spco.end_ajax(); return false; } }) .catch((error) => { - this_pm.throwError(error, this_pm.slug); + this_pm.throwError(error, '', this_pm.slug); }); }; @@ -318,14 +421,19 @@ jQuery(document).ready(function ($) { * Send a request to the server side to capture the Order through the PayPal API. * @function */ - this.captureOrder = function (order_data, order_actions) { - console.log('-- captureOrder:', order_data); + this.captureOrder = function (order_data) { + console.log('function captureOrder()'); const this_pm = this; + this_pm.spco.do_before_sending_ajax(); const request_data = new FormData(); - request_data.append('action', 'eeaPpCaptureOrder'); + let order_id = this.getOrderId(); + if (typeof order_data.orderID !== 'undefined' && order_data.orderID) { + order_id = order_data.orderID; + } + request_data.append('action', 'eeaPPCCaptureOrder'); request_data.append('payment_method', this.slug); request_data.append('txn_id', eeaPPCommerceParameters.txn_id); - request_data.append('order_id', order_data.orderID); + request_data.append('order_id', order_id); // Do a request to capture the Order. return fetch(eei18n.ajax_url, { @@ -336,14 +444,13 @@ jQuery(document).ready(function ($) { .then((response_data) => { console.log('-- captureOrder response:', response_data); if (typeof response_data.error !== 'undefined') { - // if (response_data.name === 'INSTRUMENT_DECLINED') { - // this_pm.clearOrder(); - // } + if (response_data.name === 'INSTRUMENT_DECLINED') { + this_pm.clearOrder(); + } let message = eeaPPCommerceParameters.payment_error; if (response_data.message) message += '\n\n' + response_data.message; - this_pm.throwError(message, this_pm.slug); + this_pm.throwError(message, response_data, this_pm.slug); // order_actions.restart(); - console.log('-- captureOrder return []:'); this_pm.spco.end_ajax(); return []; } @@ -353,7 +460,7 @@ jQuery(document).ready(function ($) { $('.hide-me-after-successful-payment-js').hide(); // Trigger click event on SPCO "Proceed to Next Step" button. this_pm.spco.enable_submit_buttons(); - this_pm.payment_button_container.parents('form:first').find('.spco-next-step-btn').trigger('click'); + this_pm.order_nonce_input.parents('form:first').find('.spco-next-step-btn').trigger('click'); console.log('-- captureOrder return response_data:', response_data); this_pm.spco.end_ajax(); return response_data; @@ -361,19 +468,75 @@ jQuery(document).ready(function ($) { }; + /** + * Hide the advanced credit debit card fields. + * @function + */ + this.hideACDCForm = function () { + const card_fields = [ + $(this.card_fields_class), + $(this.type_separator_id), + $(this.new_state_form_id), + $(this.acdc_submit_btn_dv), + ]; + const bill_fields = [ + $(this.card_holder_name_input_id), + this.bill_address, + this.bill_address_2, + this.bill_city, + this.bill_state, + this.bill_country, + this.bill_zip, + this.bill_phone, + this.add_new_state, + ]; + // No need to validate this form, disable inputs. + SPCO.remove_previous_validation_rules(); + // Send additional POST data with the form submit + SPCO.additional_post_data += '&eep_ppc_skip_form_validation=true'; + $.each(bill_fields, function(index, field) { + field.parent().hide(); + field.removeAttr('required'); + } + ); + // Hide card fields as well. + $.each(card_fields, function(index, field) { + field.hide(); + } + ); + } + + /** * Add order data to the payment form to pass it to the gateway. * @function */ this.saveOrderData = function (order_data) { - console.log('-- saveOrderData:', order_data); - // Save order data in case there's an error. - this.pp_order_id = order_data.pp_order_id; - this.pp_order_nonce = order_data.pp_order_nonce; + console.log('function saveOrderData()', order_data); + // Check each parameter before setting its value. + if (typeof order_data.pp_order_id !== 'undefined' && order_data.pp_order_id) { + this.pp_order_id = order_data.pp_order_id; + } + if (typeof order_data.pp_order_nonce !== 'undefined' && order_data.pp_order_nonce) { + this.pp_order_nonce = order_data.pp_order_nonce; + } + if (typeof order_data.pp_order_status !== 'undefined' && order_data.pp_order_status) { + this.order_status_input.val(order_data.pp_order_status); + } + if (typeof order_data.pp_order_amount !== 'undefined' && order_data.pp_order_amount) { + this.order_amount_input.val(order_data.pp_order_amount); + } + this.saveOrderInDom(); + }; + + + /** + * Get paypal_order_id. + * @function + */ + this.saveOrderInDom = function () { this.order_id_input.val(this.pp_order_id); this.order_nonce_input.val(this.pp_order_nonce); - this.order_status_input.val(order_data.pp_order_status); - this.order_amount_input.val(order_data.pp_order_amount); }; @@ -400,7 +563,7 @@ jQuery(document).ready(function ($) { * @function */ this.clearOrder = function () { - console.log('-- clearOrder:'); + console.log('function clearOrder()'); this.pp_order_id = ''; this.pp_order_nonce = ''; return true; @@ -410,24 +573,28 @@ jQuery(document).ready(function ($) { /** * Display and/or log the error. * @function + * @param message * @param details * @param pm_slug * @param log_error * @param display_error */ - this.throwError = function (details, pm_slug, log_error, display_error) { - let error = eeaPPCommerceParameters.error_response; - if (details) { - error = error + ': ' + details; + this.throwError = function (message, details, pm_slug, log_error, display_error) { + let error_message = eeaPPCommerceParameters.error_response; + if (message) { + error_message = error_message + ': ' + message; } - console.error(error); + console.error(error_message, details); // front-end message if (display_error !== false) { - this.displayError(details); + this.displayError(error_message); } // add to PM logs if (log_error !== false) { - this.logError(error, pm_slug); + if (!details) { + details = error_message; + } + this.logError(details, pm_slug); } }; @@ -446,13 +613,12 @@ jQuery(document).ready(function ($) { * Scroll to top and display the error message. * @function displayError */ - this.displayError = function (message) { + this.displayError = function (message, item) { let notification = this.spco.generate_message_object('', '', message); - this.spco.scroll_to_top_and_display_messages( - this.payment_method_info_div, - notification, - this - ); + if (typeof item === 'undefined' || typeof item.offset() === 'undefined') { + item = this.spco.main_container; + } + this.spco.scroll_to_top_and_display_messages(item, notification, true); }; @@ -483,7 +649,7 @@ jQuery(document).ready(function ($) { * @function disableSubmitButtons */ this.disableSubmitButtons = function () { - if (this.selected && this.payment_button_container.length > 0) { + if (this.selected && this.order_nonce_input.length > 0) { this.spco.allow_enable_submit_buttons = false; this.spco.disable_submit_buttons(); } diff --git a/PaymentMethods/PayPalCommerce/domain/Domain.php b/PaymentMethods/PayPalCommerce/domain/Domain.php index 83130b95b0b..f2755ef6e8d 100644 --- a/PaymentMethods/PayPalCommerce/domain/Domain.php +++ b/PaymentMethods/PayPalCommerce/domain/Domain.php @@ -2,6 +2,8 @@ namespace EventEspresso\PaymentMethods\PayPalCommerce\domain; +use EE_Payment_Method; + /** * Class Domain * @@ -33,20 +35,15 @@ class Domain public const META_KEY_ACCESS_TOKEN = 'access_token'; /** - * Name of the extra meta that stores the seller nonce. + * Name of the extra meta that stores the last request tracking ID. */ - public const META_KEY_SELLER_NONCE = 'seller_nonce'; + public const META_KEY_TRACKING_ID = 'tracking_id'; /** * Name of the extra meta that stores the Event Espresso PayPal Account's merchant id. */ public const META_KEY_APP_ID = 'app_id'; - /** - * Name of the extra meta that holds the partner client ID. - */ - public const META_KEY_PARTNER_CLIENT_ID = 'partner_client_id'; - /** * Name of the extra meta that holds the seller client ID. */ @@ -63,15 +60,20 @@ class Domain public const META_KEY_EXPIRES_IN = 'expires_in'; /** - * Name of the extra meta that holds the seller payer ID. + * Name of the extra meta that holds the partner client ID. */ - public const META_KEY_PAYER_ID = 'payer_id'; + public const META_KEY_PARTNER_CLIENT_ID = 'partner_client_id'; /** * Name of the extra meta that holds the partner merchant ID. */ public const META_KEY_PARTNER_MERCHANT_ID = 'partner_merchant_id'; + /** + * Name of the extra meta that holds the seller merchant ID. + */ + public const META_KEY_SELLER_MERCHANT_ID = 'merchantIdInPayPal'; + /** * Name of the extra meta that holds the onboarding URL. */ @@ -87,23 +89,90 @@ class Domain */ public const META_KEY_LAST_ORDER = 'last_order_details'; + /** + * Name of the extra meta that stores the allowed PP checkout type selected by merchant while onboarding. + */ + public const META_KEY_ALLOWED_CHECKOUT_TYPE = 'allowed_checkout_type'; + + /** + * Name of the extra meta that stores the PP checkout type selected by merchant after onboarding. + */ + public const META_KEY_CHECKOUT_TYPE = 'checkout_type'; + /** * Name of the PayPal API parameter that holds the auth code. */ public const API_KEY_AUTH_CODE = 'authCode'; /** - * Name of the PayPal API parameter that holds the shared ID. + * Name of the PayPal API parameter that holds the client token. + */ + public const API_KEY_CLIENT_TOKEN = 'client_token'; + + /** + * Name of the extra meta that holds the seller payer ID. + */ + public const META_KEY_PAYER_ID = 'payer_id'; + + /** + * Name of the PayPal API parameter that holds the client token expiration time. */ - public const API_KEY_SHARED_ID = 'sharedId'; + public const API_KEY_EXPIRES_IN = 'expires_in'; + + /** + * Name of the PayPal API parameter that holds the bool of if permissions were granted. + */ + public const API_PARAM_PERMISSIONS_GRANTED = 'permissionsGranted'; + + /** + * Name of the PayPal API parameter that holds the bool of if the primary email was confirmed. + */ + public const API_PARAM_PRIM_EMAIL_CONFIRMED = 'primary_email_confirmed'; + + /** + * Name of the PayPal API parameter that holds the bool of if email was confirmed. + */ + public const API_PARAM_EMAIL_CONFIRMED = 'isEmailConfirmed'; + + /** + * Name of the PayPal API parameter that holds the partner ID. + */ + public const API_PARAM_PARTNER_ID = 'merchantId'; + + /** + * Name of the PayPal API parameter that holds the merchant ID in the Track seller onboarding status request. + */ + public const API_PARAM_TRACK_MERCHANT_ID = 'merchant_id'; + + /** + * Name of the PayPal API parameter that holds the payments_receivable status. + */ + public const API_PARAM_PAYMENTS_RECEIVABLE = 'payments_receivable'; /** * Name of the nonce used in the capture order request. */ public const CAPTURE_ORDER_NONCE_NAME = 'eea_pp_commerce_capture_order_payment'; + /** + * Name of the nonce used in the onboarding process. + */ + public const NONCE_NAME_ONBOARDING_RETURN = 'eea_pp_commerce_onboarding_return'; + /** * Holds this payment method slug. */ public const PM_SLUG = 'paypalcheckout'; + + + /** + * Returns the base PayPal API URL. + * + * @param EE_Payment_Method $payment_method + * @return string + */ + public static function getPayPalApiUrl(EE_Payment_Method $payment_method): string + { + return $payment_method->debug_mode() ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; + } } diff --git a/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php b/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php index 94743a6f185..c8e2b66b769 100644 --- a/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php +++ b/PaymentMethods/PayPalCommerce/modules/EED_PayPalCommerce.module.php @@ -2,9 +2,12 @@ use EventEspresso\core\domain\services\database\DbStatus; use EventEspresso\core\services\request\DataType; +use EventEspresso\PaymentMethods\PayPalCommerce\api\clients\ClientToken; use EventEspresso\PaymentMethods\PayPalCommerce\api\orders\CaptureOrder; use EventEspresso\PaymentMethods\PayPalCommerce\api\orders\CreateOrder; use EventEspresso\PaymentMethods\PayPalCommerce\api\PayPalApi; +use EventEspresso\PaymentMethods\PayPalCommerce\api\FirstPartyPayPalApi; +use EventEspresso\PaymentMethods\PayPalCommerce\api\ThirdPartyPayPalApi; use EventEspresso\PaymentMethods\PayPalCommerce\domain\Domain; use EventEspresso\PaymentMethods\PayPalCommerce\tools\extra_meta\PayPalExtraMetaManager; use EventEspresso\PaymentMethods\PayPalCommerce\tools\logging\PayPalLogger; @@ -36,17 +39,6 @@ public static function instance(): EED_Module * @return void */ public function run($WP) - { - // TODO: Implement run() method. - } - - - /** - * For hooking into EE Core and other modules. - * - * @return void - */ - public static function set_hooks() { } @@ -56,15 +48,15 @@ public static function set_hooks() * * @return void */ - public static function set_hooks_admin() + public static function set_hooks_admin(): void { if (DbStatus::isOnline()) { // Create an Order. - add_action('wp_ajax_eeaPpCreateOrder', [__CLASS__, 'createOrderRequest']); - add_action('wp_ajax_nopriv_eeaPpCreateOrder', [__CLASS__, 'createOrderRequest']); + add_action('wp_ajax_eeaPPCCreateOrder', [__CLASS__, 'createOrderRequest']); + add_action('wp_ajax_nopriv_eeaPPCCreateOrder', [__CLASS__, 'createOrderRequest']); // Capture the Order. - add_action('wp_ajax_eeaPpCaptureOrder', [__CLASS__, 'captureOrderRequest']); - add_action('wp_ajax_nopriv_eeaPpCaptureOrder', [__CLASS__, 'captureOrderRequest']); + add_action('wp_ajax_eeaPPCCaptureOrder', [__CLASS__, 'captureOrderRequest']); + add_action('wp_ajax_nopriv_eeaPPCCaptureOrder', [__CLASS__, 'captureOrderRequest']); // Log errors from the JS side. add_action('wp_ajax_eeaPPCommerceLogError', [__CLASS__, 'logJsError']); add_action('wp_ajax_nopriv_eeaPPCommerceLogError', [__CLASS__, 'logJsError']); @@ -87,9 +79,9 @@ public static function set_hooks_admin() * @throws EE_Error * @throws ReflectionException */ - public static function createOrderRequest() + public static function createOrderRequest(): void { - $paypal_pm = self::getPaymentMethod(); + $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); $request = EED_Module::getRequest(); $post_params = $request->postParams(); if (! $paypal_pm instanceof EE_Payment_Method) { @@ -98,24 +90,21 @@ public static function createOrderRequest() $post_params ); } - $transaction = self::getTransaction(); + $transaction = EED_PayPalCommerce::getTransaction(); $billing_info = $post_params['billing_info'] ?? []; if ($billing_info) { $billing_info_decoded = json_decode(stripslashes($billing_info), true); - if (is_array($billing_info_decoded)) { - $billing_info = $billing_info_decoded; - } + $billing_info = is_array($billing_info_decoded) ? $billing_info_decoded : []; } - if (! $transaction || ! $billing_info || ! is_array($billing_info)) { + if (! $transaction) { PayPalLogger::errorLogAndExit( - esc_html__('Transaction or billing info not found.', 'event_espresso'), + esc_html__('Transaction info not found.', 'event_espresso'), $post_params, $paypal_pm ); } - $order_data = self::createOrder($transaction, $billing_info, $paypal_pm); - echo json_encode($order_data); - exit(); + $order_data = EED_PayPalCommerce::createOrder($transaction, $billing_info, $paypal_pm); + wp_send_json($order_data); } @@ -124,17 +113,15 @@ public static function createOrderRequest() * (AJAX) * * @return void - * @throws EE_Error - * @throws ReflectionException */ - public static function captureOrderRequest() + public static function captureOrderRequest(): void { $error_message = false; - $paypal_pm = self::getPaymentMethod(); + $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); if (! $paypal_pm instanceof EE_Payment_Method) { $error_message = esc_html__('Payment method not found (capture Order).', 'event_espresso'); } - $transaction = self::getTransaction(); + $transaction = EED_PayPalCommerce::getTransaction(); if (! $transaction) { $error_message = esc_html__('Transaction not found.', 'event_espresso'); } @@ -146,9 +133,8 @@ public static function captureOrderRequest() if ($error_message) { PayPalLogger::errorLogAndExit($error_message, EED_Module::getRequest()->postParams(), $paypal_pm); } - $capture_response = self::captureOrder($transaction, $paypal_pm, $order_id); - echo json_encode($capture_response); - exit(); + $capture_response = EED_PayPalCommerce::captureOrder($transaction, $paypal_pm, $order_id); + wp_send_json($capture_response); } @@ -161,14 +147,13 @@ public static function captureOrderRequest() * @return array * @throws EE_Error * @throws ReflectionException - * @throws Exception */ public static function createOrder( EE_Transaction $transaction, array $billing_info, EE_Payment_Method $paypal_pm ): array { - $create_order_api = self::getCreateOrderApi($transaction, $billing_info, $paypal_pm); + $create_order_api = EED_PayPalCommerce::getCreateOrderApi($transaction, $billing_info, $paypal_pm); if (! $create_order_api instanceof CreateOrder) { return [ 'error' => 'CREATE_ORDER_API_FAULT', @@ -195,16 +180,13 @@ public static function createOrder( * @param EE_Payment_Method $paypal_pm * @param string $order_id * @return array - * @throws EE_Error - * @throws ReflectionException - * @throws Exception */ public static function captureOrder( EE_Transaction $transaction, EE_Payment_Method $paypal_pm, string $order_id ): array { - $capture_order_api = self::getCaptureOrderApi($transaction, $paypal_pm, $order_id); + $capture_order_api = EED_PayPalCommerce::getCaptureOrderApi($transaction, $paypal_pm, $order_id); if (! $capture_order_api instanceof CaptureOrder) { return [ 'error' => 'CAPTURE_ORDER_API_FAULT', @@ -216,7 +198,11 @@ public static function captureOrder( return $order; } // Attach the transaction ID to this order. - $order['ee_txn_id'] = $transaction->ID(); + try { + $order['ee_txn_id'] = $transaction->ID(); + } catch (Exception $e) { + // Just don't set the txn id. + } // Save this order details. PayPalExtraMetaManager::savePmOption($paypal_pm, Domain::META_KEY_LAST_ORDER, $order); $nonce = wp_create_nonce(Domain::CAPTURE_ORDER_NONCE_NAME); @@ -236,15 +222,14 @@ public static function captureOrder( * @param array $billing_info * @param EE_Payment_Method $paypal_pm * @return CreateOrder|null - * @throws Exception */ public static function getCreateOrderApi( EE_Transaction $transaction, array $billing_info, EE_Payment_Method $paypal_pm ): ?CreateOrder { - $paypal_api = self::getPayPalApi($paypal_pm); - if (! $paypal_api) { + $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm); + if (! $paypal_api instanceof PayPalApi) { return null; } return new CreateOrder($paypal_api, $transaction, $billing_info); @@ -258,15 +243,14 @@ public static function getCreateOrderApi( * @param EE_Payment_Method $paypal_pm * @param string $order_id * @return CaptureOrder|null - * @throws Exception */ public static function getCaptureOrderApi( EE_Transaction $transaction, EE_Payment_Method $paypal_pm, string $order_id ): ?CaptureOrder { - $paypal_api = self::getPayPalApi($paypal_pm); - if (! $paypal_api) { + $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm); + if (! $paypal_api instanceof PayPalApi) { return null; } return new CaptureOrder($paypal_api, $transaction, $order_id); @@ -278,17 +262,55 @@ public static function getCaptureOrderApi( * * @param EE_Payment_Method $paypal_pm * @return PayPalApi|null - * @throws Exception + * @throws EE_Error + * @throws ReflectionException */ public static function getPayPalApi(EE_Payment_Method $paypal_pm): ?PayPalApi { + // Try getting the first party credentials to determine if this is a first party integration that's active. $client_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_CLIENT_ID); $client_secret = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_CLIENT_SECRET); $bn_code = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_BN_CODE); - if (! $client_id || ! $client_secret || ! $bn_code) { + if ($client_id && $client_secret) { + return new FirstPartyPayPalApi($client_id, $client_secret, $bn_code, $paypal_pm->debug_mode()); + } + // Third party integration credentials: + $access_token = EED_PayPalOnboard::getPartnerAccessToken($paypal_pm); + if (! $access_token || ! $bn_code) { return null; } - return new PayPalApi($client_id, $client_secret, $bn_code, $paypal_pm->debug_mode()); + $partner_client_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_PARTNER_CLIENT_ID) ?: ''; + $payer_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_SELLER_MERCHANT_ID) ?: ''; + return new ThirdPartyPayPalApi($access_token, $bn_code, $partner_client_id, $payer_id, $paypal_pm->debug_mode()); + } + + + /** + * Requests a client token. + * + * @param EE_Payment_Method $paypal_pm + * @return array + */ + public static function requestClientToken(EE_Payment_Method $paypal_pm): array + { + $error = [ + 'error' => 'GET_CLIENT_TOKEN_FAULT' + ]; + $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm); + if (! $paypal_api instanceof PayPalApi) { + $error['message'] = esc_html__('Got an error while trying to get the client token.', 'event_espresso'); + return $error; + } + $client_token_api = new ClientToken($paypal_api, $paypal_pm->debug_mode()); + $client_token = $client_token_api->getToken(); + if (isset($client_token['error'])) { + return $client_token; + } + if (empty($client_token)) { + $error['message'] = esc_html__('Client token not valid.', 'event_espresso'); + return $error; + } + return $client_token; } @@ -296,23 +318,40 @@ public static function getPayPalApi(EE_Payment_Method $paypal_pm): ?PayPalApi * Retrieve the payment method from the provided data. * * @return EE_Transaction|null - * @throws EE_Error */ public static function getTransaction(): ?EE_Transaction { - $txn_id = EED_Module::getRequest()->getRequestParam('txn_id', 0, DataType::INT); - $transaction = EEM_Transaction::instance()->get_one_by_ID($txn_id); - if (! $transaction instanceof EE_Transaction) { - PayPalLogger::errorLog( - esc_html__('No transaction found by ID:', 'event_espresso'), - EED_Module::getRequest()->postParams() - ); + try { + $txn_id = EED_Module::getRequest()->getRequestParam('txn_id', 0, DataType::INT); + $transaction = EEM_Transaction::instance()->get_one_by_ID($txn_id); + if (! $transaction instanceof EE_Transaction) { + PayPalLogger::errorLog( + esc_html__('No transaction found by ID:', 'event_espresso'), + EED_Module::getRequest()->postParams() + ); + return null; + } + } catch (Exception $e) { return null; } return $transaction; } + /** + * Return a PayPal API object, or false on failure. + * + * @param EE_Payment_Method $paypal_pm + * @return bool + */ + public static function isThirdParty(EE_Payment_Method $paypal_pm): bool + { + $pp_meta_data = PayPalExtraMetaManager::getAllData($paypal_pm); + return ! empty($pp_meta_data[ Domain::META_KEY_SELLER_MERCHANT_ID ]) + && ! empty($pp_meta_data[ Domain::META_KEY_ACCESS_TOKEN ]); + } + + /** * Retrieve the payment method from the provided data. * @@ -327,7 +366,7 @@ public static function getPaymentMethod(): ?EE_Payment_Method if ($payment_method instanceof EE_Payment_Method) { return $payment_method; } - } catch (EE_Error $e) { + } catch (Exception $e) { return null; } return null; @@ -338,10 +377,8 @@ public static function getPaymentMethod(): ?EE_Payment_Method * Log JS error. * * @return void - * @throws EE_Error - * @throws ReflectionException */ - public static function logJsError() + public static function logJsError(): void { // Default to the "first" PayPal checkout PM. $request = EED_Module::getRequest(); @@ -365,7 +402,6 @@ public static function logJsError() * @param EE_Transaction $transaction * @param string $scope @see EEM_Payment_Method::get_all_for_events * @return array - * @throws EE_Error */ public static function filterPaymentMethods(array $payment_methods, $transaction, $scope): array { @@ -378,4 +414,26 @@ public static function filterPaymentMethods(array $payment_methods, $transaction } return $payment_methods; } + + + /** + * Get all active states. + * + * @return array + * @throws EE_Error + * @throws ReflectionException + */ + public static function getActiveStates(): array + { + $state_options = []; + $states = EEM_State::instance()->get_all_active_states(); + if (! empty($states)) { + foreach ($states as $numeral => $state) { + if ($state instanceof EE_State) { + $state_options[ $numeral ] = $state->abbrev(); + } + } + } + return $state_options; + } } diff --git a/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php b/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php index 3f0f6e3cfdb..a294428d709 100644 --- a/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php +++ b/PaymentMethods/PayPalCommerce/modules/EED_PayPalOnboard.module.php @@ -1,6 +1,9 @@ postParams()); - $signup_link = self::getSignUpLink($paypal_pm); + // $signup_link = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL); + // $token_expired = EED_PayPalOnboard::partnerAccessTokenExpired($paypal_pm); + // if (! $signup_link || $token_expired) { + $signup_link = EED_PayPalOnboard::requestOnboardingUrl($paypal_pm); + // } + if (! $signup_link) { + $err_msg = esc_html__('Error! Could not generate a sign-up link.', 'event_espresso'); + PayPalLogger::errorLogAndExit($err_msg, ['signup_link' => $signup_link], $paypal_pm); + } + PayPalExtraMetaManager::savePmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL, $signup_link); } catch (Exception $exception) { - self::exitWithError($exception->getMessage()); + PayPalLogger::errorLogAndExit($exception->getMessage()); } // Is it empty (can happen if we didn't get the URL through the API). $signup_link = $signup_link ? $signup_link . '?&displayMode=minibrowser' : '#'; - echo json_encode( + wp_send_json( [ 'signup_link' => $signup_link, ] ); - exit(); - } - - - /** - * Get the URL to redirect the seller to and start the onboarding. - * - * @param EE_Payment_Method $paypal_pm - * @return string - * @throws EE_Error - * @throws Exception - */ - public static function getSignUpLink(EE_Payment_Method $paypal_pm): string - { - $signup_link = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL); - $token_expired = self::partnerAccessTokenExpired($paypal_pm); - if (! $signup_link || $token_expired) { - // Generate sign-up link and save. - $signup_link = self::requestOnboardingUrl($paypal_pm); - if (! $signup_link) { - $err_msg = esc_html__('Error! Could not generate a sign-up link.', 'event_espresso'); - PayPalLogger::errorLog($err_msg, ['signup_link' => $signup_link], $paypal_pm); - return ''; - } - PayPalExtraMetaManager::savePmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL, $signup_link); - } - return $signup_link; } @@ -134,53 +110,25 @@ public static function getSignUpLink(EE_Payment_Method $paypal_pm): string * Request the sign-up link from PayPal. * * @param EE_Payment_Method $paypal_pm + * @param bool $one_time_request * @return string * @throws EE_Error * @throws Exception */ - public static function requestOnboardingUrl(EE_Payment_Method $paypal_pm): string + public static function requestOnboardingUrl(EE_Payment_Method $paypal_pm, bool $one_time_request = false): string { $signup_link = ''; // Get the access token. - $access_token = self::getPartnerAccessToken($paypal_pm); + $access_token = EED_PayPalOnboard::getPartnerAccessToken($paypal_pm); if (! $access_token) { $err_msg = esc_html__('Error! No access token.', 'event_espresso'); PayPalLogger::errorLog($err_msg, ['access_token' => $access_token], $paypal_pm); - return $signup_link; + return ''; } - $identifier_string = new OneTimeString($paypal_pm->debug_mode()); - $seller_nonce = $identifier_string->value(); - // Save the identifier for future use. - PayPalExtraMetaManager::savePmOption($paypal_pm, Domain::META_KEY_SELLER_NONCE, $seller_nonce); // Request the access token. - $body_params = json_encode( - [ - 'products' => ['EXPRESS_CHECKOUT'], - 'legal_consents' => [ - [ - 'type' => 'SHARE_DATA_CONSENT', - 'granted' => true, - ], - ], - 'operations' => [ - [ - 'operation' => 'API_INTEGRATION', - 'api_integration_preference' => [ - 'rest_api_integration' => [ - 'integration_method' => 'PAYPAL', - 'integration_type' => 'FIRST_PARTY', - 'first_party_details' => [ - 'features' => ['PAYMENT', 'REFUND', 'PARTNER_FEE'], - 'seller_nonce' => $seller_nonce, - ], - ], - ], - ], - ], - ] - ); + $body_params = EED_PayPalOnboard::signupLinkRequestBody($paypal_pm); $bn_code = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_BN_CODE); - $post_args = [ + $post_params = [ 'method' => 'POST', 'headers' => [ 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), @@ -190,13 +138,19 @@ public static function requestOnboardingUrl(EE_Payment_Method $paypal_pm): strin ], 'body' => $body_params, ]; - $request_url = self::getPayPalApiUrl($paypal_pm) . '/v2/customer/partner-referrals'; - $response = self::sendRequest($paypal_pm, $request_url, $post_args); - if (isset($response['error'])) { - return ''; - } + $request_url = Domain::getPayPalApiUrl($paypal_pm) . '/v2/customer/partner-referrals'; + $response = EED_PayPalOnboard::sendRequest($paypal_pm, $request_url, $post_params); // Check the data we received. - if (empty($response['links'])) { + if (isset($response['error']) || empty($response['links'])) { + // Did the original access token get replaced by any chance ? + if (! $one_time_request + && ! empty($response['message']) + && $response['message'] === 'Access Token not found in cache' + ) { + // Clear all PM metadata and try getting the access token One more time. + PayPalExtraMetaManager::deleteAllData($paypal_pm); + return EED_PayPalOnboard::requestOnboardingUrl($paypal_pm, true); + } $err_msg = esc_html__('Incoming sign-up link parameter validation failed.', 'event_espresso'); PayPalLogger::errorLog($err_msg, $response, $paypal_pm); return ''; @@ -204,29 +158,215 @@ public static function requestOnboardingUrl(EE_Payment_Method $paypal_pm): strin // Now retrieve that sign-up link. foreach ($response['links'] as $link) { if ($link['rel'] === 'action_url') { - return $link['href'] ?? ''; + $signup_link = $link['href'] ?? ''; } } return $signup_link; } + /** + * Get the return URL. + * + * @param EE_Payment_Method $paypal_pm + * @return string + * @throws Exception + */ + public static function signupLinkRequestBody(EE_Payment_Method $paypal_pm): string + { + $identifier_string = new OneTimeString($paypal_pm->debug_mode()); + $tracking_id = $identifier_string->value(); + $request = LoaderFactory::getLoader()->getShared(RequestInterface::class); + $checkout_type = $request->getRequestParam('checkout_type', 'EXPRESS_CHECKOUT', DataType::STRING); + // Save the identifier for future use. + PayPalExtraMetaManager::savePmOption($paypal_pm, Domain::META_KEY_TRACKING_ID, $tracking_id); + // Assemble the return URL. + $return_url = EED_PayPalOnboard::getReturnUrl($paypal_pm); + return json_encode([ + 'tracking_id' => $tracking_id, + 'operations' => [ + [ + 'operation' => 'API_INTEGRATION', + 'api_integration_preference' => [ + 'rest_api_integration' => [ + 'integration_method' => 'PAYPAL', + 'integration_type' => 'THIRD_PARTY', + 'third_party_details' => [ + 'features' => ['PAYMENT', 'REFUND'], + ], + ], + ], + ], + ], + 'products' => [$checkout_type], + 'legal_consents' => [ + [ + 'type' => 'SHARE_DATA_CONSENT', + 'granted' => true, + ], + ], + 'partner_config_override' => [ + 'return_url' => $return_url, + ], + ]); + } + + + /** + * Get the return URL. + * + * @param EE_Payment_Method $paypal_pm + * @return string + * @throws EE_Error + * @throws ReflectionException + */ + public static function getReturnUrl(EE_Payment_Method $paypal_pm): string + { + $wp_nonce = EED_Module::getRequest()->getRequestParam('wp_nonce'); + $nonce = wp_create_nonce(Domain::NONCE_NAME_ONBOARDING_RETURN); + return add_query_arg( + [ + 'page' => 'espresso_payment_settings', + 'webhook_action' => 'eea_pp_commerce_merchant_onboard', + 'payment_method' => $paypal_pm->slug(), + '_wpnonce' => $wp_nonce, + 'nonce' => $nonce, + Domain::META_KEY_SANDBOX_MODE => $paypal_pm->debug_mode() ? '1' : '0', + ], + admin_url('admin.php') + ); + } + + + /** + * Redirect to the payment method (PP) settings home page. + * + * @return void + */ + public static function redirectToPmSettingsHome(): void + { + $get_params = EED_Module::getRequest()->getParams(); + if (empty($get_params['payment_method'])) { + // Simply do not redirect. + return; + } + $args_to_add = [ + 'page' => 'espresso_payment_settings', + 'payment_method' => $get_params['payment_method'], + ]; + if (isset($get_params['sandbox_mode'])) { + $args_to_add[ Domain::META_KEY_SANDBOX_MODE ] = $get_params['sandbox_mode']; + } + $home_url = add_query_arg($args_to_add, admin_url('admin.php')); + wp_redirect($home_url); + exit; + } + + + /** + * Check user’s onboarding status. + * This will handle the user return from the auth page and also check the status via the API. + * + * @return void + * @throws EE_Error + * @throws ReflectionException + */ + public static function updateOnboardingStatus(): void + { + // Check if this is the webhook from PayPal. + if (! isset($_GET['webhook_action'], $_GET['nonce']) + || $_GET['webhook_action'] !== 'eea_pp_commerce_merchant_onboard' + ) { + return; // Ignore. + } + $get_params = EED_Module::getRequest()->getParams(); + // Get the payment method. + $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); + // Check the response (GET) parameters. + if (! EED_PayPalOnboard::onboardingStatusResponseValid($get_params, $paypal_pm)) { + // Missing parameters. Can't proceed. + PayPalLogger::errorLog( + esc_html__('Missing required onboarding parameters.', 'event_espresso'), + $get_params, + $paypal_pm + ); + EED_PayPalOnboard::redirectToPmSettingsHome(); + return; + } + // Were the requested permissions granted ? + if (empty($get_params[ Domain::API_PARAM_PERMISSIONS_GRANTED ])) { + $error_message = esc_html__( + 'Permissions not granted by merchant or email not confirmed.', + 'event_espresso' + ); + PayPalLogger::errorLog($error_message, $get_params, $paypal_pm); + EED_PayPalOnboard::redirectToPmSettingsHome(); + return; + } + // Check on the onboarding status (recommended by PP). + $onboarding_status = EED_PayPalOnboard::trackSellerOnboarding( + $paypal_pm, + $get_params[ Domain::META_KEY_SELLER_MERCHANT_ID ] + ); + if (! isset($onboarding_status['valid']) || ! $onboarding_status['valid']) { + PayPalLogger::errorLog( + $onboarding_status['message'], + array_merge($get_params, $onboarding_status), + $paypal_pm + ); + EED_PayPalOnboard::redirectToPmSettingsHome(); + return; + } + // Start saving the setup and info. + PayPalExtraMetaManager::parseAndSaveOptions($paypal_pm, $onboarding_status); + // Save the credentials. + PayPalExtraMetaManager::saveSellerApiCredentials($paypal_pm, $get_params); + // If onboarded successfully, remove the onboarding URL. + PayPalExtraMetaManager::deletePmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL); + // Also clen GET params by redirecting, because PP auto redirects to the return_url on closing the onboarding window. + EED_PayPalOnboard::redirectToPmSettingsHome(); + } + + + /** + * Check if all required parameters for the onboarding status check are present. + * + * @param array $data + * @param mixed $paypal_pm + * @return bool + */ + public static function onboardingStatusResponseValid(array $data, mixed $paypal_pm): bool + { + // Check that we have all the required parameters and the nonce is ok. + if ($paypal_pm instanceof EE_Payment_Method + && wp_verify_nonce($data['nonce'], Domain::NONCE_NAME_ONBOARDING_RETURN) + && ! empty($data[ Domain::API_PARAM_PARTNER_ID ]) + && ! empty($data[ Domain::META_KEY_SELLER_MERCHANT_ID ]) + && isset($data[ Domain::API_PARAM_PERMISSIONS_GRANTED ]) + && isset($data[ Domain::API_PARAM_EMAIL_CONFIRMED ]) + ) { + return true; + } + return false; + } + + /** * Get partner access token. * * @param EE_Payment_Method $paypal_pm * @return string * @throws EE_Error - * @throws Exception + * @throws ReflectionException */ public static function getPartnerAccessToken(EE_Payment_Method $paypal_pm): string { // Do we have it saved ? $access_token = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_ACCESS_TOKEN); - $expired = self::partnerAccessTokenExpired($paypal_pm); + $expired = EED_PayPalOnboard::partnerAccessTokenExpired($paypal_pm); // If we don't have it, request/update it. if (! $access_token || $expired) { - return self::requestPartnerAccessToken($paypal_pm); + return EED_PayPalOnboard::requestPartnerAccessToken($paypal_pm); } // Access token is saved as encrypted, so return decrypted. return $access_token; @@ -238,7 +378,6 @@ public static function getPartnerAccessToken(EE_Payment_Method $paypal_pm): stri * * @param EE_Payment_Method $paypal_pm * @return bool - * @throws Exception */ public static function partnerAccessTokenExpired(EE_Payment_Method $paypal_pm): bool { @@ -246,7 +385,7 @@ public static function partnerAccessTokenExpired(EE_Payment_Method $paypal_pm): if (! $expires_at) { return true; } - // Validate the token. Do a health check. + // Validate the token expiration date. $now = time(); $minutes_left = round(($expires_at - $now) / 60); // Count as expired if less than 60 minutes till expiration left. @@ -263,6 +402,7 @@ public static function partnerAccessTokenExpired(EE_Payment_Method $paypal_pm): * @param EE_Payment_Method $paypal_pm * @return string * @throws EE_Error + * @throws ReflectionException */ public static function requestPartnerAccessToken(EE_Payment_Method $paypal_pm): string { @@ -279,13 +419,13 @@ public static function requestPartnerAccessToken(EE_Payment_Method $paypal_pm): if (defined('LOCAL_MIDDLEMAN_SERVER')) { $post_args['sslverify'] = false; } - $post_url = self::getMiddlemanBaseUrl($paypal_pm) . 'get_token'; - $response = self::sendRequest($paypal_pm, $post_url, $post_args); + $post_url = EED_PayPalOnboard::getMiddlemanBaseUrl($paypal_pm) . 'get_token'; + $response = EED_PayPalOnboard::sendRequest($paypal_pm, $post_url, $post_args); if (isset($response['error'])) { return ''; } // Check the data we received. - if (! self::partnerTokenResponseValid($response, $paypal_pm)) { + if (! EED_PayPalOnboard::partnerTokenResponseValid($response, $paypal_pm)) { return ''; } // If we are here all seems to be ok. Save the token and it's data. @@ -298,178 +438,19 @@ public static function requestPartnerAccessToken(EE_Payment_Method $paypal_pm): /** - * Get the seller access token. - * (AJAX) - * - * @return void - * @throws Exception - */ - public static function getSellerAccessToken() - { - $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); - $post_params = EED_Module::getRequest()->postParams(); - $bn_code = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_BN_CODE); - if (! $paypal_pm instanceof EE_Payment_Method) { - PayPalLogger::errorLogAndExit( - esc_html__('No payment method.', 'event_espresso'), - $post_params, - $paypal_pm - ); - } - $seller_nonce = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_SELLER_NONCE); - // Look for mandatory parameters. - if ( - empty($post_params[ Domain::API_KEY_AUTH_CODE ]) - || empty($post_params[ Domain::API_KEY_SHARED_ID ]) - || ! $seller_nonce - || ! $bn_code - ) { - $error_message = esc_html__('Missing authCode and sharedId.', 'event_espresso'); - PayPalLogger::errorLogAndExit($error_message, $post_params, $paypal_pm); - } - $nonce = wp_create_nonce('eea_pp_commerce_get_seller_access_token'); - // Request the access token. - $post_args = [ - 'method' => 'POST', - 'headers' => [ - 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), - 'Content-Type' => 'application/json', - 'Authorization' => 'Basic ' . base64_encode($post_params[ Domain::API_KEY_SHARED_ID ]), - 'PayPal-Partner-Attribution-Id' => $bn_code, - ], - 'body' => [ - 'nonce' => $nonce, - 'grant_type' => 'authorization_code', - 'code' => $post_params[ Domain::API_KEY_AUTH_CODE ], - 'code_verifier' => $seller_nonce, - ], - ]; - $post_url = self::getPayPalApiUrl($paypal_pm) . '/v1/oauth2/token'; - $response = self::sendRequest($paypal_pm, $post_url, $post_args); - if (isset($response['error'])) { - self::exitWithError($response['message']); - } - // Check the data we received. - if ( - empty($response['access_token']) - || empty($response['expires_in']) - || empty($response['refresh_token']) - ) { - // This is an error. - $err_msg = esc_html__('Incoming parameter validation failed.', 'event_espresso'); - PayPalLogger::errorLogAndExit($err_msg, $response, $paypal_pm); - } - // Now we can request the seller API credentials. - $credentials_saved = self::requestApiCredentials($paypal_pm, $response['access_token']); - if (isset($credentials_saved['error'])) { - echo json_encode( - [ - 'error' => $credentials_saved['error'], - 'message' => $credentials_saved['message'], - ] - ); - } else { - echo json_encode( - [ - 'success' => true, - 'on_board' => true, - ] - ); - } - exit(); - } - - - /** - * Get the seller API credentials. + * Request seller onboarding status from PayPal. * * @param EE_Payment_Method $paypal_pm - * @param string $seller_token + * @param string $merchant_id * @return array - * @throws EE_Error - * @throws Exception */ - public static function requestApiCredentials(EE_Payment_Method $paypal_pm, string $seller_token): array + public static function trackSellerOnboarding(EE_Payment_Method $paypal_pm, string $merchant_id): array { - $partner_merchant_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_PARTNER_MERCHANT_ID); - $bn_code = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_BN_CODE); - $get_params = [ - 'method' => 'GET', - 'headers' => [ - 'User-Agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT']), - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $seller_token, - 'PayPal-Partner-Attribution-Id' => $bn_code, - ], - ]; - $request_url = self::getPayPalApiUrl($paypal_pm) - . '/v1/customer/partners/' - . $partner_merchant_id - . '/merchant-integrations/credentials/'; - $response = self::sendRequest($paypal_pm, $request_url, $get_params); - // Check the data we received. - if (empty($response['client_id']) || empty($response['client_secret'])) { - // This is an error. - if (isset($response['message'])) { - $err_msg = $response['message']; - $err_name = $response['name'] ?? 'UNRECOGNIZED_ERROR'; - } else { - $err_msg = esc_html__('Incoming parameter validation failed.', 'event_espresso'); - $err_name = 'INCOMING_PARAMETER_INVALID'; - } - PayPalLogger::errorLog($err_msg, $response, $paypal_pm); - return ['error' => $err_name, 'message' => $err_msg]; - } - // Finally, track seller onboarding status. - $onboarding_status = self::trackSellerOnboarding( - $paypal_pm, - $partner_merchant_id, - $response[ Domain::META_KEY_PAYER_ID ], - $response[ Domain::META_KEY_CLIENT_ID ], - $response[ Domain::META_KEY_CLIENT_SECRET ] - ); - if (isset($onboarding_status['error'])) { - return $onboarding_status; - } - // If onboarded successfully, remove the onetime onboarding URL. - if (PayPalExtraMetaManager::saveSellerApiCredentials($paypal_pm, $response)) { - PayPalExtraMetaManager::deletePmOption($paypal_pm, Domain::META_KEY_ONBOARDING_URL); - return ['success' => true]; - } else { - return [ - 'error' => 'SELLER_CREDENTIALS_NOT_SAVED', - 'message' => esc_html__('Seller credentials were not saved.', 'event_espresso'), - ]; + $track_onboarding = EED_PayPalOnboard::getTrackOnboardingApi($paypal_pm, $merchant_id); + if (! $track_onboarding instanceof TrackSellerOnboarding) { + $err_msg = esc_html__('Failed to track seller onboarding.', 'event_espresso'); + return ['error' => 'TRACK_ONBOARDING_FAILED', 'message' => $err_msg]; } - } - - - /** - * Request seller onboarding status from PayPal. - * - * @param EE_Payment_Method $paypal_pm - * @param string $partner_id - * @param $seller_id - * @param string $client_id - * @param string $client_secret - * @return array - * @throws EE_Error - * @throws Exception - */ - public static function trackSellerOnboarding( - EE_Payment_Method $paypal_pm, - string $partner_id, - $seller_id, - string $client_id, - string $client_secret - ): array { - $track_onboarding = self::getTrackOnboardingApi( - $paypal_pm, - $partner_id, - $seller_id, - $client_id, - $client_secret - ); return $track_onboarding->isValid(); } @@ -478,47 +459,21 @@ public static function trackSellerOnboarding( * Returns the Track Seller Onboarding API. * * @param EE_Payment_Method $paypal_pm - * @param string $partner_id - * @param string $seller_id - * @param string $client_id - * @param string $client_secret + * @param string $merchant_id * @return TrackSellerOnboarding|null - * @throws Exception + * @throws EE_Error + * @throws ReflectionException */ public static function getTrackOnboardingApi( EE_Payment_Method $paypal_pm, - string $partner_id, - string $seller_id, - string $client_id, - string $client_secret + string $merchant_id ): ?TrackSellerOnboarding { - $paypal_api = self::getPayPalApi($paypal_pm, $client_id, $client_secret); - if (! $paypal_api) { + $partner_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_PARTNER_MERCHANT_ID); + $paypal_api = EED_PayPalCommerce::getPayPalApi($paypal_pm); + if (! $paypal_api instanceof PayPalApi || ! $partner_id) { return null; } - return new TrackSellerOnboarding($paypal_api, $partner_id, $seller_id, $paypal_pm->debug_mode()); - } - - - /** - * Return a PayPal API object, or false on failure. - * - * @param EE_Payment_Method $paypal_pm - * @param string $client_id - * @param string $client_secret - * @return PayPalApi|null - * @throws Exception - */ - public static function getPayPalApi( - EE_Payment_Method $paypal_pm, - string $client_id, - string $client_secret - ): ?PayPalApi { - $bn_code = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_BN_CODE); - if (! $client_id || ! $client_secret || ! $bn_code) { - return null; - } - return new PayPalApi($client_id, $client_secret, $bn_code, $paypal_pm->debug_mode()); + return new TrackSellerOnboarding($paypal_api, $partner_id, $merchant_id, $paypal_pm->debug_mode()); } @@ -527,48 +482,71 @@ public static function getPayPalApi( * (AJAX) * * @return void - * @throws EE_Error */ - public static function getOnboardStatus() + public static function getOnboardStatus(): void { $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); if (! $paypal_pm instanceof EE_Payment_Method) { $err_msg = esc_html__('Could not specify the payment method.', 'event_espresso'); PayPalLogger::errorLog($err_msg, EED_Module::getRequest()->postParams(), $paypal_pm); - echo json_encode(['on_board' => false]); - exit(); + wp_send_json(['on_board' => false]); } try { - $seller_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_PAYER_ID) ?? '--'; + $seller_id = PayPalExtraMetaManager::getPmOption($paypal_pm, Domain::META_KEY_SELLER_MERCHANT_ID) ?? '--'; } catch (Exception $e) { $seller_id = '--'; } - echo json_encode( + wp_send_json( [ - 'on_board' => self::isOnboard($paypal_pm), + 'on_board' => EED_PayPalOnboard::isOnboard($paypal_pm), 'seller_id' => $seller_id, ] ); - exit(); } /** - * Deauthorize the seller. Remove all API credentials. + * De-authorize the seller. Remove all API credentials. * (AJAX) * * @return void */ - public static function offboard() + public static function offboard(): void { $paypal_pm = EED_PayPalCommerce::getPaymentMethod(); + if (! $paypal_pm instanceof EE_Payment_Method) { + wp_send_json([ + 'error' => 'INVALID_PM', + 'message' => esc_html__( + 'Invalid payment method. Please refresh the page and try again.', + 'event_espresso' + ), + ]); + } PayPalExtraMetaManager::deleteAllData($paypal_pm); - echo json_encode( - [ - 'success' => true, - ] - ); - exit(); + wp_send_json(['success' => true]); + } + + + /** + * Checks if already onboard. + * + * @param EE_Payment_Method $payment_method + * @return boolean + */ + public static function isOnboard(EE_Payment_Method $payment_method): bool + { + $pp_meta_data = PayPalExtraMetaManager::getAllData($payment_method); + return + // onborded with a third party integration ? + (! empty($pp_meta_data[ Domain::META_KEY_SELLER_MERCHANT_ID ]) + && ! empty($pp_meta_data[ Domain::META_KEY_ACCESS_TOKEN ]) + ) + // or with the first party integration ? + || (! empty($pp_meta_data[ Domain::META_KEY_CLIENT_ID ]) + && ! empty($pp_meta_data[ Domain::META_KEY_CLIENT_SECRET ]) + && ! empty($pp_meta_data[ Domain::META_KEY_PAYER_ID ]) + ); } @@ -579,7 +557,6 @@ public static function offboard() * @param string $request_url * @param array $request_args * @return array - * @throws EE_Error */ public static function sendRequest(EE_Payment_Method $paypal_pm, string $request_url, array $request_args): array { @@ -594,10 +571,10 @@ public static function sendRequest(EE_Payment_Method $paypal_pm, string $request $response_body = (isset($response['body']) && $response['body']) ? json_decode($response['body'], true) : []; if (empty($response_body) || isset($response_body['error'])) { $message = $response_body['error_description'] - ?? sprintf( - esc_html__('Unknown response received while sending a request to: %1$s', 'event_espresso'), - $request_url - ); + ?? sprintf( + esc_html__('Unknown response received while sending a request to: %1$s', 'event_espresso'), + $request_url + ); PayPalLogger::errorLog($message, [$request_url, $request_args, $response], $paypal_pm); $error_return['message'] = $message; return $error_return; @@ -612,7 +589,6 @@ public static function sendRequest(EE_Payment_Method $paypal_pm, string $request * @param $response * @param EE_Payment_Method $paypal_pm * @return bool - * @throws EE_Error */ public static function partnerTokenResponseValid($response, EE_Payment_Method $paypal_pm): bool { @@ -641,6 +617,8 @@ public static function partnerTokenResponseValid($response, EE_Payment_Method $p * * @param EE_Payment_Method $payment_method * @return string + * @throws EE_Error + * @throws ReflectionException */ public static function getMiddlemanBaseUrl(EE_Payment_Method $payment_method): string { @@ -654,56 +632,61 @@ public static function getMiddlemanBaseUrl(EE_Payment_Method $payment_method): s /** - * Returns the base PayPal API URL. + * This Payment Method admin notices. * - * @param EE_Payment_Method $payment_method - * @return string - */ - public static function getPayPalApiUrl(EE_Payment_Method $payment_method): string - { - return $payment_method->debug_mode() ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; - } - - - /** - * Checks if already onboard. - * - * @param EE_Payment_Method $payment_method - * @return boolean + * @return void * @throws EE_Error + * @throws ReflectionException */ - public static function isOnboard(EE_Payment_Method $payment_method): bool + public static function adminNotice() { - $pp_meta_data = PayPalExtraMetaManager::getAllData($payment_method); - if ( - $pp_meta_data - && isset($pp_meta_data[ Domain::META_KEY_CLIENT_ID ]) - && $pp_meta_data[ Domain::META_KEY_CLIENT_ID ] - && isset($pp_meta_data[ Domain::META_KEY_CLIENT_SECRET ]) - && $pp_meta_data[ Domain::META_KEY_CLIENT_SECRET ] + // A notice to re-connect if still connected through the first party integration. + $pp_commerce = EEM_Payment_Method::instance()->get_one_by_slug('paypalcheckout'); + // Show the notice only if PayPal Commerce is active. + if (! $pp_commerce instanceof EE_Payment_Method + || ! EED_PayPalOnboard::isOnboard($pp_commerce) + || EED_PayPalCommerce::isThirdParty($pp_commerce) ) { - return true; + return; } - return false; + add_action('admin_notices', [__CLASS__, 'reConnectNotice']); } /** - * Return error message as json allowing to show an alert on the front-end. + * Re-connect notice contents. * - * @param string $error_message - * @param bool $show_alert * @return void + * @throws EE_Error + * @throws ReflectionException */ - public static function exitWithError(string $error_message = '', bool $show_alert = false) + public static function reConnectNotice() { - echo json_encode( - [ - 'error' => $error_message, - 'message' => $error_message, - 'alert' => $show_alert, - ] - ); - exit(); + $open_anchor = $close_anchor = ''; + $pp_commerce = EEM_Payment_Method::instance()->get_one_by_slug('paypalcheckout'); + if ($pp_commerce instanceof EE_Payment_Method) { + $pm_page = add_query_arg( + [ + 'page' => 'espresso_payment_settings', + 'webhook_action' => 'eea_pp_commerce_merchant_onboard', + 'payment_method' => $pp_commerce->slug(), + ], + admin_url('admin.php') + ); + $open_anchor = ""; + $close_anchor = ""; + } + echo '' + . sprintf( + esc_html__( + '%1$sPayPal Commerce%2$s has updated the API integration type to allow more flexibility with payments. Please disconnect and re-Connect on the %3$sPayment Methods admin%4$s page to update the credentials and allow advanced payment type options.', + 'event_espresso' + ), + '', + '', + $open_anchor, + $close_anchor + ) + . '
(Component: React.ComponentType
): React.FC
=> {\n\tconst WrappedComponent: React.FC
= (props) => {\n\t\treturn (\n\t\t\t