568 lines
14 KiB
PHP
568 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Gateway Manager
|
|
*
|
|
* Manages the registering and activation of gateways.
|
|
*
|
|
* @package WP_Ultimo
|
|
* @subpackage Managers/Gateway
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
namespace WP_Ultimo\Managers;
|
|
|
|
use Psr\Log\LogLevel;
|
|
use WP_Ultimo\Managers\Base_Manager;
|
|
use WP_Ultimo\Gateways\Ignorable_Exception;
|
|
|
|
use WP_Ultimo\Gateways\Free_Gateway;
|
|
use WP_Ultimo\Gateways\Stripe_Gateway;
|
|
use WP_Ultimo\Gateways\Stripe_Checkout_Gateway;
|
|
use WP_Ultimo\Gateways\PayPal_Gateway;
|
|
use WP_Ultimo\Gateways\Manual_Gateway;
|
|
|
|
// Exit if accessed directly
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* Manages the registering and activation of gateways.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
class Gateway_Manager extends Base_Manager {
|
|
|
|
use \WP_Ultimo\Traits\Singleton;
|
|
|
|
/**
|
|
* Lists the registered gateways.
|
|
*
|
|
* @since 2.0.0
|
|
* @var array
|
|
*/
|
|
protected $registered_gateways = [];
|
|
|
|
/**
|
|
* Lists the gateways that are enabled.
|
|
*
|
|
* @since 2.0.0
|
|
* @var array
|
|
*/
|
|
protected $enabled_gateways = [];
|
|
|
|
/**
|
|
* Keeps a list of the gateways with auto-renew.
|
|
*
|
|
* @since 2.0.0
|
|
* @var array
|
|
*/
|
|
protected $auto_renewable_gateways = [];
|
|
|
|
/**
|
|
* Instantiate the necessary hooks.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function init(): void {
|
|
|
|
add_action('plugins_loaded', [$this, 'on_load']);
|
|
}
|
|
|
|
/**
|
|
* Runs after all plugins have been loaded to allow for add-ons to hook into it correctly.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function on_load(): void {
|
|
/*
|
|
* Adds our own default gateways.
|
|
*/
|
|
add_action('wu_register_gateways', [$this, 'add_default_gateways'], 5);
|
|
/*
|
|
* Allow developers to add new gateways.
|
|
*/
|
|
add_action(
|
|
'init',
|
|
function () {
|
|
do_action('wu_register_gateways');
|
|
},
|
|
19
|
|
);
|
|
|
|
/*
|
|
* Adds the Gateway selection fields
|
|
*/
|
|
add_action('init', [$this, 'add_gateway_selector_field']);
|
|
|
|
/*
|
|
* Handle gateway confirmations.
|
|
* We need it both on the front-end and the back-end.
|
|
*/
|
|
add_action('template_redirect', [$this, 'process_gateway_confirmations'], -99999);
|
|
add_action('load-admin_page_wu-checkout', [$this, 'process_gateway_confirmations'], -99999);
|
|
|
|
/*
|
|
* Waits for webhook signals and deal with them.
|
|
*/
|
|
add_action('init', [$this, 'maybe_process_webhooks'], 1);
|
|
|
|
/*
|
|
* Waits for webhook signals and deal with them.
|
|
*/
|
|
add_action('admin_init', [$this, 'maybe_process_v1_webhooks'], 1);
|
|
}
|
|
|
|
/**
|
|
* Checks if we need to process webhooks received by gateways.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function maybe_process_webhooks(): void {
|
|
|
|
$gateway = wu_request('wu-gateway');
|
|
|
|
if ($gateway && ! is_admin() && is_main_site()) {
|
|
/*
|
|
* Do not cache this!
|
|
*/
|
|
!defined('DONOTCACHEPAGE') && define('DONOTCACHEPAGE', true); // phpcs:ignore
|
|
|
|
try {
|
|
/*
|
|
* Passes it down to Gateways.
|
|
*
|
|
* Gateways will hook into here
|
|
* to handle their respective webhook
|
|
* calls.
|
|
*
|
|
* We also wrap it inside a try/catch
|
|
* to make sure we log errors,
|
|
* tell network admins, and make sure
|
|
* the gateway tries again by sending back
|
|
* a non-200 HTTP code.
|
|
*/
|
|
do_action("wu_{$gateway}_process_webhooks");
|
|
|
|
http_response_code(200);
|
|
|
|
die('Thanks!');
|
|
} catch (Ignorable_Exception $e) {
|
|
$message = sprintf('We failed to handle a webhook call, but in this case, no further action is necessary. Message: %s', $e->getMessage());
|
|
|
|
wu_log_add("wu-{$gateway}-webhook-errors", $message);
|
|
|
|
/*
|
|
* Send the error back, but with a 200.
|
|
*/
|
|
wp_send_json_error(new \WP_Error('webhook-error', $message), 200);
|
|
} catch (\Throwable $e) {
|
|
$file = $e->getFile();
|
|
$line = $e->getLine();
|
|
|
|
$message = sprintf('We failed to handle a webhook call. Error: %s', $e->getMessage());
|
|
|
|
$message .= PHP_EOL . "Location: {$file}:{$line}";
|
|
$message .= PHP_EOL . $e->getTraceAsString();
|
|
|
|
wu_log_add("wu-{$gateway}-webhook-errors", $message, LogLevel::ERROR);
|
|
|
|
/*
|
|
* Force a 500.
|
|
*
|
|
* Most gateways will try again later when
|
|
* a non-200 code is returned.
|
|
*/
|
|
wp_send_json_error(new \WP_Error('webhook-error', $message), 500);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if we need to process webhooks received by legacy gateways.
|
|
*
|
|
* @since 2.0.4
|
|
* @return void
|
|
*/
|
|
public function maybe_process_v1_webhooks(): void {
|
|
|
|
$action = wu_request('action', '');
|
|
|
|
if ($action && str_contains((string) $action, 'notify_gateway_')) {
|
|
/*
|
|
* Get the gateway id from the action.
|
|
*/
|
|
$gateway_id = str_replace(['nopriv_', 'notify_gateway_'], '', (string) $action);
|
|
|
|
$gateway = wu_get_gateway($gateway_id);
|
|
|
|
if ($gateway) {
|
|
$gateway->before_backwards_compatible_webhook();
|
|
|
|
/*
|
|
* Do not cache this!
|
|
*/
|
|
!defined('DONOTCACHEPAGE') && define('DONOTCACHEPAGE', true); // phpcs:ignore
|
|
|
|
try {
|
|
/*
|
|
* Passes it down to Gateways.
|
|
*
|
|
* Gateways will hook into here
|
|
* to handle their respective webhook
|
|
* calls.
|
|
*
|
|
* We also wrap it inside a try/catch
|
|
* to make sure we log errors,
|
|
* tell network admins, and make sure
|
|
* the gateway tries again by sending back
|
|
* a non-200 HTTP code.
|
|
*/
|
|
do_action("wu_{$gateway_id}_process_webhooks");
|
|
|
|
http_response_code(200);
|
|
|
|
die('Thanks!');
|
|
} catch (Ignorable_Exception $e) {
|
|
$message = sprintf('We failed to handle a webhook call, but in this case, no further action is necessary. Message: %s', $e->getMessage());
|
|
|
|
wu_log_add("wu-{$gateway_id}-webhook-errors", $message);
|
|
|
|
/*
|
|
* Send the error back, but with a 200.
|
|
*/
|
|
wp_send_json_error(new \WP_Error('webhook-error', $message), 200);
|
|
} catch (\Throwable $e) {
|
|
$message = sprintf('We failed to handle a webhook call. Error: %s', $e->getMessage());
|
|
|
|
wu_log_add("wu-{$gateway_id}-webhook-errors", $message, LogLevel::ERROR);
|
|
|
|
/*
|
|
* Force a 500.
|
|
*
|
|
* Most gateways will try again later when
|
|
* a non-200 code is returned.
|
|
*/
|
|
http_response_code(500);
|
|
|
|
wp_send_json_error(new \WP_Error('webhook-error', $message));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Let gateways deal with their confirmation steps.
|
|
*
|
|
* This is the case for PayPal Express.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function process_gateway_confirmations(): void {
|
|
/*
|
|
* First we check for the confirmation parameter.
|
|
*/
|
|
if ( ! wu_request('wu-confirm') || (wu_request('status') && wu_request('status') === 'done')) {
|
|
return;
|
|
}
|
|
|
|
ob_start();
|
|
|
|
add_filter('body_class', fn($classes) => array_merge($classes, ['wu-process-confirmation']));
|
|
|
|
$gateway_id = sanitize_text_field(wu_request('wu-confirm'));
|
|
|
|
$gateway = wu_get_gateway($gateway_id);
|
|
|
|
if ( ! $gateway) {
|
|
$error = new \WP_Error('missing_gateway', __('Missing gateway parameter.', 'wp-ultimo'));
|
|
|
|
wp_die(
|
|
$error,
|
|
__('Error', 'wp-ultimo'),
|
|
[
|
|
'back_link' => true,
|
|
'response' => '200',
|
|
]
|
|
);
|
|
}
|
|
|
|
try {
|
|
$payment_hash = wu_request('payment');
|
|
|
|
$payment = wu_get_payment_by_hash($payment_hash);
|
|
|
|
if ($payment) {
|
|
$gateway->set_payment($payment);
|
|
}
|
|
|
|
/*
|
|
* Pass it down to the gateway.
|
|
*
|
|
* Here you can throw exceptions, that
|
|
* we will catch it and throw it as a wp_die
|
|
* message.
|
|
*/
|
|
$results = $gateway->process_confirmation();
|
|
|
|
if (is_wp_error($results)) {
|
|
wp_die(
|
|
$results,
|
|
__('Error', 'wp-ultimo'),
|
|
[
|
|
'back_link' => true,
|
|
'response' => '200',
|
|
]
|
|
);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$error = new \WP_Error('confirm-error-' . $e->getCode(), $e->getMessage());
|
|
|
|
wp_die(
|
|
$error,
|
|
__('Error', 'wp-ultimo'),
|
|
[
|
|
'back_link' => true,
|
|
'response' => '200',
|
|
]
|
|
);
|
|
}
|
|
|
|
$output = ob_get_clean();
|
|
|
|
if ( ! empty($output)) {
|
|
/*
|
|
* Add a filter to bypass the checkout form.
|
|
* This is used for PayPal confirmation page.
|
|
*/
|
|
add_action('wu_bypass_checkout_form', fn($bypass, $atts) => $output, 10, 2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the field that enabled and disables Payment Gateways on the settings.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function add_gateway_selector_field(): void {
|
|
|
|
wu_register_settings_field(
|
|
'payment-gateways',
|
|
'active_gateways',
|
|
[
|
|
'title' => __('Active Payment Gateways', 'wp-ultimo'),
|
|
'desc' => __('Payment gateways are what your customers will use to pay.', 'wp-ultimo'),
|
|
'type' => 'multiselect',
|
|
'columns' => 2,
|
|
'options' => [$this, 'get_gateways_as_options'],
|
|
'default' => [],
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of registered gateways as options for the gateway selector setting.
|
|
*
|
|
* @since 2.0.0
|
|
* @return array
|
|
*/
|
|
public function get_gateways_as_options() {
|
|
/*
|
|
* We use this to order the options.
|
|
*/
|
|
$active_gateways = wu_get_setting('active_gateways', []);
|
|
|
|
$gateways = $this->get_registered_gateways();
|
|
|
|
$gateways = array_filter($gateways, fn($item) => $item['hidden'] === false);
|
|
|
|
return $gateways;
|
|
}
|
|
|
|
/**
|
|
* Loads the default gateways.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function add_default_gateways(): void {
|
|
/*
|
|
* Free Payments
|
|
*/
|
|
wu_register_gateway('free', __('Free', 'wp-ultimo'), '', Free_Gateway::class, true);
|
|
|
|
/*
|
|
* Stripe Payments
|
|
*/
|
|
$stripe_desc = __('Stripe is a suite of payment APIs that powers commerce for businesses of all sizes, including subscription management.', 'wp-ultimo');
|
|
wu_register_gateway('stripe', __('Stripe', 'wp-ultimo'), $stripe_desc, Stripe_Gateway::class);
|
|
|
|
/*
|
|
* Stripe Checkout Payments
|
|
*/
|
|
$stripe_checkout_desc = __('Stripe Checkout is the hosted solution for checkouts using Stripe.', 'wp-ultimo');
|
|
wu_register_gateway('stripe-checkout', __('Stripe Checkout', 'wp-ultimo'), $stripe_checkout_desc, Stripe_Checkout_Gateway::class);
|
|
|
|
/*
|
|
* PayPal Payments
|
|
*/
|
|
$paypal_desc = __('PayPal is the leading provider in checkout solutions and it is the easier way to get your network subscriptions going.', 'wp-ultimo');
|
|
wu_register_gateway('paypal', __('PayPal', 'wp-ultimo'), $paypal_desc, PayPal_Gateway::class);
|
|
|
|
/*
|
|
* Manual Payments
|
|
*/
|
|
$manual_desc = __('Use the Manual Gateway to allow users to pay you directly via bank transfers, checks, or other channels.', 'wp-ultimo');
|
|
wu_register_gateway('manual', __('Manual', 'wp-ultimo'), $manual_desc, Manual_Gateway::class);
|
|
}
|
|
|
|
/**
|
|
* Checks if a gateway was already registered.
|
|
*
|
|
* @since 2.0.0
|
|
* @param string $id The id of the gateway.
|
|
* @return boolean
|
|
*/
|
|
public function is_gateway_registered($id) {
|
|
|
|
return is_array($this->registered_gateways) && isset($this->registered_gateways[ $id ]);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all the registered gateways
|
|
*
|
|
* @since 2.0.0
|
|
* @return array
|
|
*/
|
|
public function get_registered_gateways() {
|
|
|
|
return $this->registered_gateways;
|
|
}
|
|
|
|
/**
|
|
* Returns a particular Gateway registered
|
|
*
|
|
* @since 2.0.0
|
|
* @param string $id The id of the gateway.
|
|
* @return array
|
|
*/
|
|
public function get_gateway($id) {
|
|
|
|
return $this->is_gateway_registered($id) ? $this->registered_gateways[ $id ] : false;
|
|
}
|
|
|
|
/**
|
|
* Adds a new Gateway to the System. Used by gateways to make themselves visible.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param string $id ID of the gateway. This is how we will identify the gateway in the system.
|
|
* @param string $title Name of the gateway.
|
|
* @param string $desc A description of the gateway to help super admins understand what services they integrate with.
|
|
* @param string $class_name Gateway class name.
|
|
* @param bool $hidden If we need to hide this gateway publicly.
|
|
* @return bool
|
|
*/
|
|
public function register_gateway($id, $title, $desc, $class_name, $hidden = false) {
|
|
|
|
// Checks if gateway was already added
|
|
if ($this->is_gateway_registered($id)) {
|
|
return;
|
|
}
|
|
|
|
$active_gateways = (array) wu_get_setting('active_gateways', []);
|
|
|
|
// Adds to the global
|
|
$this->registered_gateways[ $id ] = [
|
|
'id' => $id,
|
|
'title' => $title,
|
|
'desc' => $desc,
|
|
'class_name' => $class_name,
|
|
'active' => in_array($id, $active_gateways, true),
|
|
'active' => in_array($id, $active_gateways, true),
|
|
'hidden' => (bool) $hidden,
|
|
'gateway' => $class_name, // Deprecated.
|
|
];
|
|
|
|
$this->install_hooks($class_name);
|
|
|
|
// Return the value
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Adds additional hooks for each of the gateway registered.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param string $class_name Gateway class name.
|
|
* @return void
|
|
*/
|
|
public function install_hooks($class_name): void {
|
|
|
|
$gateway = new $class_name();
|
|
|
|
$gateway_id = $gateway->get_id();
|
|
|
|
/*
|
|
* If the gateway supports recurring
|
|
* payments, add it to the list.
|
|
*/
|
|
if ($gateway->supports_recurring()) {
|
|
$this->auto_renewable_gateways[] = $gateway_id;
|
|
}
|
|
|
|
add_action('wu_checkout_scripts', [$gateway, 'register_scripts']);
|
|
|
|
$gateway->hooks();
|
|
add_action('wu_settings_payment_gateways', [$gateway, 'settings']);
|
|
|
|
add_action("wu_{$gateway_id}_process_webhooks", [$gateway, 'process_webhooks']);
|
|
|
|
add_action("wu_{$gateway_id}_remote_payment_url", [$gateway, 'get_payment_url_on_gateway']);
|
|
|
|
add_action("wu_{$gateway_id}_remote_subscription_url", [$gateway, 'get_subscription_url_on_gateway']);
|
|
|
|
add_action("wu_{$gateway_id}_remote_customer_url", [$gateway, 'get_customer_url_on_gateway']);
|
|
|
|
/*
|
|
* Renders the gateway fields.
|
|
*/
|
|
add_action(
|
|
'wu_checkout_gateway_fields',
|
|
function ($checkout) use ($gateway) {
|
|
|
|
$field_content = call_user_func([$gateway, 'fields']);
|
|
|
|
ob_start();
|
|
|
|
?>
|
|
|
|
<div v-cloak v-show="gateway == '<?php echo esc_attr($gateway->get_id()); ?>' && order && order.should_collect_payment" class="wu-overflow">
|
|
|
|
<?php echo $field_content; ?>
|
|
|
|
</div>
|
|
|
|
<?php
|
|
|
|
echo ob_get_clean();
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns an array with the list of gateways that support auto-renew.
|
|
*
|
|
* @since 2.0.0
|
|
* @return mixed
|
|
*/
|
|
public function get_auto_renewable_gateways() {
|
|
|
|
return (array) $this->auto_renewable_gateways;
|
|
}
|
|
}
|