Initial Commit
This commit is contained in:
885
inc/gateways/class-base-gateway.php
Normal file
885
inc/gateways/class-base-gateway.php
Normal file
@ -0,0 +1,885 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Gateway.
|
||||
*
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Managers/Site_Manager
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
// Exit if accessed directly
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* For more info on actual implementations,
|
||||
* check the Gateway_Manual class and the Gateway_Stripe class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
abstract class Base_Gateway {
|
||||
|
||||
/**
|
||||
* The gateway ID.
|
||||
*
|
||||
* A simple string that the class should set.
|
||||
* e.g. stripe, manual, paypal, etc.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Allow gateways to declare multiple additional ids.
|
||||
*
|
||||
* These ids can be retrieved alongside the main id,
|
||||
* via the method get_all_ids().
|
||||
*
|
||||
* This is useful when dealing with different gateway implementations
|
||||
* that share the same base code, or that have code that is applicable
|
||||
* to other gateways.
|
||||
*
|
||||
* A classical example is the way Stripe is setup on WP Ultimo now:
|
||||
* - We have two stripe gateways - stripe and stripe-checkout;
|
||||
* - Both of those gateways inherit from class-base-stripe-gateway.php,
|
||||
* which deals with appending the remote gateway links to the admin panel,
|
||||
* for example.
|
||||
* - The problem arises when the hooks are id-bound. If you have customer
|
||||
* that signup via stripe and later on you deactivate stripe in favor of
|
||||
* stripe-checkout, the admin panel links will stop working, as the hooks
|
||||
* are only triggered for stripe-checkout integrations, and old memberships
|
||||
* have stripe as the gateway.
|
||||
* - If you declare the other ids here, the hooks will be loaded for the
|
||||
* other gateways, and that will no longer be a problem.
|
||||
*
|
||||
* @since 2.0.7
|
||||
* @var array
|
||||
*/
|
||||
protected $other_ids = array();
|
||||
|
||||
/**
|
||||
* The order cart object.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var \WP_Ultimo\Checkout\Cart
|
||||
*/
|
||||
protected $order;
|
||||
|
||||
/**
|
||||
* The customer.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var \WP_Ultimo\Models\Customer
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
/**
|
||||
* The membership.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var \WP_Ultimo\Models\Membership
|
||||
*/
|
||||
protected $membership;
|
||||
|
||||
/**
|
||||
* The payment.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var \WP_Ultimo\Models\Payment
|
||||
*/
|
||||
protected $payment;
|
||||
|
||||
/**
|
||||
* The URL to return.
|
||||
*
|
||||
* @since 2.1
|
||||
* @var string
|
||||
*/
|
||||
protected $return_url;
|
||||
|
||||
/**
|
||||
* The cancel URL.
|
||||
*
|
||||
* @since 2.1
|
||||
* @var string
|
||||
*/
|
||||
protected $cancel_url;
|
||||
|
||||
/**
|
||||
* The confirm URL.
|
||||
*
|
||||
* @since 2.1
|
||||
* @var string
|
||||
*/
|
||||
protected $confirm_url;
|
||||
|
||||
/**
|
||||
* The discount code, if any.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var null|\WP_Ultimo\Models\Discount_Code
|
||||
*/
|
||||
protected $discount_code;
|
||||
|
||||
/**
|
||||
* Backwards compatibility for the old notify ajax url.
|
||||
*
|
||||
* @since 2.0.4
|
||||
* @var bool|string
|
||||
*/
|
||||
protected $backwards_compatibility_v1_id = false;
|
||||
|
||||
/**
|
||||
* Initialized the gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @param null|\WP_Ultimo\Checkout\Cart $order A order cart object.
|
||||
*/
|
||||
public function __construct($order = null) {
|
||||
/*
|
||||
* Loads the order, if any
|
||||
*/
|
||||
$this->set_order($order);
|
||||
|
||||
/*
|
||||
* Calls the init code.
|
||||
*/
|
||||
$this->init();
|
||||
|
||||
} // end __construct;
|
||||
|
||||
/**
|
||||
* Sets an order.
|
||||
*
|
||||
* Useful for loading the order on a later
|
||||
* stage, where the gateway object might
|
||||
* have been already instantiated.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Checkout\Cart $order The order.
|
||||
* @return void
|
||||
*/
|
||||
public function set_order($order) {
|
||||
|
||||
if ($order === null) {
|
||||
|
||||
return;
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* The only thing we do is to set the order.
|
||||
* It contains everything we need.
|
||||
*/
|
||||
$this->order = $order;
|
||||
|
||||
/*
|
||||
* Based on the order, we set the other
|
||||
* useful parameters.
|
||||
*/
|
||||
$this->customer = $this->order->get_customer();
|
||||
$this->membership = $this->order->get_membership();
|
||||
$this->payment = $this->order->get_payment();
|
||||
$this->discount_code = $this->order->get_discount_code();
|
||||
|
||||
} // end set_order;
|
||||
|
||||
/**
|
||||
* Returns the id of the gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public final function get_id() {
|
||||
|
||||
return $this->id;
|
||||
|
||||
} // end get_id;
|
||||
|
||||
/*
|
||||
* Required Methods.
|
||||
*
|
||||
* The methods below are mandatory.
|
||||
* You need to have them on your Gateway implementation
|
||||
* even if they do nothing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Process a checkout.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a new checkout and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* set up recurring payment profiles, etc.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
|
||||
* @param string $type The checkout type. Can be 'new', 'retry', 'upgrade', 'downgrade', 'addon'.
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function process_checkout($payment, $membership, $customer, $cart, $type);
|
||||
|
||||
/**
|
||||
* Process a cancellation.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a membership cancellation and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* to cancel a recurring profile, etc.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
abstract public function process_cancellation($membership, $customer);
|
||||
|
||||
/**
|
||||
* Process a refund.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a refund and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* to issue a refund.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param float $amount The amount to refund.
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function process_refund($amount, $payment, $membership, $customer);
|
||||
|
||||
/*
|
||||
* Optional Methods.
|
||||
*
|
||||
* The methods below are good to have,
|
||||
* but are not mandatory.
|
||||
*
|
||||
* You can implement the ones you need only.
|
||||
* The base class provides defaults so you
|
||||
* don't have to worry about the ones you
|
||||
* don't need.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialization code.
|
||||
*
|
||||
* This method gets called by the constructor.
|
||||
* It is a good chance to set public properties to the
|
||||
* gateway object and run preparations.
|
||||
*
|
||||
* For example, it's here that the Stripe Gateway
|
||||
* sets its sandbox mode and API keys
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function init() {} // end init;
|
||||
|
||||
/**
|
||||
* Adds Settings.
|
||||
*
|
||||
* This method allows developers to use
|
||||
* WP Ultimo apis to add settings to the settings
|
||||
* page.
|
||||
*
|
||||
* Gateways can use wu_register_settings_field
|
||||
* to register API key fields and other options.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function settings() {} // end settings;
|
||||
|
||||
/**
|
||||
* Checkout fields.
|
||||
*
|
||||
* This method gets called during the printing
|
||||
* of the gateways section of the payment page.
|
||||
*
|
||||
* Use this to add the pertinent fields to your gateway
|
||||
* like credit card number fields, for example.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function fields() {} // end fields;
|
||||
|
||||
/**
|
||||
* Declares support for recurring payments.
|
||||
*
|
||||
* Not all gateways support the creation of
|
||||
* automatically recurring payments.
|
||||
*
|
||||
* For those that don't, we need to manually
|
||||
* create pending payments when the time comes
|
||||
* and we use this declaration to decide that.
|
||||
*
|
||||
* If your gateway supports recurring payments
|
||||
* (like Stripe or PayPal, for example)
|
||||
* override this method to return true instead.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return bool
|
||||
*/
|
||||
public function supports_recurring() {
|
||||
|
||||
return false;
|
||||
|
||||
} // end supports_recurring;
|
||||
|
||||
/**
|
||||
* Declares support for free trials.
|
||||
*
|
||||
* WP Ultimo offers to ways of dealing with free trials:
|
||||
* (1) By asking for a payment method upfront; or
|
||||
* (2) By not asking for a payment method until the trial is over.
|
||||
*
|
||||
* If you go the second route, WP Ultimo uses
|
||||
* the free gateway to deal with the first payment (which will be 0)
|
||||
*
|
||||
* If you go the first route, though, the payment gateway
|
||||
* must be able to handle delayed first payments.
|
||||
*
|
||||
* If that's the case for your payment gateway,
|
||||
* override this method to return true.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return bool
|
||||
*/
|
||||
public function supports_free_trials() {
|
||||
|
||||
return false;
|
||||
|
||||
} // end supports_free_trials;
|
||||
|
||||
/**
|
||||
* Declares support for recurring amount updates.
|
||||
*
|
||||
* Some gateways can update the amount of a recurring
|
||||
* payment. For example, Stripe allows you to update
|
||||
* the amount of a subscription.
|
||||
*
|
||||
* If your gateway supports this, override this
|
||||
* method to return true. You will also need to
|
||||
* implement the process_membership_update() method.
|
||||
*
|
||||
* @since 2.1.2
|
||||
* @return bool
|
||||
*/
|
||||
public function supports_amount_update() {
|
||||
|
||||
return false;
|
||||
|
||||
} // end supports_amount_update;
|
||||
|
||||
/**
|
||||
* Handles payment method updates.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function update_payment_method() {} // end update_payment_method;
|
||||
|
||||
/**
|
||||
* Defines a public title.
|
||||
*
|
||||
* This is useful to be able to define a nice-name
|
||||
* for a gateway that will make more sense for customers.
|
||||
*
|
||||
* Stripe, for example, sets this value to 'Credit Card'
|
||||
* as showing up simply as Stripe would confuse customers.
|
||||
*
|
||||
* By default, we use the title passed when calling
|
||||
* wu_register_gateway().
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_public_title() {
|
||||
|
||||
$gateways = wu_get_gateways();
|
||||
|
||||
$registered_gateway = wu_get_isset($gateways, $this->get_id());
|
||||
|
||||
if (!$registered_gateway) {
|
||||
|
||||
$default = $this->get_id();
|
||||
$default = str_replace('-', ' ', $default);
|
||||
|
||||
return ucwords($default);
|
||||
|
||||
} // end if;
|
||||
|
||||
return $registered_gateway['title'];
|
||||
|
||||
} // end get_public_title;
|
||||
|
||||
/**
|
||||
* Adds additional hooks.
|
||||
*
|
||||
* Useful to add additional hooks and filters
|
||||
* that do not need to be set during initialization.
|
||||
*
|
||||
* As this runs later on the wp lifecycle, user apis
|
||||
* and other goodies are available.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function hooks() {} // end hooks;
|
||||
|
||||
/**
|
||||
* Run preparations before checkout processing.
|
||||
*
|
||||
* This runs during the checkout form validation
|
||||
* and it is a great chance to do preflight stuff
|
||||
* if the gateway requires it.
|
||||
*
|
||||
* If you return an array here, Ultimo
|
||||
* will append the key => value of that array
|
||||
* as hidden fields to the checkout field,
|
||||
* and those get submitted with the rest of the form.
|
||||
*
|
||||
* As an example, this is how we create payment
|
||||
* intents for Stripe to make the experience more
|
||||
* streamlined.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void|array
|
||||
*/
|
||||
public function run_preflight() {} // end run_preflight;
|
||||
|
||||
/**
|
||||
* Registers and Enqueue scripts.
|
||||
*
|
||||
* This method gets called during the rendering
|
||||
* of the checkout page, so you can use it
|
||||
* to register and enqueue custom scripts
|
||||
* and styles.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function register_scripts() {} // end register_scripts;
|
||||
|
||||
/**
|
||||
* Gives gateways a chance to run things before backwards compatible webhooks are run.
|
||||
*
|
||||
* @since 2.0.7
|
||||
* @return void
|
||||
*/
|
||||
public function before_backwards_compatible_webhook() {} // end before_backwards_compatible_webhook;
|
||||
|
||||
/**
|
||||
* Handles webhook calls.
|
||||
*
|
||||
* This is the endpoint that gets called
|
||||
* when a webhook message is posted to the gateway
|
||||
* endpoint.
|
||||
*
|
||||
* You should process the message, if necessary,
|
||||
* and take the appropriate actions, such as
|
||||
* renewing memberships, marking payments as complete, etc.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function process_webhooks() {} // end process_webhooks;
|
||||
|
||||
/**
|
||||
* Handles confirmation windows and extra processing.
|
||||
*
|
||||
* This endpoint gets called when we get to the
|
||||
* /confirm/ URL on the registration page.
|
||||
*
|
||||
* For example, PayPal needs a confirmation screen.
|
||||
* And it uses this method to handle that.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function process_confirmation() {} // end process_confirmation;
|
||||
|
||||
/**
|
||||
* Returns the external link to view the payment on the payment gateway.
|
||||
*
|
||||
* Return an empty string to hide the link element.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $gateway_payment_id The gateway payment id.
|
||||
* @return void|string
|
||||
*/
|
||||
public function get_payment_url_on_gateway($gateway_payment_id) {} // end get_payment_url_on_gateway;
|
||||
|
||||
/**
|
||||
* Returns the external link to view the membership on the membership gateway.
|
||||
*
|
||||
* Return an empty string to hide the link element.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $gateway_subscription_id The gateway subscription id.
|
||||
* @return void|string.
|
||||
*/
|
||||
public function get_subscription_url_on_gateway($gateway_subscription_id) {} // end get_subscription_url_on_gateway;
|
||||
|
||||
/**
|
||||
* Returns the external link to view the membership on the membership gateway.
|
||||
*
|
||||
* Return an empty string to hide the link element.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $gateway_customer_id The gateway customer id.
|
||||
* @return void|string.
|
||||
*/
|
||||
public function get_customer_url_on_gateway($gateway_customer_id) {} // end get_customer_url_on_gateway;
|
||||
|
||||
/**
|
||||
* Reflects membership changes on the gateway.
|
||||
*
|
||||
* By default, this method will process tha cancellation of current gateway subscription
|
||||
*
|
||||
* @since 2.1.3
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership object.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer object.
|
||||
* @return bool|\WP_Error true if it's all done or error object if something went wrong.
|
||||
*/
|
||||
public function process_membership_update(&$membership, $customer) {
|
||||
|
||||
$original = $membership->_get_original();
|
||||
|
||||
$has_amount_change = (float) $membership->get_amount() !== (float) wu_get_isset($original, 'amount');
|
||||
$has_duration_change = $membership->get_duration() !== absint(wu_get_isset($original, 'duration')) || $membership->get_duration_unit() !== wu_get_isset($original, 'duration_unit');
|
||||
|
||||
// If there is no change in amount or duration, we don't do anything here.
|
||||
if (!$has_amount_change && !$has_duration_change) {
|
||||
|
||||
return true;
|
||||
|
||||
} // end if;
|
||||
|
||||
// Cancel the current gateway integration.
|
||||
$cancellation = $this->process_cancellation($membership, $customer);
|
||||
|
||||
if (is_wp_error($cancellation)) {
|
||||
|
||||
return $cancellation;
|
||||
|
||||
} // end if;
|
||||
|
||||
// Reset the gateway in the membership object.
|
||||
$membership->set_gateway('');
|
||||
$membership->set_gateway_customer_id('');
|
||||
$membership->set_gateway_subscription_id('');
|
||||
$membership->set_auto_renew(false);
|
||||
|
||||
return true;
|
||||
|
||||
} // end process_membership_update;
|
||||
|
||||
/*
|
||||
* Helper methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a message about what will happen to the gateway subscription
|
||||
* when the membership is updated.
|
||||
*
|
||||
* @since 2.1.2
|
||||
*
|
||||
* @param bool $to_customer Whether the message is being shown to the customer or not.
|
||||
* @return string
|
||||
*/
|
||||
public function get_amount_update_message($to_customer = false) {
|
||||
|
||||
if (!$this->supports_amount_update()) {
|
||||
|
||||
$message = __('The current payment integration will be cancelled.', 'wp-ultimo');
|
||||
|
||||
if ($to_customer) {
|
||||
|
||||
$message .= ' ' . __('You will receive a new invoice on the next billing cycle.', 'wp-ultimo');
|
||||
|
||||
} else {
|
||||
|
||||
$message .= ' ' . __('The customer will receive a new invoice on the next billing cycle.', 'wp-ultimo');
|
||||
|
||||
} // end if;
|
||||
|
||||
return $message;
|
||||
|
||||
} // end if;
|
||||
|
||||
return __('The current payment integration will be updated.', 'wp-ultimo');
|
||||
|
||||
} // end get_amount_update_message;
|
||||
|
||||
/**
|
||||
* Get the return URL.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_return_url() {
|
||||
|
||||
if (empty($this->return_url)) {
|
||||
|
||||
$this->return_url = wu_get_current_url();
|
||||
|
||||
} // end if;
|
||||
|
||||
$return_url = is_admin() ? admin_url('admin.php') : $this->return_url;
|
||||
|
||||
$return_url = remove_query_arg(array(
|
||||
'wu-confirm',
|
||||
'token',
|
||||
'PayerID',
|
||||
), $return_url);
|
||||
|
||||
if (is_admin()) {
|
||||
|
||||
$args = array('page' => 'account');
|
||||
|
||||
if ($this->order) {
|
||||
|
||||
$args['updated'] = $this->order->get_cart_type();
|
||||
|
||||
} // end if;
|
||||
|
||||
$return_url = add_query_arg($args, $return_url);
|
||||
|
||||
} else {
|
||||
|
||||
$return_url = add_query_arg(array(
|
||||
'payment' => $this->payment->get_hash(),
|
||||
'status' => 'done',
|
||||
), $return_url);
|
||||
|
||||
} // end if;
|
||||
|
||||
/**
|
||||
* Allow developers to change the gateway return URL used after checkout processes.
|
||||
*
|
||||
* @since 2.0.20
|
||||
*
|
||||
* @param string $return_url the URL to redirect after process.
|
||||
* @param self $gateway the gateway instance.
|
||||
* @param \WP_Ultimo\Models\Payment $payment the WP Ultimo payment instance.
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart the current WP Ultimo cart order.
|
||||
* @return string
|
||||
*/
|
||||
return apply_filters('wu_return_url', $return_url, $this, $this->payment, $this->order);
|
||||
|
||||
} // end get_return_url;
|
||||
|
||||
/**
|
||||
* Get the cancel URL.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_cancel_url() {
|
||||
|
||||
if (empty($this->cancel_url)) {
|
||||
|
||||
$this->cancel_url = wu_get_current_url();
|
||||
|
||||
} // end if;
|
||||
|
||||
return add_query_arg(array(
|
||||
'payment' => $this->payment->get_hash(),
|
||||
), $this->cancel_url);
|
||||
|
||||
} // end get_cancel_url;
|
||||
|
||||
/**
|
||||
* Get the confirm URL.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_confirm_url() {
|
||||
|
||||
if (empty($this->confirm_url)) {
|
||||
|
||||
$this->confirm_url = wu_get_current_url();
|
||||
|
||||
} // end if;
|
||||
|
||||
return add_query_arg(array(
|
||||
'payment' => $this->payment->get_hash(),
|
||||
'wu-confirm' => $this->get_id(),
|
||||
), $this->confirm_url);
|
||||
|
||||
} // end get_confirm_url;
|
||||
|
||||
/**
|
||||
* Returns the webhook url for the listener of this gateway events.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_webhook_listener_url() {
|
||||
|
||||
$site_url = defined('WU_GATEWAY_LISTENER_URL') ? WU_GATEWAY_LISTENER_URL : get_site_url(wu_get_main_site_id(), '/');
|
||||
|
||||
return add_query_arg('wu-gateway', $this->get_id(), $site_url);
|
||||
|
||||
} // end get_webhook_listener_url;
|
||||
|
||||
/**
|
||||
* Set the payment.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment.
|
||||
* @return void
|
||||
*/
|
||||
public function set_payment($payment) {
|
||||
|
||||
$this->payment = $payment;
|
||||
|
||||
} // end set_payment;
|
||||
|
||||
/**
|
||||
* Set the membership.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @return void
|
||||
*/
|
||||
public function set_membership($membership) {
|
||||
|
||||
$this->membership = $membership;
|
||||
|
||||
} // end set_membership;
|
||||
|
||||
/**
|
||||
* Set the customer.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @param \WP_Ultimo\Models\Payment $customer The customer.
|
||||
* @return void
|
||||
*/
|
||||
public function set_customer($customer) {
|
||||
|
||||
$this->customer = $customer;
|
||||
|
||||
} // end set_customer;
|
||||
|
||||
/**
|
||||
* Triggers the events related to processing a payment.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment model.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership object.
|
||||
* @return void
|
||||
*/
|
||||
public function trigger_payment_processed($payment, $membership = null) {
|
||||
|
||||
if ($membership === null) {
|
||||
|
||||
$membership = $payment->get_membership();
|
||||
|
||||
} // end if;
|
||||
|
||||
do_action('wu_gateway_payment_processed', $payment, $membership, $this);
|
||||
|
||||
} // end trigger_payment_processed;
|
||||
|
||||
/**
|
||||
* Save a cart for a future swap.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart The cart to swap to.
|
||||
* @return string
|
||||
*/
|
||||
public function save_swap($cart) {
|
||||
|
||||
$swap_id = uniqid('wu_swap_');
|
||||
|
||||
set_site_transient($swap_id, $cart, DAY_IN_SECONDS);
|
||||
|
||||
return $swap_id;
|
||||
|
||||
} // end save_swap;
|
||||
|
||||
/**
|
||||
* Gets a saved swap based on the id.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $swap_id The saved swap id.
|
||||
* @return \WP_Ultimo\Checkout\Cart|false
|
||||
*/
|
||||
public function get_saved_swap($swap_id) {
|
||||
|
||||
return get_site_transient($swap_id);
|
||||
|
||||
} // end get_saved_swap;
|
||||
|
||||
/**
|
||||
* Get the compatibility ids for this gateway.
|
||||
*
|
||||
* @since 2.0.7
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_ids() {
|
||||
|
||||
$all_ids = array_merge(array($this->get_id()), (array) $this->other_ids);
|
||||
|
||||
return array_unique($all_ids);
|
||||
|
||||
} // end get_all_ids;
|
||||
|
||||
/**
|
||||
* Returns the backwards compatibility id of the gateway from v1.
|
||||
*
|
||||
* @since 2.0.4
|
||||
* @return string
|
||||
*/
|
||||
public function get_backwards_compatibility_v1_id() {
|
||||
|
||||
return $this->backwards_compatibility_v1_id;
|
||||
|
||||
} // end get_backwards_compatibility_v1_id;
|
||||
|
||||
} // end class Base_Gateway;
|
3380
inc/gateways/class-base-stripe-gateway.php
Normal file
3380
inc/gateways/class-base-stripe-gateway.php
Normal file
File diff suppressed because it is too large
Load Diff
149
inc/gateways/class-free-gateway.php
Normal file
149
inc/gateways/class-free-gateway.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Gateway.
|
||||
*
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Managers/Site_Manager
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
use WP_Ultimo\Gateways\Base_Gateway;
|
||||
use \WP_Ultimo\Database\Memberships\Membership_Status;
|
||||
use \WP_Ultimo\Database\Payments\Payment_Status;
|
||||
|
||||
// Exit if accessed directly
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Free_Gateway extends Base_Gateway {
|
||||
|
||||
/**
|
||||
* Holds the ID of a given gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $id = 'free';
|
||||
|
||||
/**
|
||||
* Process a checkout.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a new checkout and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* set up recurring payment profiles, etc.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
|
||||
* @param string $type The checkout type. Can be 'new', 'retry', 'upgrade', 'downgrade', 'addon'.
|
||||
* @return void
|
||||
*/
|
||||
public function process_checkout($payment, $membership, $customer, $cart, $type) {
|
||||
|
||||
$membership_status = $membership->get_status();
|
||||
|
||||
// Set current gateway
|
||||
$membership->set_gateway_subscription_id('');
|
||||
$membership->set_gateway_customer_id('');
|
||||
$membership->set_gateway($this->get_id());
|
||||
$membership->set_auto_renew(false);
|
||||
|
||||
if ($type === 'downgrade' && ($membership_status === Membership_Status::ACTIVE || $membership_status === Membership_Status::TRIALING)) {
|
||||
/*
|
||||
* When downgrading, we need to schedule a swap for the end of the
|
||||
* current expiration date.
|
||||
*/
|
||||
$membership->schedule_swap($cart);
|
||||
|
||||
/*
|
||||
* Saves the membership with the changes.
|
||||
*/
|
||||
$status = $membership->save();
|
||||
|
||||
return;
|
||||
|
||||
} elseif ($type === 'upgrade' || $type === 'downgrade' || $type === 'addon') {
|
||||
/*
|
||||
* A change to another free membership
|
||||
* is a upgrade and if membership is not
|
||||
* active or trialling, we swap now.
|
||||
*/
|
||||
$membership->swap($cart);
|
||||
|
||||
$membership->set_status(Membership_Status::ACTIVE);
|
||||
|
||||
} // end if;
|
||||
|
||||
$membership->save();
|
||||
|
||||
$payment->set_status(Payment_Status::COMPLETED);
|
||||
|
||||
$payment->save();
|
||||
|
||||
} // end process_checkout;
|
||||
|
||||
/**
|
||||
* Process a cancellation.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a membership cancellation and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* to cancel a recurring profile, etc.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return void
|
||||
*/
|
||||
public function process_cancellation($membership, $customer) {} // end process_cancellation;
|
||||
|
||||
/**
|
||||
* Process a refund.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a refund and process it.
|
||||
*
|
||||
* Here's where you will want to send
|
||||
* API calls to the gateway server,
|
||||
* to issue a refund.
|
||||
*
|
||||
* This method is required and MUST
|
||||
* be implemented by gateways extending the
|
||||
* Base_Gateway class.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param float $amount The amount to refund.
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return void
|
||||
*/
|
||||
public function process_refund($amount, $payment, $membership, $customer) {} // end process_refund;
|
||||
|
||||
} // end class Free_Gateway;
|
17
inc/gateways/class-ignorable-exception.php
Normal file
17
inc/gateways/class-ignorable-exception.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Ignorable Exception.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Gateways
|
||||
* @since 2.0.7
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
/**
|
||||
* This exception will be caught but will not trigger a 500.
|
||||
*
|
||||
* @since 2.0.7
|
||||
*/
|
||||
class Ignorable_Exception extends \Exception {} // end class Ignorable_Exception;
|
433
inc/gateways/class-manual-gateway.php
Normal file
433
inc/gateways/class-manual-gateway.php
Normal file
@ -0,0 +1,433 @@
|
||||
<?php
|
||||
/**
|
||||
* Manual Gateway.
|
||||
*
|
||||
* This gateway is the simplest one possible.
|
||||
* It doesn't do anything with the payments,
|
||||
* as they need to be manually approved by the super admin
|
||||
* but it serves as a good example of how
|
||||
* to implement a custom gateway for WP Ultimo.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Gateways
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
use \WP_Ultimo\Gateways\Base_Gateway;
|
||||
use \WP_Ultimo\Database\Memberships\Membership_Status;
|
||||
use \WP_Ultimo\Database\Payments\Payment_Status;
|
||||
|
||||
// Exit if accessed directly
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Manual Payments Gateway
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Manual_Gateway extends Base_Gateway {
|
||||
|
||||
/**
|
||||
* Holds the ID of a given gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $id = 'manual';
|
||||
|
||||
/**
|
||||
* Adds the necessary hooks for the manual gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function hooks() {
|
||||
/*
|
||||
* Adds payment instructions to the thank you page.
|
||||
*/
|
||||
add_action('wu_thank_you_before_info_blocks', array($this, 'add_payment_instructions_block'), 10, 3);
|
||||
|
||||
} // end hooks;
|
||||
|
||||
/**
|
||||
* Declares support to recurring payments.
|
||||
*
|
||||
* Manual payments need to be manually paid,
|
||||
* so we return false here.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return false
|
||||
*/
|
||||
public function supports_recurring(): bool {
|
||||
|
||||
return false;
|
||||
|
||||
} // end supports_recurring;
|
||||
|
||||
/**
|
||||
* Declares support to free trials
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return false
|
||||
*/
|
||||
public function supports_free_trials(): bool {
|
||||
|
||||
return false;
|
||||
|
||||
} // end supports_free_trials;
|
||||
|
||||
/**
|
||||
* Adds the Stripe Gateway settings to the settings screen.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function settings() {
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'manual_header', array(
|
||||
'title' => __('Manual', 'wp-ultimo'),
|
||||
'desc' => __('Use the settings section below to configure the manual payment method. This method allows your customers to manually pay for their memberships, but those payments require manual confirmation on your part.', 'wp-ultimo'),
|
||||
'type' => 'header',
|
||||
'show_as_submenu' => true,
|
||||
'require' => array(
|
||||
'active_gateways' => 'manual',
|
||||
),
|
||||
));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'manual_payment_instructions', array(
|
||||
'title' => __('Payment Instructions', 'wp-ultimo'),
|
||||
'desc' => __('This instructions will be shown to the customer on the thank you page, as well as be sent via email.', 'wp-ultimo'),
|
||||
'type' => 'wp_editor',
|
||||
'allow_html' => true,
|
||||
'default' => __('Payment instructions here.', 'wp-ultimo'),
|
||||
'require' => array(
|
||||
'active_gateways' => 'manual',
|
||||
),
|
||||
));
|
||||
|
||||
} // end settings;
|
||||
|
||||
/**
|
||||
* Reflects membership changes on the gateway.
|
||||
*
|
||||
* By default, this method will process tha cancellation of current gateway subscription
|
||||
*
|
||||
* @since 2.1.3
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership object.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer object.
|
||||
* @return bool|\WP_Error true if it's all done or error object if something went wrong.
|
||||
*/
|
||||
public function process_membership_update(&$membership, $customer) {
|
||||
|
||||
return true;
|
||||
|
||||
} // end process_membership_update;
|
||||
|
||||
/**
|
||||
* Returns a message about what will happen to the gateway subscription
|
||||
* when the membership is updated.
|
||||
*
|
||||
* @since 2.1.2
|
||||
*
|
||||
* @param bool $to_customer Whether the message is being shown to the customer or not.
|
||||
* @return string
|
||||
*/
|
||||
public function get_amount_update_message($to_customer = false) {
|
||||
|
||||
if ($to_customer) {
|
||||
|
||||
$message = __('You will receive a updated invoice on the next billing cycle.', 'wp-ultimo');
|
||||
|
||||
} else {
|
||||
|
||||
$message = __('The customer will receive a updated invoice on the next billing cycle.', 'wp-ultimo');
|
||||
|
||||
} // end if;
|
||||
|
||||
return $message;
|
||||
|
||||
} // end get_amount_update_message;
|
||||
/**
|
||||
* Process a checkout.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a new checkout and process it.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
|
||||
* @param string $type The checkout type. Can be 'new', 'retry', 'upgrade', 'downgrade', 'addon'.
|
||||
*
|
||||
* @throws \Exception When saving a membership fails.
|
||||
*/
|
||||
public function process_checkout($payment, $membership, $customer, $cart, $type): bool {
|
||||
/*
|
||||
* Let's lay out the payment process logic in here.
|
||||
* Basically, it all depends on the cart object, cart type, the membership that was created
|
||||
* and the pending payment that was created.
|
||||
*
|
||||
* With that info, we can decide what to do with a given cart
|
||||
* and process that with the gateway accordingly.
|
||||
*
|
||||
* For the manual payments gateway, the payment keeps it's pending status
|
||||
* So we don't need to do anything on the cart_type = new.
|
||||
*
|
||||
* We have 6 different cart types:
|
||||
*
|
||||
* - display: This type should not be processed at all. These carts are
|
||||
* created to be able to display itemized list tables in pages when that's
|
||||
* required.
|
||||
* - new: This is the first time registration of a new membership. In cases like this,
|
||||
* we should handle the first payment with the additional fees, if they exist,
|
||||
* as well as setup recurring profiles, if necessary.
|
||||
* - renewal: This type of cart handles a membership renewal. In here, you should also check
|
||||
* for an auto_renew flag, to be able to set up a auto-recurring profile, if needed.
|
||||
* - upgrade: This cart changes the membership plan. It is the same for downgrade.
|
||||
* - downgrade: @see upgrade.
|
||||
* - retry: This cart is created when a customer tries to settle a pending or failed payment.
|
||||
* - addon: Contains only services or packages that should be added to the membership in question.
|
||||
*/
|
||||
$status = true;
|
||||
|
||||
/*
|
||||
* We'll organize the code into if-else statements
|
||||
* to make things more spaced and allow for
|
||||
* greater readability, but there's nothing
|
||||
* preventing you from using a simple switch statement
|
||||
* to deal with the different types.
|
||||
*
|
||||
* This gateway relies on the super admin manually
|
||||
* approving a payment, so there isn't any logic to take care in here.
|
||||
*
|
||||
* The exception are memberships that contain
|
||||
* free trials.
|
||||
*
|
||||
* This will not be the case for other gateways.
|
||||
* See the stripe implementation of this method more
|
||||
* a better example.
|
||||
*
|
||||
* If you wish to stop the process at any point
|
||||
* due to some error, API failure or such,
|
||||
* simply throw a exception and WP Ultimo will
|
||||
* catch it and rollback any changes.
|
||||
*/
|
||||
if ($type === 'new') {
|
||||
|
||||
// Your logic here.
|
||||
|
||||
} elseif ($type === 'renewal') {
|
||||
|
||||
// Your logic here.
|
||||
|
||||
} elseif ($type === 'downgrade') {
|
||||
/*
|
||||
* When downgrading, we need to schedule a swap for the end of the
|
||||
* current expiration date.
|
||||
*/
|
||||
$membership->schedule_swap($cart);
|
||||
|
||||
/*
|
||||
* Mark the membership as pending, as we need to
|
||||
* wait for the payment confirmation.
|
||||
*/
|
||||
$membership->set_status(Membership_Status::ON_HOLD);
|
||||
|
||||
/*
|
||||
* Saves the membership with the changes.
|
||||
*/
|
||||
$status = $membership->save();
|
||||
|
||||
} elseif ($type === 'upgrade' || $type === 'addon') {
|
||||
/*
|
||||
* After everything is said and done,
|
||||
* we need to swap the membership to the new products
|
||||
* (plans and addons), and save it.
|
||||
*
|
||||
* The membership swap method takes in a Cart object
|
||||
* and handled all the changes we need to make to the
|
||||
* membership.
|
||||
*
|
||||
* It updates the products, the recurring status,
|
||||
* the initial and recurring amounts, etc.
|
||||
*
|
||||
* It doesn't save the membership, though, so
|
||||
* you'll have to do that manually (example below).
|
||||
*/
|
||||
$membership->swap($cart);
|
||||
|
||||
/*
|
||||
* Mark the membership as pending, as we need to
|
||||
* wait for the payment confirmation.
|
||||
*/
|
||||
$membership->set_status(Membership_Status::ON_HOLD);
|
||||
|
||||
/*
|
||||
* Saves the membership with the changes.
|
||||
*/
|
||||
$status = $membership->save();
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* We want to check the status
|
||||
* for a possible wp_error.
|
||||
*
|
||||
* If that happens, we need to throw an exception
|
||||
* WP Ultimo will capture that exception and
|
||||
* rollback database changes for us,
|
||||
* to avoid problems with data integrity.
|
||||
*
|
||||
* That means that if you throw an exception in here,
|
||||
* every change made to memberships, payments and such
|
||||
* will be undone, including the swap above.
|
||||
*/
|
||||
if (is_wp_error($status)) {
|
||||
|
||||
throw new \Exception($status->get_error_message(), $status->get_error_code());
|
||||
|
||||
} // end if;
|
||||
|
||||
// In case of trials with payment method
|
||||
if ($payment->get_total() === 0.00) {
|
||||
|
||||
$payment->set_status(Payment_Status::COMPLETED);
|
||||
|
||||
$payment->save();
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* You don't need to return anything,
|
||||
* but if you return false from this method,
|
||||
* WP Ultimo will assume that you want to handle redirection
|
||||
* and such by yourself.
|
||||
*
|
||||
* This can be useful for some gateways that require
|
||||
* extra redirects.
|
||||
*/
|
||||
return true;
|
||||
|
||||
} // end process_checkout;
|
||||
|
||||
/**
|
||||
* Process a cancellation.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a membership cancellation and process it.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return void|bool
|
||||
*/
|
||||
public function process_cancellation($membership, $customer) {} // end process_cancellation;
|
||||
|
||||
/**
|
||||
* Process a checkout.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a refund and process it.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param float $amount The amount to refund.
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @return void|bool
|
||||
*/
|
||||
public function process_refund($amount, $payment, $membership, $customer) {
|
||||
|
||||
$status = $payment->refund($amount);
|
||||
|
||||
if (is_wp_error($status)) {
|
||||
|
||||
throw new \Exception($status->get_error_code(), $status->get_error_message());
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end process_refund;
|
||||
/**
|
||||
* Adds additional fields to the checkout form for a particular gateway.
|
||||
*
|
||||
* In this method, you can either return an array of fields (that we will display
|
||||
* using our form display methods) or you can return plain HTML in a string,
|
||||
* which will get outputted to the gateway section of the checkout.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return mixed[]|string
|
||||
*/
|
||||
public function fields() {
|
||||
|
||||
$message = __('After you finish signing up, we will send you an email with instructions to finalize the payment. Your account will be pending until the payment is finalized and confirmed.', 'wp-ultimo');
|
||||
|
||||
return sprintf('<p v-if="!order.has_trial" class="wu-p-4 wu-bg-yellow-200">%s</p>', $message);
|
||||
|
||||
} // end fields;
|
||||
|
||||
/**
|
||||
* Adds the payment instruction block.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The current payment.
|
||||
* @param \WP_Ultimo\Models\Membership $membership the current membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer the current customer.
|
||||
* @return void
|
||||
*/
|
||||
public function add_payment_instructions_block($payment, $membership, $customer) {
|
||||
|
||||
if ($payment->get_gateway() !== $this->id) {
|
||||
|
||||
return;
|
||||
|
||||
} // end if;
|
||||
|
||||
// phpcs:disable
|
||||
|
||||
if ($payment->get_total() > 0 && $payment->get_status() === 'pending') : ?>
|
||||
|
||||
<!-- Instructions for Payment -->
|
||||
<div id="wu-thank-you-instructions-for-payment">
|
||||
|
||||
<!-- Title Element -->
|
||||
<div class="wu-element-header wu-p-4 wu-flex wu-items-center <?php echo wu_env_picker('', 'wu-bg-gray-100'); ?>">
|
||||
|
||||
<h4 class="wu-m-0 <?php echo wu_env_picker('', 'wu-widget-title'); ?>">
|
||||
|
||||
<?php _e('Instructions for Payment', 'wp-ultimo'); ?>
|
||||
|
||||
</h4>
|
||||
|
||||
</div>
|
||||
<!-- Title Element - End -->
|
||||
|
||||
<!-- Body Content -->
|
||||
<div class="wu-thank-you-instructions-for-payment wu-px-4 wu-mb-4">
|
||||
|
||||
<div class="wu-bg-gray-100 wu-rounded wu-p-4">
|
||||
|
||||
<?php echo do_shortcode(wu_get_setting('manual_payment_instructions')); ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Body Content - End -->
|
||||
|
||||
</div>
|
||||
<!-- Instructions for Payment - End -->
|
||||
|
||||
<?php endif;
|
||||
|
||||
// phpcs:enable
|
||||
|
||||
} // end add_payment_instructions_block;
|
||||
|
||||
} // end class Manual_Gateway;
|
1768
inc/gateways/class-paypal-gateway.php
Normal file
1768
inc/gateways/class-paypal-gateway.php
Normal file
File diff suppressed because it is too large
Load Diff
494
inc/gateways/class-stripe-checkout-gateway.php
Normal file
494
inc/gateways/class-stripe-checkout-gateway.php
Normal file
@ -0,0 +1,494 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Gateway.
|
||||
*
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Managers/Site_Manager
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
use \WP_Ultimo\Gateways\Base_Stripe_Gateway;
|
||||
use \WP_Ultimo\Dependencies\Stripe;
|
||||
use \WP_Ultimo\Checkout\Cart;
|
||||
|
||||
// Exit if accessed directly
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Stripe_Checkout_Gateway extends Base_Stripe_Gateway {
|
||||
|
||||
/**
|
||||
* Holds the ID of a given gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $id = 'stripe-checkout';
|
||||
|
||||
/**
|
||||
* Adds the Stripe Gateway settings to the settings screen.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function settings() {
|
||||
|
||||
$error_message_wrap = '<span class="wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-mt-3 wu-mb-0 wu-block wu-text-xs">%s</span>';
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_header', array(
|
||||
'title' => __('Stripe Checkout', 'wp-ultimo'),
|
||||
'desc' => __('Use the settings section below to configure Stripe Checkout as a payment method.', 'wp-ultimo'),
|
||||
'type' => 'header',
|
||||
'show_as_submenu' => true,
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
),
|
||||
));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_public_title', array(
|
||||
'title' => __('Stripe Public Name', 'wp-ultimo'),
|
||||
'tooltip' => __('The name to display on the payment method selection field. By default, "Credit Card" is used.', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => __('Credit Card', 'wp-ultimo'),
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
),
|
||||
));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_sandbox_mode', array(
|
||||
'title' => __('Stripe Checkout Sandbox Mode', 'wp-ultimo'),
|
||||
'desc' => __('Toggle this to put Stripe on sandbox mode. This is useful for testing and making sure Stripe is correctly setup to handle your payments.', 'wp-ultimo'),
|
||||
'type' => 'toggle',
|
||||
'default' => 1,
|
||||
'html_attr' => array(
|
||||
'v-model' => 'stripe_checkout_sandbox_mode',
|
||||
),
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
),
|
||||
));
|
||||
|
||||
$pk_test_status = wu_get_setting('stripe_checkout_test_pk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_test_pk_key', array(
|
||||
'title' => __('Stripe Test Publishable Key', 'wp-ultimo'),
|
||||
'desc' => !empty($pk_test_status) ? sprintf($error_message_wrap, $pk_test_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the TEST keys, not the live ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('pk_test_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
'stripe_checkout_sandbox_mode' => 1,
|
||||
),
|
||||
));
|
||||
|
||||
$sk_test_status = wu_get_setting('stripe_checkout_test_sk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_test_sk_key', array(
|
||||
'title' => __('Stripe Test Secret Key', 'wp-ultimo'),
|
||||
'desc' => !empty($sk_test_status) ? sprintf($error_message_wrap, $sk_test_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the TEST keys, not the live ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('sk_test_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
'stripe_checkout_sandbox_mode' => 1,
|
||||
),
|
||||
));
|
||||
|
||||
$pk_status = wu_get_setting('stripe_checkout_live_pk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_live_pk_key', array(
|
||||
'title' => __('Stripe Live Publishable Key', 'wp-ultimo'),
|
||||
'desc' => !empty($pk_status) ? sprintf($error_message_wrap, $pk_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the LIVE keys, not the test ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('pk_live_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
'stripe_checkout_sandbox_mode' => 0,
|
||||
),
|
||||
));
|
||||
|
||||
$sk_status = wu_get_setting('stripe_checkout_live_sk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_live_sk_key', array(
|
||||
'title' => __('Stripe Live Secret Key', 'wp-ultimo'),
|
||||
'desc' => !empty($sk_status) ? sprintf($error_message_wrap, $sk_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the LIVE keys, not the test ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('sk_live_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
'stripe_checkout_sandbox_mode' => 0,
|
||||
),
|
||||
));
|
||||
|
||||
$webhook_message = sprintf('<span class="wu-p-2 wu-bg-blue-100 wu-text-blue-600 wu-rounded wu-mt-3 wu-mb-0 wu-block wu-text-xs">%s</span>', __('Whenever you change your Stripe settings, WP Ultimo will automatically check the webhook URLs on your Stripe account to make sure we get notified about changes in subscriptions and payments.', 'wp-ultimo'));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_checkout_webhook_listener_explanation', array(
|
||||
'title' => __('Webhook Listener URL', 'wp-ultimo'),
|
||||
'desc' => $webhook_message,
|
||||
'tooltip' => __('This is the URL Stripe should send webhook calls to.', 'wp-ultimo'),
|
||||
'type' => 'text-display',
|
||||
'copy' => true,
|
||||
'default' => $this->get_webhook_listener_url(),
|
||||
'wrapper_classes' => '',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe-checkout',
|
||||
),
|
||||
));
|
||||
|
||||
parent::settings();
|
||||
|
||||
} // end settings;
|
||||
|
||||
/**
|
||||
* Run preparations before checkout processing.
|
||||
*
|
||||
* This runs during the checkout form validation
|
||||
* and it is a great chance to do preflight stuff
|
||||
* if the gateway requires it.
|
||||
*
|
||||
* If you return an array here, Ultimo
|
||||
* will append the key => value of that array
|
||||
* as hidden fields to the checkout field,
|
||||
* and those get submitted with the rest of the form.
|
||||
*
|
||||
* As an example, this is how we create payment
|
||||
* intents for Stripe to make the experience more
|
||||
* streamlined.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void|array
|
||||
*/
|
||||
public function run_preflight() {
|
||||
|
||||
/**
|
||||
* Ensure the correct api keys are set
|
||||
*/
|
||||
$this->setup_api_keys();
|
||||
|
||||
/*
|
||||
* Creates or retrieves the Stripe Customer
|
||||
*/
|
||||
$s_customer = $this->get_or_create_customer($this->customer->get_id());
|
||||
|
||||
/*
|
||||
* Stripe Checkout allows for tons of different payment methods.
|
||||
* These include:
|
||||
*
|
||||
* 'card'
|
||||
* 'alipay'
|
||||
* 'ideal'
|
||||
* 'fpx'
|
||||
* 'bacs_debit'
|
||||
* 'bancontact'
|
||||
* 'giropay'
|
||||
* 'p24'
|
||||
* 'eps'
|
||||
* 'sofort'
|
||||
* 'sepa_debit'
|
||||
* 'grabpay'
|
||||
* 'afterpay_clearpay'
|
||||
*
|
||||
* For those to work, you'll need to activate them on your
|
||||
* Stripe account, and you should also be in live mode.
|
||||
*/
|
||||
$allowed_payment_method_types = apply_filters('wu_stripe_checkout_allowed_payment_method_types', array(
|
||||
'card',
|
||||
), $this);
|
||||
|
||||
$metadata = array(
|
||||
'payment_id' => $this->payment->get_id(),
|
||||
'membership_id' => $this->membership->get_id(),
|
||||
'customer_id' => $this->customer->get_id(),
|
||||
);
|
||||
|
||||
$this->membership->set_gateway_customer_id($s_customer->id);
|
||||
$this->membership->set_gateway($this->get_id());
|
||||
|
||||
$this->membership->save();
|
||||
|
||||
/**
|
||||
* Verify the card type
|
||||
*/
|
||||
if ($this->order->get_cart_type() === 'new') {
|
||||
|
||||
$redirect_url = $this->get_return_url();
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Saves cart for later swap.
|
||||
*/
|
||||
$swap_id = $this->save_swap($this->order);
|
||||
|
||||
$redirect_url = add_query_arg('swap', $swap_id, $this->get_confirm_url());
|
||||
|
||||
$metadata['swap_id'] = $swap_id;
|
||||
|
||||
} // end if;
|
||||
|
||||
$subscription_data = array(
|
||||
'payment_method_types' => $allowed_payment_method_types,
|
||||
'success_url' => $redirect_url,
|
||||
'cancel_url' => $this->get_cancel_url(),
|
||||
'billing_address_collection' => 'required',
|
||||
'client_reference_id' => $this->customer->get_id(),
|
||||
'customer' => $s_customer->id,
|
||||
'metadata' => $metadata,
|
||||
);
|
||||
|
||||
if ($this->order->should_auto_renew()) {
|
||||
|
||||
$stripe_cart = $this->build_stripe_cart($this->order);
|
||||
$stripe_non_recurring_cart = $this->build_non_recurring_cart($this->order);
|
||||
|
||||
/*
|
||||
* Adds recurring stuff.
|
||||
*/
|
||||
$subscription_data['subscription_data'] = array(
|
||||
'items' => array_values($stripe_cart),
|
||||
);
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Create non-recurring only cart.
|
||||
*/
|
||||
$stripe_non_recurring_cart = $this->build_non_recurring_cart($this->order, true);
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* Add non-recurring line items
|
||||
*/
|
||||
$subscription_data['line_items'] = $stripe_non_recurring_cart;
|
||||
|
||||
/**
|
||||
* If we have pro-rata credit (in case of an upgrade, for example)
|
||||
* try to create a custom coupon.
|
||||
*/
|
||||
$s_coupon = $this->get_credit_coupon($this->order);
|
||||
|
||||
if ($s_coupon) {
|
||||
|
||||
$subscription_data['discounts'] = array(
|
||||
array('coupon' => $s_coupon),
|
||||
);
|
||||
|
||||
} // end if;
|
||||
|
||||
/**
|
||||
* If its a downgrade, we need to set as a trial,
|
||||
* billing_cycle_anchor isn't supported by Checkout.
|
||||
* (https://stripe.com/docs/api/checkout/sessions/create)
|
||||
*/
|
||||
if ($this->order->get_cart_type() === 'downgrade') {
|
||||
|
||||
$next_charge = $this->order->get_billing_next_charge_date();
|
||||
$next_charge_date = \DateTime::createFromFormat('U', $next_charge);
|
||||
$current_time = new \DateTime();
|
||||
|
||||
if ($current_time < $next_charge_date) {
|
||||
|
||||
// The `trial_end` date has to be at least 2 days in the future.
|
||||
$next_charge = $next_charge_date->diff($current_time)->days > 2 ? $next_charge : strtotime('+2 days');
|
||||
|
||||
$subscription_data['subscription_data']['trial_end'] = $next_charge;
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* Handle trial periods.
|
||||
*/
|
||||
if ($this->order->has_trial() && $this->order->has_recurring()) {
|
||||
|
||||
$subscription_data['subscription_data']['trial_end'] = $this->order->get_billing_start_date();
|
||||
|
||||
} // end if;
|
||||
|
||||
$session = Stripe\Checkout\Session::create($subscription_data);
|
||||
|
||||
// Add the client secret to the JSON success data.
|
||||
return array(
|
||||
'stripe_session_id' => sanitize_text_field($session->id),
|
||||
);
|
||||
|
||||
} // end run_preflight;
|
||||
|
||||
/**
|
||||
* Handles confirmation windows and extra processing.
|
||||
*
|
||||
* This endpoint gets called when we get to the
|
||||
* /confirm/ URL on the registration page.
|
||||
*
|
||||
* For example, PayPal needs a confirmation screen.
|
||||
* And it uses this method to handle that.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function process_confirmation() {
|
||||
|
||||
$saved_swap = $this->get_saved_swap(wu_request('swap'));
|
||||
|
||||
$membership = $this->payment ? $this->payment->get_membership() : wu_get_membership_by_hash(wu_request('membership'));
|
||||
|
||||
if ($saved_swap && $membership) {
|
||||
|
||||
if ($saved_swap->get_cart_type() === 'downgrade') {
|
||||
|
||||
$membership->schedule_swap($saved_swap);
|
||||
|
||||
} else {
|
||||
|
||||
$membership->swap($saved_swap);
|
||||
|
||||
} // end if;
|
||||
|
||||
$membership->save();
|
||||
|
||||
$redirect_url = $this->get_return_url();
|
||||
|
||||
wp_redirect($redirect_url);
|
||||
|
||||
exit;
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end process_confirmation;
|
||||
/**
|
||||
* Add credit card fields.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function fields(): string {
|
||||
|
||||
$message = __('You will be redirected to a checkout to complete the purchase.', 'wp-ultimo');
|
||||
|
||||
return sprintf('<p class="wu-p-4 wu-bg-yellow-200">%s</p>', $message);
|
||||
|
||||
} // end fields;
|
||||
|
||||
/**
|
||||
* Returns the payment methods.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function payment_methods() {
|
||||
|
||||
$fields = array();
|
||||
|
||||
$card_options = $this->get_saved_card_options();
|
||||
|
||||
if ($card_options) {
|
||||
|
||||
foreach ($card_options as $payment_method => $card) {
|
||||
|
||||
$fields = array(
|
||||
"payment_method_{$payment_method}" => array(
|
||||
'type' => 'text-display',
|
||||
'title' => __('Saved Cards', 'wp-ultimo'),
|
||||
'display_value' => $card,
|
||||
)
|
||||
);
|
||||
|
||||
} // end foreach;
|
||||
|
||||
} // end if;
|
||||
|
||||
return $fields;
|
||||
|
||||
} // end payment_methods;
|
||||
|
||||
/**
|
||||
* Get the saved Stripe payment methods for a given user ID.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @throws \Exception, When info is wrong.
|
||||
* @throws \Exception When info is wrong 2.
|
||||
* @return PaymentMethod[]|array
|
||||
*/
|
||||
public function get_user_saved_payment_methods() {
|
||||
|
||||
$customer = wu_get_current_customer();
|
||||
|
||||
if (!$customer) {
|
||||
|
||||
return array();
|
||||
|
||||
} // end if;
|
||||
|
||||
$customer_id = $customer->get_id();
|
||||
|
||||
try {
|
||||
/*
|
||||
* Declare static to prevent multiple calls.
|
||||
*/
|
||||
static $existing_payment_methods;
|
||||
|
||||
if (!is_null($existing_payment_methods) && array_key_exists($customer_id, $existing_payment_methods)) {
|
||||
|
||||
return $existing_payment_methods[$customer_id];
|
||||
|
||||
} // end if;
|
||||
|
||||
$customer_payment_methods = array();
|
||||
|
||||
$stripe_customer_id = \WP_Ultimo\Models\Membership::query(array(
|
||||
'customer_id' => $customer_id,
|
||||
'search' => 'cus_*',
|
||||
'fields' => array('gateway_customer_id'),
|
||||
));
|
||||
|
||||
$stripe_customer_id = current(array_column($stripe_customer_id, 'gateway_customer_id'));
|
||||
|
||||
/**
|
||||
* Ensure the correct api keys are set
|
||||
*/
|
||||
$this->setup_api_keys();
|
||||
|
||||
$payment_methods = Stripe\PaymentMethod::all(array(
|
||||
'customer' => $stripe_customer_id,
|
||||
'type' => 'card'
|
||||
));
|
||||
|
||||
foreach ($payment_methods->data as $payment_method) {
|
||||
|
||||
$customer_payment_methods[$payment_method->id] = $payment_method;
|
||||
|
||||
} // end foreach;
|
||||
|
||||
$existing_payment_methods[$customer_id] = $customer_payment_methods;
|
||||
|
||||
return $existing_payment_methods[$customer_id];
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
|
||||
return array();
|
||||
|
||||
} // end try;
|
||||
|
||||
} // end get_user_saved_payment_methods;
|
||||
|
||||
} // end class Stripe_Checkout_Gateway;
|
825
inc/gateways/class-stripe-gateway.php
Normal file
825
inc/gateways/class-stripe-gateway.php
Normal file
@ -0,0 +1,825 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Gateway.
|
||||
*
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @package WP_Ultimo
|
||||
* @subpackage Managers/Site_Manager
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
namespace WP_Ultimo\Gateways;
|
||||
|
||||
use \WP_Ultimo\Database\Payments\Payment_Status;
|
||||
use \WP_Ultimo\Database\Memberships\Membership_Status;
|
||||
use \WP_Ultimo\Gateways\Base_Stripe_Gateway;
|
||||
use \WP_Ultimo\Dependencies\Stripe;
|
||||
|
||||
// Exit if accessed directly
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Base Gateway class. Should be extended to add new payment gateways.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Stripe_Gateway extends Base_Stripe_Gateway {
|
||||
|
||||
/**
|
||||
* Holds the ID of a given gateway.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $id = 'stripe';
|
||||
|
||||
/**
|
||||
* Adds additional hooks.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function hooks() {
|
||||
|
||||
parent::hooks();
|
||||
|
||||
add_filter('wu_customer_payment_methods', function($fields, $customer): array {
|
||||
|
||||
$this->customer = $customer;
|
||||
|
||||
$extra_fields = $this->payment_methods();
|
||||
|
||||
return array_merge($fields, $extra_fields);
|
||||
|
||||
}, 10, 2);
|
||||
|
||||
} // end hooks;
|
||||
|
||||
/**
|
||||
* Adds the Stripe Gateway settings to the settings screen.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function settings() {
|
||||
|
||||
$error_message_wrap = '<span class="wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-mt-3 wu-mb-0 wu-block wu-text-xs">%s</span>';
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_header', array(
|
||||
'title' => __('Stripe', 'wp-ultimo'),
|
||||
'desc' => __('Use the settings section below to configure Stripe as a payment method.', 'wp-ultimo'),
|
||||
'type' => 'header',
|
||||
'show_as_submenu' => true,
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
),
|
||||
));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_public_title', array(
|
||||
'title' => __('Stripe Public Name', 'wp-ultimo'),
|
||||
'tooltip' => __('The name to display on the payment method selection field. By default, "Credit Card" is used.', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => __('Credit Card', 'wp-ultimo'),
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
),
|
||||
));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_sandbox_mode', array(
|
||||
'title' => __('Stripe Sandbox Mode', 'wp-ultimo'),
|
||||
'desc' => __('Toggle this to put Stripe on sandbox mode. This is useful for testing and making sure Stripe is correctly setup to handle your payments.', 'wp-ultimo'),
|
||||
'type' => 'toggle',
|
||||
'default' => 1,
|
||||
'html_attr' => array(
|
||||
'v-model' => 'stripe_sandbox_mode',
|
||||
),
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
),
|
||||
));
|
||||
|
||||
$pk_test_status = wu_get_setting('stripe_test_pk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_test_pk_key', array(
|
||||
'title' => __('Stripe Test Publishable Key', 'wp-ultimo'),
|
||||
'desc' => !empty($pk_test_status) ? sprintf($error_message_wrap, $pk_test_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the TEST keys, not the live ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('pk_test_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
'stripe_sandbox_mode' => 1,
|
||||
),
|
||||
));
|
||||
|
||||
$sk_test_status = wu_get_setting('stripe_test_sk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_test_sk_key', array(
|
||||
'title' => __('Stripe Test Secret Key', 'wp-ultimo'),
|
||||
'desc' => !empty($sk_test_status) ? sprintf($error_message_wrap, $sk_test_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the TEST keys, not the live ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('sk_test_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
'stripe_sandbox_mode' => 1,
|
||||
),
|
||||
));
|
||||
|
||||
$pk_status = wu_get_setting('stripe_live_pk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_live_pk_key', array(
|
||||
'title' => __('Stripe Live Publishable Key', 'wp-ultimo'),
|
||||
'desc' => !empty($pk_status) ? sprintf($error_message_wrap, $pk_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the LIVE keys, not the test ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('pk_live_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
'stripe_sandbox_mode' => 0,
|
||||
),
|
||||
));
|
||||
|
||||
$sk_status = wu_get_setting('stripe_live_sk_key_status', '');
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_live_sk_key', array(
|
||||
'title' => __('Stripe Live Secret Key', 'wp-ultimo'),
|
||||
'desc' => !empty($sk_status) ? sprintf($error_message_wrap, $sk_status) : '',
|
||||
'tooltip' => __('Make sure you are placing the LIVE keys, not the test ones.', 'wp-ultimo'),
|
||||
'placeholder' => __('sk_live_***********', 'wp-ultimo'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'capability' => 'manage_api_keys',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
'stripe_sandbox_mode' => 0,
|
||||
),
|
||||
));
|
||||
|
||||
$webhook_message = sprintf('<span class="wu-p-2 wu-bg-blue-100 wu-text-blue-600 wu-rounded wu-mt-3 wu-mb-0 wu-block wu-text-xs">%s</span>', __('Whenever you change your Stripe settings, WP Ultimo will automatically check the webhook URLs on your Stripe account to make sure we get notified about changes in subscriptions and payments.', 'wp-ultimo'));
|
||||
|
||||
wu_register_settings_field('payment-gateways', 'stripe_webhook_listener_explanation', array(
|
||||
'title' => __('Webhook Listener URL', 'wp-ultimo'),
|
||||
'desc' => $webhook_message,
|
||||
'tooltip' => __('This is the URL Stripe should send webhook calls to.', 'wp-ultimo'),
|
||||
'type' => 'text-display',
|
||||
'copy' => true,
|
||||
'default' => $this->get_webhook_listener_url(),
|
||||
'wrapper_classes' => '',
|
||||
'require' => array(
|
||||
'active_gateways' => 'stripe',
|
||||
),
|
||||
));
|
||||
|
||||
parent::settings();
|
||||
|
||||
} // end settings;
|
||||
|
||||
/**
|
||||
* Run preparations before checkout processing.
|
||||
*
|
||||
* This runs during the checkout form validation
|
||||
* and it is a great chance to do preflight stuff
|
||||
* if the gateway requires it.
|
||||
*
|
||||
* If you return an array here, Ultimo
|
||||
* will append the key => value of that array
|
||||
* as hidden fields to the checkout field,
|
||||
* and those get submitted with the rest of the form.
|
||||
*
|
||||
* As an example, this is how we create payment
|
||||
* intents for Stripe to make the experience more
|
||||
* streamlined.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return void|array
|
||||
*/
|
||||
public function run_preflight() {
|
||||
/*
|
||||
* This is the stripe preflight code.
|
||||
*
|
||||
* Stripe requires us to create a payment intent
|
||||
* or payment setup to be able to charge customers.
|
||||
*
|
||||
* This is done in order to comply with EU SCA
|
||||
* and other such regulations.
|
||||
*
|
||||
* Before we get started, we need to get our stripe
|
||||
* customer.
|
||||
*/
|
||||
$s_customer = $this->get_or_create_customer($this->customer->get_id(), $this->customer->get_user_id());
|
||||
|
||||
/*
|
||||
* Things can go wrong,
|
||||
* check for WP_Error.
|
||||
*/
|
||||
if (is_wp_error($s_customer)) {
|
||||
|
||||
// translators: %s is the error message.
|
||||
return new \WP_Error($s_customer->get_error_code(), sprintf(__('Error creating Stripe customer: %s', 'wp-ultimo'), $s_customer->get_error_message()));
|
||||
|
||||
} // end if;
|
||||
|
||||
$this->membership->set_gateway_customer_id($s_customer->id);
|
||||
$this->membership->set_gateway($this->get_id());
|
||||
|
||||
$type = $this->order->get_cart_type();
|
||||
|
||||
/*
|
||||
* Lets deal with upgrades, downgrades and addons
|
||||
*
|
||||
* Here, we just need to make sure we process
|
||||
* a membership swap.
|
||||
*/
|
||||
if ($type === 'upgrade' || $type === 'addon') {
|
||||
|
||||
$this->membership->swap($this->order);
|
||||
|
||||
} elseif ($type === 'downgrade') {
|
||||
|
||||
$this->membership->schedule_swap($this->order);
|
||||
|
||||
} // end if;
|
||||
|
||||
$this->membership->save();
|
||||
|
||||
$intent_args = array(
|
||||
'customer' => $s_customer->id,
|
||||
'metadata' => $this->get_customer_metadata(),
|
||||
'description' => $this->order->get_cart_descriptor(),
|
||||
);
|
||||
|
||||
/*
|
||||
* Maybe use an existing payment method.
|
||||
*/
|
||||
if (wu_request('payment_method', 'add-new') !== 'add-new') {
|
||||
|
||||
$intent_args['payment_method'] = sanitize_text_field(wu_request('payment_method'));
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* Let's start with the intent options.
|
||||
*
|
||||
* We'll append the extra options as we go.
|
||||
* This should also be filterable, to allow support
|
||||
* for Stripe Connect in the future.
|
||||
*/
|
||||
$intent_options = array();
|
||||
|
||||
/*
|
||||
* Tries to retrieve an existing intent id,
|
||||
* from the current payment.
|
||||
*/
|
||||
$payment_intent_id = $this->payment->get_meta('stripe_payment_intent_id');
|
||||
$existing_intent = false;
|
||||
|
||||
/**
|
||||
* Ensure the correct api keys are set
|
||||
*/
|
||||
$this->setup_api_keys();
|
||||
|
||||
/*
|
||||
* Tries to retrieve an intent on Stripe.
|
||||
*
|
||||
* If we success, we update it, if we fail,
|
||||
* we try to create a new one.
|
||||
*/
|
||||
try {
|
||||
/*
|
||||
* Payment intents are used when we have an initial
|
||||
* payment attached to the membership. Those start with a pi_
|
||||
* id.
|
||||
*/
|
||||
if (!empty($payment_intent_id) && strncmp((string) $payment_intent_id, 'pi_', strlen('pi_')) === 0) {
|
||||
|
||||
$existing_intent = Stripe\PaymentIntent::retrieve($payment_intent_id);
|
||||
|
||||
/*
|
||||
* Setup intents are created with the intent
|
||||
* of future charging. This is what we use
|
||||
* when we set up a subscription without a
|
||||
* initial amount.
|
||||
*/
|
||||
} elseif (!empty($payment_intent_id) && strncmp((string) $payment_intent_id, 'seti_', strlen('seti_')) === 0) {
|
||||
|
||||
$existing_intent = Stripe\SetupIntent::retrieve($payment_intent_id);
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* We can't use cancelled intents
|
||||
* for obvious reasons...
|
||||
*/
|
||||
if (!empty($existing_intent) && 'canceled' === $existing_intent->status) {
|
||||
|
||||
$existing_intent = false;
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* If we have a initial payment,
|
||||
* we need to take care of that logic.
|
||||
*
|
||||
* If we have a trial, we need to deal with that via a setup intent.
|
||||
*/
|
||||
if ($this->order->get_total() && $this->order->has_trial() === false) {
|
||||
|
||||
$intent_args = wp_parse_args($intent_args, array(
|
||||
'amount' => $this->order->get_total() * wu_stripe_get_currency_multiplier(),
|
||||
'confirmation_method' => 'automatic',
|
||||
'setup_future_usage' => 'off_session',
|
||||
'currency' => strtolower((string) wu_get_setting('currency_symbol', 'USD')),
|
||||
'confirm' => false,
|
||||
));
|
||||
|
||||
/**
|
||||
* Filters the payment intent arguments.
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
* @param array $intent_args The list of intent args.
|
||||
* @param \WP_Ultimo\Gateways\Stripe_Gateway $this.
|
||||
* @return array
|
||||
*/
|
||||
$intent_args = apply_filters('wu_stripe_create_payment_intent_args', $intent_args, $this);
|
||||
|
||||
if (!empty($existing_intent) && 'payment_intent' === $existing_intent->object) {
|
||||
|
||||
$idempotency_args = $intent_args;
|
||||
$idempotency_args['update'] = true;
|
||||
|
||||
/*
|
||||
* Stripe allows us to send a key
|
||||
* together with the arguments to prevent
|
||||
* duplication in payment intents.
|
||||
*
|
||||
* Same parameters = same key,
|
||||
* so Stripe knows what to ignore.
|
||||
*/
|
||||
$intent_options['idempotency_key'] = wu_stripe_generate_idempotency_key($idempotency_args);
|
||||
|
||||
// Unset some options we can't update.
|
||||
$unset_args = array('confirmation_method', 'confirm');
|
||||
|
||||
foreach ($unset_args as $unset_arg) {
|
||||
|
||||
if (isset($intent_args[$unset_arg])) {
|
||||
|
||||
unset($intent_args[$unset_arg]);
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end foreach;
|
||||
|
||||
/*
|
||||
* Tries to update the payment intent.
|
||||
*/
|
||||
$intent = Stripe\PaymentIntent::update($existing_intent->id, $intent_args, $intent_options);
|
||||
|
||||
} else {
|
||||
|
||||
$intent_options['idempotency_key'] = wu_stripe_generate_idempotency_key($intent_args);
|
||||
|
||||
$intent = Stripe\PaymentIntent::create($intent_args, $intent_options);
|
||||
|
||||
} // end if;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Create a setup intent instead.
|
||||
*/
|
||||
$intent_args = wp_parse_args($intent_args, array(
|
||||
'usage' => 'off_session'
|
||||
));
|
||||
|
||||
if (empty($existing_intent) || 'setup_intent' !== $existing_intent->object) {
|
||||
|
||||
$intent_options['idempotency_key'] = wu_stripe_generate_idempotency_key($intent_args);
|
||||
|
||||
/*
|
||||
* Tries to create in Stripe.
|
||||
*/
|
||||
$intent = Stripe\SetupIntent::create($intent_args, $intent_options);
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end if;
|
||||
|
||||
} catch (Stripe\Stripe\Error\Base $e) {
|
||||
|
||||
return $this->get_stripe_error($e);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$error_code = $e->getCode();
|
||||
|
||||
// WP Error did not handle empty error code
|
||||
if (empty($error_code)) {
|
||||
|
||||
if (method_exists($e, 'getHttpStatus')) {
|
||||
|
||||
$error_code = $e->getHttpStatus();
|
||||
|
||||
} else {
|
||||
|
||||
$error_code = 500;
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end if;
|
||||
|
||||
return new \WP_Error($error_code, $e->getMessage());
|
||||
|
||||
} // end try;
|
||||
|
||||
/*
|
||||
* To prevent re-doing all this
|
||||
* work again, we save the intent on
|
||||
* the payment, so we can use it
|
||||
* in cases a retry is needed.
|
||||
*/
|
||||
$this->payment->update_meta('stripe_payment_intent_id', sanitize_text_field($intent->id));
|
||||
|
||||
/*
|
||||
* Anything returned in this array
|
||||
* gets added to the checkout form as hidden
|
||||
* fields just before the form submission.
|
||||
*
|
||||
* Here we pass the data we need from the
|
||||
* recently create intents.
|
||||
*
|
||||
* Using this info, we'll be able to process
|
||||
* the Stripe payment on the next step: process_checkout
|
||||
*/
|
||||
return array(
|
||||
'stripe_client_secret' => sanitize_text_field($intent->client_secret),
|
||||
'stripe_intent_type' => sanitize_text_field($intent->object),
|
||||
);
|
||||
|
||||
} // end run_preflight;
|
||||
|
||||
/**
|
||||
* Process a checkout.
|
||||
*
|
||||
* It takes the data concerning
|
||||
* a new checkout and process it.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param \WP_Ultimo\Models\Payment $payment The payment associated with the checkout.
|
||||
* @param \WP_Ultimo\Models\Membership $membership The membership.
|
||||
* @param \WP_Ultimo\Models\Customer $customer The customer checking out.
|
||||
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
|
||||
* @param string $type The checkout type. Can be 'new', 'retry', 'upgrade', 'downgrade', 'addon'.
|
||||
*
|
||||
* @throws \Exception When a stripe API error is caught.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_checkout($payment, $membership, $customer, $cart, $type) {
|
||||
/*
|
||||
* Here's the general idea
|
||||
* of how the Stripe integration works.
|
||||
*
|
||||
* Despite of the type, we'll need to
|
||||
* cancel an existing subscription to create
|
||||
* a new one.
|
||||
*
|
||||
* Then, after that's all said and done
|
||||
* we can move our attention back to handling
|
||||
* the membership, payment, and customer locally.
|
||||
*
|
||||
* For sanity reasons, stripe variants of data type
|
||||
* such as a Stripe\Customer instance, will be
|
||||
* hold by variables stating with s_ (e.g. s_customer)
|
||||
*
|
||||
* First, we need to check for a valid payment intent.
|
||||
*/
|
||||
$payment_intent_id = $payment->get_meta('stripe_payment_intent_id');
|
||||
|
||||
if (empty($payment_intent_id)) {
|
||||
|
||||
throw new \Exception(__('Missing Stripe payment intent, please try again or contact support if the issue persists.', 'wp-ultimo'), 'missing_stripe_payment_intent');
|
||||
|
||||
} // end if;
|
||||
|
||||
/**
|
||||
* Ensure the correct api keys are set
|
||||
*/
|
||||
$this->setup_api_keys();
|
||||
|
||||
/*
|
||||
* To make our lives easier, let's
|
||||
* set a couple of variables based on the order.
|
||||
*/
|
||||
$should_auto_renew = $cart->should_auto_renew();
|
||||
$is_recurring = $cart->has_recurring();
|
||||
$is_setup_intent = false;
|
||||
|
||||
/*
|
||||
* Get the correct intent
|
||||
* type depending on the intent ID
|
||||
*/
|
||||
if (strncmp((string) $payment_intent_id, 'seti_', strlen('seti_')) === 0) {
|
||||
|
||||
$is_setup_intent = true;
|
||||
|
||||
$payment_intent = Stripe\SetupIntent::retrieve($payment_intent_id);
|
||||
|
||||
} else {
|
||||
|
||||
$payment_intent = Stripe\PaymentIntent::retrieve($payment_intent_id);
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* Retrieves the Stripe Customer
|
||||
* or create a new one!
|
||||
*/
|
||||
$s_customer = $this->get_or_create_customer($customer->get_id(), $customer->get_user_id(), $payment_intent->customer);
|
||||
|
||||
// translators: first is the customer id, then the customer email.
|
||||
$description = sprintf(__('Customer ID: %1$d - User Email: %2$s', 'wp-ultimo'), $customer->get_id(), $customer->get_email_address());
|
||||
|
||||
if (strlen($description) > 350) {
|
||||
|
||||
$description = substr($description, 0, 350);
|
||||
|
||||
} // end if;
|
||||
|
||||
/*
|
||||
* Updates the customer on Stripe
|
||||
* to make sure it always has the most
|
||||
* up-to-date info.
|
||||
*/
|
||||
Stripe\Customer::update($s_customer->id, array(
|
||||
'address' => $this->convert_to_stripe_address($customer->get_billing_address()),
|
||||
'description' => sanitize_text_field($description),
|
||||
'metadata' => array(
|
||||
'email' => $customer->get_email_address(),
|
||||
'user_id' => $customer->get_user_id(),
|
||||
'customer_id' => $customer->get_id(),
|
||||
),
|
||||
));
|
||||
|
||||
/*
|
||||
* Persist payment methods.
|
||||
*
|
||||
* This is not really THAT mission
|
||||
* critical, but it is a nice-to-have
|
||||
* that being said, we'll have it happen
|
||||
* on the sidelines.
|
||||
*/
|
||||
$payment_method = $this->save_payment_method($payment_intent, $s_customer);
|
||||
|
||||
$payment_completed = $is_setup_intent || (!empty($payment_intent->charges->data[0]['id']) && 'succeeded' === $payment_intent->charges->data[0]['status']);
|
||||
|
||||
$subscription = false;
|
||||
|
||||
if ($payment_completed && $should_auto_renew && $is_recurring) {
|
||||
|
||||
$subscription = $this->create_recurring_payment($membership, $cart, $payment_method, $s_customer);
|
||||
|
||||
if (!$subscription) {
|
||||
/**
|
||||
* Another process is already taking care of this (webhook).
|
||||
*/
|
||||
return;
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end if;
|
||||
|
||||
if ($payment_completed) {
|
||||
|
||||
$payment_id = $is_setup_intent ? $payment_intent->id : sanitize_text_field($payment_intent->charges->data[0]['id']);
|
||||
|
||||
$payment->set_status(Payment_Status::COMPLETED);
|
||||
$payment->set_gateway($this->get_id());
|
||||
$payment->set_gateway_payment_id($payment_id);
|
||||
$payment->save();
|
||||
|
||||
$this->trigger_payment_processed($payment, $membership);
|
||||
|
||||
} // end if;
|
||||
|
||||
if ($subscription) {
|
||||
|
||||
$membership->set_gateway($this->get_id());
|
||||
$membership->set_gateway_customer_id($s_customer->id);
|
||||
$membership->set_gateway_subscription_id($subscription->id);
|
||||
$membership->add_to_times_billed(1);
|
||||
$membership->should_auto_renew(true);
|
||||
|
||||
if ($type !== 'downgrade') {
|
||||
|
||||
$membership_status = $cart->has_trial() ? Membership_Status::TRIALING : Membership_Status::ACTIVE;
|
||||
|
||||
$renewal_date = new \DateTime();
|
||||
$renewal_date->setTimestamp($subscription->current_period_end);
|
||||
$renewal_date->setTime(23, 59, 59);
|
||||
|
||||
$stripe_estimated_charge_timestamp = $subscription->current_period_end + (2 * HOUR_IN_SECONDS);
|
||||
|
||||
if ($stripe_estimated_charge_timestamp > $renewal_date->getTimestamp()) {
|
||||
|
||||
$renewal_date->setTimestamp($stripe_estimated_charge_timestamp);
|
||||
|
||||
} // end if;
|
||||
|
||||
$expiration = $renewal_date->format('Y-m-d H:i:s');
|
||||
|
||||
$membership->renew(true, $membership_status, $expiration);
|
||||
|
||||
} else {
|
||||
|
||||
$membership->save();
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end if;
|
||||
|
||||
} // end process_checkout;
|
||||
|
||||
/**
|
||||
* Add credit card fields.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function fields(): string {
|
||||
|
||||
$fields = array();
|
||||
|
||||
$card_options = $this->get_saved_card_options();
|
||||
|
||||
if ($card_options) {
|
||||
|
||||
$card_options['add-new'] = __('Add new card', 'wp-ultimo');
|
||||
|
||||
$fields = array(
|
||||
'payment_method' => array(
|
||||
'type' => 'radio',
|
||||
'title' => __('Saved Payment Methods', 'wp-ultimo'),
|
||||
'value' => wu_request('payment_method'),
|
||||
'options' => $card_options,
|
||||
'html_attr' => array(
|
||||
'v-model' => 'payment_method',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
} // end if;
|
||||
|
||||
$stripe_form = new \WP_Ultimo\UI\Form('billing-address-fields', $fields, array(
|
||||
'views' => 'checkout/fields',
|
||||
'variables' => array(
|
||||
'step' => (object) array(
|
||||
'classes' => '',
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
ob_start();
|
||||
|
||||
$stripe_form->render();
|
||||
|
||||
// phpcs:disable
|
||||
|
||||
?>
|
||||
|
||||
<div v-if="payment_method == 'add-new'">
|
||||
|
||||
<div id="card-element" class="wu-mb-4">
|
||||
<!-- A Stripe Element will be inserted here. -->
|
||||
</div>
|
||||
|
||||
<div class="" id="ideal-bank-element">
|
||||
<!-- A Stripe iDEAL Element will be inserted here. -->
|
||||
</div>
|
||||
|
||||
<!-- Used to display Element errors. -->
|
||||
<div id="card-errors" role="alert"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
|
||||
// phpcs:enable
|
||||
|
||||
return ob_get_clean();
|
||||
|
||||
} // end fields;
|
||||
|
||||
/**
|
||||
* Returns the payment methods.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function payment_methods() {
|
||||
|
||||
$fields = array();
|
||||
|
||||
$card_options = $this->get_saved_card_options();
|
||||
|
||||
if ($card_options) {
|
||||
|
||||
foreach ($card_options as $payment_method => $card) {
|
||||
|
||||
$fields = array(
|
||||
"payment_method_{$payment_method}" => array(
|
||||
'type' => 'text-display',
|
||||
'title' => __('Saved Cards', 'wp-ultimo'),
|
||||
'display_value' => $card,
|
||||
)
|
||||
);
|
||||
|
||||
} // end foreach;
|
||||
|
||||
} // end if;
|
||||
|
||||
return $fields;
|
||||
|
||||
} // end payment_methods;
|
||||
|
||||
/**
|
||||
* Get the saved Stripe payment methods for a given user ID.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @throws \Exception, When info is wrong.
|
||||
* @throws \Exception When info is wrong 2.
|
||||
* @return PaymentMethod[]|array
|
||||
*/
|
||||
public function get_user_saved_payment_methods() {
|
||||
|
||||
$customer = wu_get_current_customer();
|
||||
|
||||
if (!$customer) {
|
||||
|
||||
return array();
|
||||
|
||||
} // end if;
|
||||
|
||||
$customer_id = $customer->get_id();
|
||||
|
||||
try {
|
||||
/*
|
||||
* Declare static to prevent multiple calls.
|
||||
*/
|
||||
static $existing_payment_methods;
|
||||
|
||||
if (!is_null($existing_payment_methods) && array_key_exists($customer_id, $existing_payment_methods)) {
|
||||
|
||||
return $existing_payment_methods[$customer_id];
|
||||
|
||||
} // end if;
|
||||
|
||||
$customer_payment_methods = array();
|
||||
|
||||
$stripe_customer_id = \WP_Ultimo\Models\Membership::query(array(
|
||||
'customer_id' => $customer_id,
|
||||
'search' => 'cus_*',
|
||||
'fields' => array('gateway_customer_id'),
|
||||
));
|
||||
|
||||
$stripe_customer_id = current(array_column($stripe_customer_id, 'gateway_customer_id'));
|
||||
|
||||
/**
|
||||
* Ensure the correct api keys are set
|
||||
*/
|
||||
$this->setup_api_keys();
|
||||
|
||||
$payment_methods = Stripe\PaymentMethod::all(array(
|
||||
'customer' => $stripe_customer_id,
|
||||
'type' => 'card'
|
||||
));
|
||||
|
||||
foreach ($payment_methods->data as $payment_method) {
|
||||
|
||||
$customer_payment_methods[$payment_method->id] = $payment_method;
|
||||
|
||||
} // end foreach;
|
||||
|
||||
$existing_payment_methods[$customer_id] = $customer_payment_methods;
|
||||
|
||||
return $existing_payment_methods[$customer_id];
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
|
||||
return array();
|
||||
|
||||
} // end try;
|
||||
|
||||
} // end get_user_saved_payment_methods;
|
||||
|
||||
} // end class Stripe_Gateway;
|
Reference in New Issue
Block a user