mirror of
https://gh.wpcy.net/https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-05-02 05:02:40 +08:00
437 lines
21 KiB
PHP
437 lines
21 KiB
PHP
<?php
|
|
|
|
/**
|
|
* The subscription module.
|
|
*
|
|
* @package WooCommerce\PayPalCommerce\WcSubscriptions
|
|
*/
|
|
declare (strict_types=1);
|
|
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
|
|
|
|
use Exception;
|
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Log\LoggerInterface;
|
|
use WC_Order;
|
|
use WC_Payment_Tokens;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
|
use WooCommerce\PayPalCommerce\Button\Helper\Context;
|
|
use WooCommerce\PayPalCommerce\SavePaymentMethods\Service\PaymentMethodTokensChecker;
|
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\FreeTrialSubscriptionHelper;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\Service\ChangePaymentMethod;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\VaultV2\ChangePaymentMethodVaultV2;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\VaultV2\DisplaySavedPaymentTokens;
|
|
use WooCommerce\PayPalCommerce\WcSubscriptions\VaultV2\VaultedPayPalEmail;
|
|
/**
|
|
* Class SubscriptionModule
|
|
*/
|
|
class WcSubscriptionsModule implements ServiceModule, ExecutableModule
|
|
{
|
|
use ModuleClassNameIdTrait;
|
|
use TransactionIdHandlingTrait;
|
|
private const VAULT_SUPPORTS_SUBSCRIPTIONS = array('subscriptions', 'subscription_cancellation', 'subscription_suspension', 'subscription_reactivation', 'subscription_amount_changes', 'subscription_date_changes', 'subscription_payment_method_change', 'subscription_payment_method_change_customer', 'subscription_payment_method_change_admin', 'multiple_subscriptions');
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function services(): array
|
|
{
|
|
return require __DIR__ . '/../services.php';
|
|
}
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function run(ContainerInterface $c): bool
|
|
{
|
|
$this->add_gateways_support($c);
|
|
add_action(
|
|
'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID,
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($amount, $order) use ($c) {
|
|
$this->renew($order, $c);
|
|
},
|
|
10,
|
|
2
|
|
);
|
|
add_action(
|
|
'woocommerce_scheduled_subscription_payment_' . CreditCardGateway::ID,
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($amount, $order) use ($c) {
|
|
$this->renew($order, $c);
|
|
},
|
|
10,
|
|
2
|
|
);
|
|
add_filter(
|
|
'woocommerce_subscription_payment_method_to_display',
|
|
/**
|
|
* Corrects the payment method name for subscriptions.
|
|
*
|
|
* @param string $payment_method_to_display The payment method string.
|
|
* @param \WC_Subscription $subscription The subscription instance.
|
|
* @param string $context The context, ex: view.
|
|
* @return string
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($payment_method_to_display, $subscription, $context) {
|
|
$payment_gateway = wc_get_payment_gateway_by_order($subscription);
|
|
if ($payment_gateway instanceof \WC_Payment_Gateway && $payment_gateway->id === PayPalGateway::ID) {
|
|
return $subscription->get_payment_method_title($context);
|
|
}
|
|
return $payment_method_to_display;
|
|
},
|
|
10,
|
|
3
|
|
);
|
|
add_action('wc_ajax_' . SubscriptionChangePaymentMethod::ENDPOINT, static function () use ($c) {
|
|
$endpoint = $c->get('wc-subscriptions.endpoint.subscription-change-payment-method');
|
|
assert($endpoint instanceof SubscriptionChangePaymentMethod);
|
|
$endpoint->handle_request();
|
|
});
|
|
add_action('woocommerce_subscriptions_change_payment_after_submit', function () use ($c) {
|
|
$context = $c->get('button.helper.context');
|
|
assert($context instanceof Context);
|
|
if (!is_user_logged_in() || !$context->is_subscription_change_payment_method_page()) {
|
|
return;
|
|
}
|
|
$payment_method_tokens_checked = $c->get('save-payment-methods.service.payment-method-tokens-checker');
|
|
assert($payment_method_tokens_checked instanceof PaymentMethodTokensChecker);
|
|
$customer_id = get_user_meta(get_current_user_id(), '_ppcp_target_customer_id', \true);
|
|
// Do not display PayPal button if the user already has a PayPal payment token.
|
|
if ($payment_method_tokens_checked->has_paypal_payment_token($customer_id)) {
|
|
return;
|
|
}
|
|
echo '<div id="ppc-button-' . esc_attr(PayPalGateway::ID) . '-save-payment-method"></div>';
|
|
});
|
|
/**
|
|
* If customer has chosen change Subscription payment to PayPal payment.
|
|
* It currently handles both cases Vault v3 and v2.
|
|
* Vault v2 would be removed when Vault v3 becomes the only available vaulting method.
|
|
*/
|
|
add_filter(
|
|
'woocommerce_paypal_payments_before_order_process',
|
|
/**
|
|
* WC_Payment_Gateway $gateway type removed.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
* @throws Exception When changing payment fails.
|
|
*/
|
|
function (bool $process, $gateway, WC_Order $wc_order) use ($c) {
|
|
if (!$gateway instanceof PayPalGateway || $gateway::ID !== PayPalGateway::ID) {
|
|
return $process;
|
|
}
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
$change_payment_method = $c->get('wc-subscriptions.change-payment-method');
|
|
assert($change_payment_method instanceof ChangePaymentMethod);
|
|
return $change_payment_method->to_paypal_payment();
|
|
}
|
|
$change_payment_method_vault_v2 = $c->get('wc-subscriptions.vault-v2.change-payment-method');
|
|
assert($change_payment_method_vault_v2 instanceof ChangePaymentMethodVaultV2);
|
|
try {
|
|
return $change_payment_method_vault_v2->to_paypal_payment($wc_order);
|
|
} catch (Exception $exception) {
|
|
throw new Exception($exception->getMessage());
|
|
}
|
|
},
|
|
10,
|
|
3
|
|
);
|
|
/**
|
|
* Vault v2 - Adds Payment Token ID to subscription after initial payment.
|
|
* It will be removed when Vault v3 becomes the only available vaulting method.
|
|
*/
|
|
add_action(
|
|
'woocommerce_subscription_payment_complete',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($subscription) use ($c) {
|
|
if (!in_array($subscription->get_payment_method(), array(PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID), \true)) {
|
|
return;
|
|
}
|
|
$paypal_subscription_id = $subscription->get_meta('ppcp_subscription') ?? '';
|
|
if ($paypal_subscription_id) {
|
|
return;
|
|
}
|
|
$payment_token_repository = $c->get('vaulting.repository.payment-token');
|
|
$logger = $c->get('woocommerce.logger.woocommerce');
|
|
if (!$c->has('save-payment-methods.eligible') || !$c->get('save-payment-methods.eligible')) {
|
|
$this->add_payment_token_id($subscription, $payment_token_repository, $logger);
|
|
}
|
|
if (count($subscription->get_related_orders()) === 1) {
|
|
$parent_order = $subscription->get_parent();
|
|
if (is_a($parent_order, WC_Order::class)) {
|
|
// Update the initial payment method title if not the same as the first order.
|
|
$payment_method_title = $parent_order->get_payment_method_title();
|
|
if ($payment_method_title && $subscription instanceof \WC_Subscription && $subscription->get_payment_method_title() !== $payment_method_title) {
|
|
$subscription->set_payment_method_title($payment_method_title);
|
|
$subscription->save();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
/**
|
|
* Vault v2 - Hides PayPal and Credit Card gateways if customer has no saved payments.
|
|
* It will be removed when Vault v3 becomes the only available vaulting method.
|
|
*/
|
|
add_filter(
|
|
'woocommerce_available_payment_gateways',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($methods) use ($c) {
|
|
if (!is_array($methods)) {
|
|
return $methods;
|
|
}
|
|
//phpcs:disable WordPress.Security.NonceVerification.Recommended
|
|
if (!(isset($_GET['change_payment_method']) && is_wc_endpoint_url('order-pay'))) {
|
|
return $methods;
|
|
}
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
return $methods;
|
|
}
|
|
// Vault v2 - If customer does not have saved PayPal payments, remove PayPal gateway from available payment methods.
|
|
// The reason is that it's not possible to save a payment without purchasing.
|
|
$paypal_tokens = WC_Payment_Tokens::get_customer_tokens(get_current_user_id(), PayPalGateway::ID);
|
|
if (!$paypal_tokens) {
|
|
unset($methods[PayPalGateway::ID]);
|
|
}
|
|
// Vault v2 - If customer does not have saved card payments, remove credit card gateway from available payment methods.
|
|
// The reason is that it's not possible to save a payment without purchasing.
|
|
$card_tokens = WC_Payment_Tokens::get_customer_tokens(get_current_user_id(), CreditCardGateway::ID);
|
|
if (!$card_tokens) {
|
|
unset($methods[CreditCardGateway::ID]);
|
|
}
|
|
return $methods;
|
|
}
|
|
);
|
|
/**
|
|
* Vault v2 - Custom saved PayPal payment tokens implementation.
|
|
* It will be removed when Vault v3 becomes the only available vaulting method.
|
|
*/
|
|
add_filter(
|
|
'woocommerce_gateway_description',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($description, $id) use ($c) {
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
return $description;
|
|
}
|
|
$display_saved_payment_tokens = $c->get('wc-subscriptions.vault-v2.display-saved-payment-tokens');
|
|
assert($display_saved_payment_tokens instanceof DisplaySavedPaymentTokens);
|
|
return $display_saved_payment_tokens->display_saved_paypal_payments((string) $id, (string) $description);
|
|
},
|
|
10,
|
|
2
|
|
);
|
|
/**
|
|
* Vault v2 - Custom saved credit card payment tokens implementation.
|
|
* It will be removed when Vault v3 becomes the only available vaulting method.
|
|
*/
|
|
add_filter(
|
|
'woocommerce_credit_card_form_fields',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($default_fields, $id) use ($c) {
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
return $default_fields;
|
|
}
|
|
$display_saved_payment_tokens = $c->get('wc-subscriptions.vault-v2.display-saved-payment-tokens');
|
|
assert($display_saved_payment_tokens instanceof DisplaySavedPaymentTokens);
|
|
return $display_saved_payment_tokens->display_saved_credit_cards((string) $id, $default_fields);
|
|
},
|
|
20,
|
|
2
|
|
);
|
|
/**
|
|
* Vault v2 Free trial subscription, adds PayPal email into checkout form.
|
|
*/
|
|
add_action('woocommerce_paypal_payments_smart_button_render_wrapper', function () use ($c) {
|
|
// Return early if save payment methods (Vault v3) is enabled.
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
return;
|
|
}
|
|
$free_trial_subscription_helper = $c->get('wc-subscriptions.free-trial-subscription-helper');
|
|
assert($free_trial_subscription_helper instanceof FreeTrialSubscriptionHelper);
|
|
if (!$free_trial_subscription_helper->is_free_trial_cart()) {
|
|
return;
|
|
}
|
|
add_action('woocommerce_review_order_after_submit', function () use ($c) {
|
|
$vaulted_paypal_email = $c->get('wc-subscriptions.vault-v2.vaulted-paypal-email');
|
|
assert($vaulted_paypal_email instanceof VaultedPayPalEmail);
|
|
$vaulted_email = $vaulted_paypal_email->get_vaulted_paypal_email();
|
|
if (!$vaulted_email) {
|
|
return;
|
|
}
|
|
?>
|
|
<div class="ppcp-vaulted-paypal-details">
|
|
<?php
|
|
echo wp_kses_post(sprintf(
|
|
// translators: %1$s - email, %2$s, %3$s - HTML tags for a link.
|
|
esc_html__('Using %2$s%1$s%3$s PayPal.', 'woocommerce-paypal-payments'),
|
|
$vaulted_email,
|
|
'<b>',
|
|
'</b>'
|
|
));
|
|
?>
|
|
</div>
|
|
<?php
|
|
});
|
|
});
|
|
/**
|
|
* Vault v2 Free trial subscription, adds vaulted PayPal email to localized script data.
|
|
*/
|
|
add_filter('woocommerce_paypal_payments_localized_script_data', function (array $localized_script_data) use ($c) {
|
|
if ($c->has('save-payment-methods.eligible') && $c->get('save-payment-methods.eligible')) {
|
|
return $localized_script_data;
|
|
}
|
|
$vaulted_paypal_email = $c->get('wc-subscriptions.vault-v2.vaulted-paypal-email');
|
|
assert($vaulted_paypal_email instanceof VaultedPayPalEmail);
|
|
$vaulted_email = $vaulted_paypal_email->get_vaulted_paypal_email();
|
|
if (!$vaulted_email) {
|
|
return $localized_script_data;
|
|
}
|
|
$free_trial_subscription_helper = $c->get('wc-subscriptions.free-trial-subscription-helper');
|
|
assert($free_trial_subscription_helper instanceof FreeTrialSubscriptionHelper);
|
|
$localized_script_data['vaulted_paypal_email'] = is_checkout() && $free_trial_subscription_helper->is_free_trial_cart() ? $vaulted_paypal_email->get_vaulted_paypal_email() : '';
|
|
return $localized_script_data;
|
|
});
|
|
return \true;
|
|
}
|
|
/**
|
|
* Handles a Subscription product renewal.
|
|
*
|
|
* @param WC_Order $order WooCommerce order.
|
|
* @param ContainerInterface $container The container.
|
|
* @return void
|
|
*/
|
|
protected function renew(WC_Order $order, ContainerInterface $container)
|
|
{
|
|
$handler = $container->get('wc-subscriptions.renewal-handler');
|
|
assert($handler instanceof \WooCommerce\PayPalCommerce\WcSubscriptions\RenewalHandler);
|
|
$handler->renew($order);
|
|
}
|
|
/**
|
|
* Adds Payment token ID to subscription.
|
|
*
|
|
* @param \WC_Subscription $subscription The subscription.
|
|
* @param PaymentTokenRepository $payment_token_repository The payment repository.
|
|
* @param LoggerInterface $logger The logger.
|
|
*/
|
|
protected function add_payment_token_id(\WC_Subscription $subscription, PaymentTokenRepository $payment_token_repository, LoggerInterface $logger): void
|
|
{
|
|
try {
|
|
$tokens = $payment_token_repository->all_for_user_id($subscription->get_customer_id());
|
|
if ($tokens) {
|
|
$latest_token_id = end($tokens)->id() ? end($tokens)->id() : '';
|
|
$subscription->update_meta_data('payment_token_id', $latest_token_id);
|
|
$subscription->save();
|
|
}
|
|
} catch (RuntimeException $error) {
|
|
$message = sprintf(
|
|
// translators: %1$s is the payment token Id, %2$s is the error message.
|
|
__('Could not add token Id to subscription %1$s: %2$s', 'woocommerce-paypal-payments'),
|
|
$subscription->get_id(),
|
|
$error->getMessage()
|
|
);
|
|
$logger->log('warning', $message);
|
|
}
|
|
}
|
|
/**
|
|
* Groups all filters for adding WC Subscriptions gateway support.
|
|
*
|
|
* @param ContainerInterface $c The container.
|
|
* @return void
|
|
*/
|
|
private function add_gateways_support(ContainerInterface $c): void
|
|
{
|
|
$subscriptions_helper = $c->get('wc-subscriptions.helper');
|
|
assert($subscriptions_helper instanceof SubscriptionHelper);
|
|
if (!$subscriptions_helper->plugin_is_active()) {
|
|
return;
|
|
}
|
|
add_filter('woocommerce_paypal_payments_paypal_gateway_supports', function (array $supports) use ($c): array {
|
|
$settings_provider = $c->get('settings.settings-provider');
|
|
assert($settings_provider instanceof SettingsProvider);
|
|
$subscription_helper = $c->get('wc-subscriptions.helper');
|
|
assert($subscription_helper instanceof SubscriptionHelper);
|
|
$subscriptions_mode = $this->get_subscriptions_mode($settings_provider, $subscription_helper);
|
|
if ('disable_paypal_subscriptions' === $subscriptions_mode) {
|
|
return $supports;
|
|
}
|
|
return array_merge($supports, self::VAULT_SUPPORTS_SUBSCRIPTIONS);
|
|
});
|
|
add_filter('woocommerce_paypal_payments_credit_card_gateway_supports', function (array $supports) use ($c): array {
|
|
$settings_provider = $c->get('settings.settings-provider');
|
|
assert($settings_provider instanceof SettingsProvider);
|
|
$subscription_helper = $c->get('wc-subscriptions.helper');
|
|
assert($subscription_helper instanceof SubscriptionHelper);
|
|
$subscriptions_mode = $this->get_subscriptions_mode($settings_provider, $subscription_helper);
|
|
if ('disable_paypal_subscriptions' === $subscriptions_mode) {
|
|
return $supports;
|
|
}
|
|
if (!$settings_provider->save_card_details()) {
|
|
return $supports;
|
|
}
|
|
return array_merge($supports, self::VAULT_SUPPORTS_SUBSCRIPTIONS);
|
|
});
|
|
add_filter('woocommerce_paypal_payments_card_button_gateway_supports', function (array $supports) use ($c): array {
|
|
$settings_provider = $c->get('settings.settings-provider');
|
|
assert($settings_provider instanceof SettingsProvider);
|
|
$subscription_helper = $c->get('wc-subscriptions.helper');
|
|
assert($subscription_helper instanceof SubscriptionHelper);
|
|
$subscriptions_mode = $this->get_subscriptions_mode($settings_provider, $subscription_helper);
|
|
if ('disable_paypal_subscriptions' === $subscriptions_mode) {
|
|
return $supports;
|
|
}
|
|
return array_merge($supports, self::VAULT_SUPPORTS_SUBSCRIPTIONS);
|
|
});
|
|
}
|
|
/**
|
|
* Gets the subscriptions mode based on settings.
|
|
*
|
|
* @param SettingsProvider $settings_provider The settings provider.
|
|
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
|
* @return string The subscriptions mode ('vaulting_api', 'subscriptions_api', or 'disable_paypal_subscriptions').
|
|
*/
|
|
private function get_subscriptions_mode(SettingsProvider $settings_provider, SubscriptionHelper $subscription_helper): string
|
|
{
|
|
if (!$subscription_helper->plugin_is_active()) {
|
|
return '';
|
|
}
|
|
$subscription_mode_disabled = (bool) apply_filters('woocommerce_paypal_payments_subscription_mode_disabled', \false);
|
|
if ($subscription_mode_disabled) {
|
|
return 'disable_paypal_subscriptions';
|
|
}
|
|
return $settings_provider->save_paypal_and_venmo() ? 'vaulting_api' : 'subscriptions_api';
|
|
}
|
|
}
|