Move PayPal subscriptions api logic to its own module

This commit is contained in:
Emili Castells Guasch 2023-10-18 17:03:15 +02:00
parent b3c66f4cbb
commit 94674adc25
29 changed files with 707 additions and 604 deletions

View file

@ -27,6 +27,7 @@ return function ( string $root_dir ): iterable {
( require "$modules_dir/ppcp-order-tracking/module.php" )(),
( require "$modules_dir/ppcp-uninstall/module.php" )(),
( require "$modules_dir/ppcp-blocks/module.php" )(),
( require "$modules_dir/ppcp-paypal-subscriptions/module.php" )(),
);
if ( apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores

View file

@ -204,7 +204,7 @@ return array(
$intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$application_context_repository = $container->get( 'api.repository.application-context' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
return new OrderEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),

View file

@ -83,7 +83,7 @@ return array(
$request_data = $container->get( 'button.request-data' );
$client_id = $container->get( 'button.client_id' );
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$messages_apply = $container->get( 'button.helper.messages-apply' );
$environment = $container->get( 'onboarding.environment' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );

View file

@ -27,7 +27,7 @@ return array(
},
'compat.ppec.subscriptions-handler' => static function ( ContainerInterface $container ) {
$ppcp_renewal_handler = $container->get( 'subscription.renewal-handler' );
$ppcp_renewal_handler = $container->get( 'wc-subscriptions.renewal-handler' );
$gateway = $container->get( 'compat.ppec.mock-gateway' );
return new PPEC\SubscriptionsHandler( $ppcp_renewal_handler, $gateway );

View file

@ -0,0 +1,3 @@
node_modules
assets/js
assets/css

View file

@ -0,0 +1,17 @@
{
"name": "woocommerce/ppcp-paypal-subscriptions",
"type": "dhii-mod",
"description": "Module for PayPal Subscriptions API integration",
"license": "GPL-2.0",
"require": {
"php": "^7.2 | ^8.0",
"dhii/module-interface": "^0.3.0-alpha1"
},
"autoload": {
"psr-4": {
"WooCommerce\\PayPalCommerce\\PayPalSubscriptions\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View file

@ -0,0 +1,14 @@
<?php
/**
* The PayPalSubscriptions module extensions.
*
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array();

View file

@ -0,0 +1,16 @@
<?php
/**
* The PayPalSubscriptions module.
*
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface {
return new PayPalSubscriptionsModule();
};

View file

@ -1,5 +1,5 @@
{
"name": "ppcp-wc-subscriptions",
"name": "ppcp-paypal-subscriptions",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"browserslist": [

View file

@ -0,0 +1,43 @@
<?php
/**
* The PayPalSubscriptions module services.
*
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'paypal-subscriptions.deactivate-plan-endpoint' => static function ( ContainerInterface $container ): DeactivatePlanEndpoint {
return new DeactivatePlanEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.billing-plans' )
);
},
'paypal-subscriptions.api-handler' => static function( ContainerInterface $container ): SubscriptionsApiHandler {
return new SubscriptionsApiHandler(
$container->get( 'api.endpoint.catalog-products' ),
$container->get( 'api.factory.product' ),
$container->get( 'api.endpoint.billing-plans' ),
$container->get( 'api.factory.billing-cycle' ),
$container->get( 'api.factory.payment-preferences' ),
$container->get( 'api.shop.currency' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'paypal-subscriptions.module.url' => static function ( ContainerInterface $container ): string {
/**
* The path cannot be false.
*
* @psalm-suppress PossiblyFalseArgument
*/
return plugins_url(
'/modules/ppcp-paypal-subscriptions/',
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
);

View file

@ -7,7 +7,7 @@
declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use Exception;
use WC_Product;

View file

@ -1,49 +1,40 @@
<?php
/**
* The subscription module.
* The PayPalSubscriptions module.
*
* @package WooCommerce\PayPalCommerce\WcSubscriptions
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use ActionScheduler_Store;
use Exception;
use WC_Order;
use WC_Product;
use WC_Product_Subscription;
use WC_Product_Subscription_Variation;
use WC_Product_Variable;
use WC_Product_Variable_Subscription;
use WC_Subscription;
use WC_Subscriptions_Product;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WP_Post;
/**
* Class SubscriptionModule
* Class SavedPaymentCheckerModule
*/
class SubscriptionModule implements ModuleInterface {
use TransactionIdHandlingTrait;
class PayPalSubscriptionsModule implements ModuleInterface {
/**
* {@inheritDoc}
@ -59,466 +50,6 @@ class SubscriptionModule implements ModuleInterface {
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
add_action(
'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID,
function ( $amount, $order ) use ( $c ) {
$this->renew( $order, $c );
},
10,
2
);
add_action(
'woocommerce_scheduled_subscription_payment_' . CreditCardGateway::ID,
function ( $amount, $order ) use ( $c ) {
$this->renew( $order, $c );
},
10,
2
);
add_action(
'woocommerce_subscription_payment_complete',
function ( $subscription ) use ( $c ) {
$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' );
$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 ) ) {
$order_repository = $c->get( 'api.repository.order' );
$order = $order_repository->for_wc_order( $parent_order );
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
$subscription->save();
}
}
}
}
);
add_filter(
'woocommerce_gateway_description',
function ( $description, $id ) use ( $c ) {
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$settings = $c->get( 'wcgateway.settings' );
$subscription_helper = $c->get( 'subscription.helper' );
return $this->display_saved_paypal_payments( $settings, (string) $id, $payment_token_repository, (string) $description, $subscription_helper );
},
10,
2
);
add_filter(
'woocommerce_credit_card_form_fields',
function ( $default_fields, $id ) use ( $c ) {
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$settings = $c->get( 'wcgateway.settings' );
$subscription_helper = $c->get( 'subscription.helper' );
return $this->display_saved_credit_cards( $settings, $id, $payment_token_repository, $default_fields, $subscription_helper );
},
20,
2
);
add_filter(
'ppcp_create_order_request_body_data',
function( array $data ) use ( $c ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) );
if ( ! $subscription_id ) {
return $data;
}
$subscription = wc_get_order( $subscription_id );
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
return $data;
}
if (
$wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID
&& isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN'
&& isset( $data['payment_source']['token']['source']->card )
) {
$data['payment_source'] = array(
'card' => array(
'vault_id' => $data['payment_source']['token']['id'],
'stored_credential' => array(
'payment_initiator' => 'MERCHANT',
'payment_type' => 'RECURRING',
'usage' => 'SUBSEQUENT',
),
),
);
$previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
if ( $previous_transaction_reference ) {
$data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference;
}
}
return $data;
}
);
$this->subscriptions_api_integration( $c );
add_action(
'admin_enqueue_scripts',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( $hook ) use ( $c ) {
if ( ! is_string( $hook ) ) {
return;
}
$settings = $c->get( 'wcgateway.settings' );
$subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) {
return;
}
//phpcs:disable WordPress.Security.NonceVerification.Recommended
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
$product = wc_get_product( $post_id );
if ( ! ( is_a( $product, WC_Product::class ) ) ) {
return;
}
$subscriptions_helper = $c->get( 'subscription.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper );
if (
! $subscriptions_helper->plugin_is_active()
|| ! (
is_a( $product, WC_Product_Subscription::class )
|| is_a( $product, WC_Product_Variable_Subscription::class )
|| is_a( $product, WC_Product_Subscription_Variation::class )
)
|| ! WC_Subscriptions_Product::is_subscription( $product )
) {
return;
}
$module_url = $c->get( 'subscription.module.url' );
wp_enqueue_script(
'ppcp-paypal-subscription',
untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js',
array( 'jquery' ),
$c->get( 'ppcp.asset-version' ),
true
);
$products = array( $this->set_product_config( $product ) );
if ( $product->get_type() === 'variable-subscription' ) {
$products = array();
/**
* Suppress pslam.
*
* @psalm-suppress TypeDoesNotContainType
*
* WC_Product_Variable_Subscription extends WC_Product_Variable.
*/
assert( $product instanceof WC_Product_Variable );
$available_variations = $product->get_available_variations();
foreach ( $available_variations as $variation ) {
/**
* The method is defined in WooCommerce.
*
* @psalm-suppress UndefinedMethod
*/
$variation = wc_get_product_object( 'variation', $variation['variation_id'] );
$products[] = $this->set_product_config( $variation );
}
}
wp_localize_script(
'ppcp-paypal-subscription',
'PayPalCommerceGatewayPayPalSubscriptionProducts',
$products
);
}
);
$endpoint = $c->get( 'subscription.deactivate-plan-endpoint' );
assert( $endpoint instanceof DeactivatePlanEndpoint );
add_action(
'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT,
array( $endpoint, 'handle_request' )
);
add_action(
'add_meta_boxes',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( string $post_type, $post_or_order_object ) use ( $c ) {
if ( ! function_exists( 'wcs_get_subscription' ) ) {
return;
}
$order = ( $post_or_order_object instanceof WP_Post )
? wc_get_order( $post_or_order_object->ID )
: $post_or_order_object;
if ( ! is_a( $order, WC_Order::class ) ) {
return;
}
$subscription = wcs_get_subscription( $order->get_id() );
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
return;
}
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( ! $subscription_id ) {
return;
}
$screen_id = wc_get_page_screen_id( 'shop_subscription' );
remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' );
$environment = $c->get( 'onboarding.environment' );
add_meta_box(
'ppcp_paypal_subscription',
__( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
function() use ( $subscription_id, $environment ) {
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
$url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id;
echo '<p>' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '</p>';
echo '<p><strong>' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . '</strong> <a href="' . esc_url( $url ) . '" target="_blank">' . esc_attr( $subscription_id ) . '</a></p>';
},
$post_type,
'side'
);
},
30,
2
);
add_action(
'action_scheduler_before_execute',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( $action_id ) {
/**
* Class exist in WooCommerce.
*
* @psalm-suppress UndefinedClass
*/
$store = ActionScheduler_Store::instance();
$action = $store->fetch_action( $action_id );
$subscription_id = $action->get_args()['subscription_id'] ?? null;
if ( $subscription_id ) {
$subscription = wcs_get_subscription( $subscription_id );
if ( is_a( $subscription, WC_Subscription::class ) ) {
$paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( $paypal_subscription_id ) {
as_unschedule_action( $action->get_hook(), $action->get_args() );
}
}
}
}
);
}
/**
* Returns the key for the module.
*
* @return string|void
*/
public function getKey() {
}
/**
* Handles a Subscription product renewal.
*
* @param \WC_Order $order WooCommerce order.
* @param ContainerInterface|null $container The container.
* @return void
*/
protected function renew( $order, $container ) {
if ( ! ( $order instanceof \WC_Order ) ) {
return;
}
$handler = $container->get( 'subscription.renewal-handler' );
$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
) {
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 );
}
}
/**
* Displays saved PayPal payments.
*
* @param Settings $settings The settings.
* @param string $id The payment gateway Id.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param string $description The payment gateway description.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @return string
*/
protected function display_saved_paypal_payments(
Settings $settings,
string $id,
PaymentTokenRepository $payment_token_repository,
string $description,
SubscriptionHelper $subscription_helper
): string {
if ( $settings->has( 'vault_enabled' )
&& $settings->get( 'vault_enabled' )
&& PayPalGateway::ID === $id
&& $subscription_helper->is_subscription_change_payment()
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) {
return esc_html__(
'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
}
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-paypal-payment" name="saved_paypal_payment">',
esc_html__( 'Select a saved PayPal payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->paypal ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s</option>',
$token->id(),
$token->source()->paypal->payer->email_address
);
}
}
$output .= '</select></p>';
return $output;
}
return $description;
}
/**
* Displays saved credit cards.
*
* @param Settings $settings The settings.
* @param string $id The payment gateway Id.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param array $default_fields Default payment gateway fields.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @return array|mixed|string
* @throws NotFoundException When setting was not found.
*/
protected function display_saved_credit_cards(
Settings $settings,
string $id,
PaymentTokenRepository $payment_token_repository,
array $default_fields,
SubscriptionHelper $subscription_helper
) {
if ( $settings->has( 'vault_enabled_dcc' )
&& $settings->get( 'vault_enabled_dcc' )
&& $subscription_helper->is_subscription_change_payment()
&& CreditCardGateway::ID === $id
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_card( $tokens ) ) {
$default_fields = array();
$default_fields['saved-credit-card'] = esc_html__(
'No Credit Card saved, in order to use a saved Credit Card you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
return $default_fields;
}
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-credit-card" name="saved_credit_card">',
esc_html__( 'Select a saved Credit Card payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s ...%3$s</option>',
$token->id(),
$token->source()->card->brand,
$token->source()->card->last_digits
);
}
}
$output .= '</select></p>';
$default_fields = array();
$default_fields['saved-credit-card'] = $output;
return $default_fields;
}
return $default_fields;
}
/**
* Adds PayPal subscriptions API integration.
*
* @param ContainerInterface $c The container.
* @return void
* @throws Exception When something went wrong.
*/
protected function subscriptions_api_integration( ContainerInterface $c ): void {
add_action(
'save_post',
/**
@ -549,7 +80,7 @@ class SubscriptionModule implements ModuleInterface {
return;
}
$subscriptions_api_handler = $c->get( 'subscription.api-handler' );
$subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' );
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
},
@ -566,7 +97,7 @@ class SubscriptionModule implements ModuleInterface {
function( $variation_id ) use ( $c ) {
$wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) );
$subscriptions_helper = $c->get( 'subscription.helper' );
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper );
if (
@ -583,7 +114,7 @@ class SubscriptionModule implements ModuleInterface {
return;
}
$subscriptions_api_handler = $c->get( 'subscription.api-handler' );
$subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' );
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
},
@ -942,6 +473,236 @@ class SubscriptionModule implements ModuleInterface {
10,
3
);
add_action(
'admin_enqueue_scripts',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( $hook ) use ( $c ) {
if ( ! is_string( $hook ) ) {
return;
}
$settings = $c->get( 'wcgateway.settings' );
$subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) {
return;
}
//phpcs:disable WordPress.Security.NonceVerification.Recommended
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
$product = wc_get_product( $post_id );
if ( ! ( is_a( $product, WC_Product::class ) ) ) {
return;
}
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper );
if (
! $subscriptions_helper->plugin_is_active()
|| ! (
is_a( $product, WC_Product_Subscription::class )
|| is_a( $product, WC_Product_Variable_Subscription::class )
|| is_a( $product, WC_Product_Subscription_Variation::class )
)
|| ! WC_Subscriptions_Product::is_subscription( $product )
) {
return;
}
$module_url = $c->get( 'paypal-subscriptions.module.url' );
wp_enqueue_script(
'ppcp-paypal-subscription',
untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js',
array( 'jquery' ),
$c->get( 'ppcp.asset-version' ),
true
);
$products = array( $this->set_product_config( $product ) );
if ( $product->get_type() === 'variable-subscription' ) {
$products = array();
/**
* Suppress pslam.
*
* @psalm-suppress TypeDoesNotContainType
*
* WC_Product_Variable_Subscription extends WC_Product_Variable.
*/
assert( $product instanceof WC_Product_Variable );
$available_variations = $product->get_available_variations();
foreach ( $available_variations as $variation ) {
/**
* The method is defined in WooCommerce.
*
* @psalm-suppress UndefinedMethod
*/
$variation = wc_get_product_object( 'variation', $variation['variation_id'] );
$products[] = $this->set_product_config( $variation );
}
}
wp_localize_script(
'ppcp-paypal-subscription',
'PayPalCommerceGatewayPayPalSubscriptionProducts',
$products
);
}
);
$endpoint = $c->get( 'paypal-subscriptions.deactivate-plan-endpoint' );
assert( $endpoint instanceof DeactivatePlanEndpoint );
add_action(
'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT,
array( $endpoint, 'handle_request' )
);
add_action(
'add_meta_boxes',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( string $post_type, $post_or_order_object ) use ( $c ) {
if ( ! function_exists( 'wcs_get_subscription' ) ) {
return;
}
$order = ( $post_or_order_object instanceof WP_Post )
? wc_get_order( $post_or_order_object->ID )
: $post_or_order_object;
if ( ! is_a( $order, WC_Order::class ) ) {
return;
}
$subscription = wcs_get_subscription( $order->get_id() );
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
return;
}
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( ! $subscription_id ) {
return;
}
$screen_id = wc_get_page_screen_id( 'shop_subscription' );
remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' );
$environment = $c->get( 'onboarding.environment' );
add_meta_box(
'ppcp_paypal_subscription',
__( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
function() use ( $subscription_id, $environment ) {
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
$url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id;
echo '<p>' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '</p>';
echo '<p><strong>' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . '</strong> <a href="' . esc_url( $url ) . '" target="_blank">' . esc_attr( $subscription_id ) . '</a></p>';
},
$post_type,
'side'
);
},
30,
2
);
add_action(
'action_scheduler_before_execute',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( $action_id ) {
/**
* Class exist in WooCommerce.
*
* @psalm-suppress UndefinedClass
*/
$store = ActionScheduler_Store::instance();
$action = $store->fetch_action( $action_id );
$subscription_id = $action->get_args()['subscription_id'] ?? null;
if ( $subscription_id ) {
$subscription = wcs_get_subscription( $subscription_id );
if ( is_a( $subscription, WC_Subscription::class ) ) {
$paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( $paypal_subscription_id ) {
as_unschedule_action( $action->get_hook(), $action->get_args() );
}
}
}
}
);
}
/**
* Updates subscription product meta.
*
* @param WC_Product $product The product.
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
* @return void
*/
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
// phpcs:ignore WordPress.Security.NonceVerification
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
$product->save();
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
$subscriptions_api_handler->update_product( $product );
$subscriptions_api_handler->update_plan( $product );
return;
}
if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) {
$subscriptions_api_handler->create_product( $product );
}
if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) {
// phpcs:ignore WordPress.Security.NonceVerification
$subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) );
if ( ! is_string( $subscription_plan_name ) ) {
return;
}
$product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name );
$product->save();
$subscriptions_api_handler->create_plan( $subscription_plan_name, $product );
}
}
}
/**
* Returns subscription product configuration.
*
* @param WC_Product $product The product.
* @return array
*/
private function set_product_config( WC_Product $product ): array {
$plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
$plan_id = $plan['id'] ?? '';
return array(
'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '',
'plan_id' => $plan_id,
'product_id' => $product->get_id(),
'ajax' => array(
'deactivate_plan' => array(
'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ),
),
),
);
}
/**
@ -1014,66 +775,4 @@ class SubscriptionModule implements ModuleInterface {
);
}
}
/**
* Updates subscription product meta.
*
* @param WC_Product $product The product.
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
* @return void
*/
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
// phpcs:ignore WordPress.Security.NonceVerification
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
$product->save();
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
$subscriptions_api_handler->update_product( $product );
$subscriptions_api_handler->update_plan( $product );
return;
}
if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) {
$subscriptions_api_handler->create_product( $product );
}
if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) {
// phpcs:ignore WordPress.Security.NonceVerification
$subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) );
if ( ! is_string( $subscription_plan_name ) ) {
return;
}
$product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name );
$product->save();
$subscriptions_api_handler->create_plan( $subscription_plan_name, $product );
}
}
}
/**
* Returns subscription product configuration.
*
* @param WC_Product $product The product.
* @return array
*/
private function set_product_config( WC_Product $product ): array {
$plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
$plan_id = $plan['id'] ?? '';
return array(
'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '',
'plan_id' => $plan_id,
'product_id' => $product->get_id(),
'ajax' => array(
'deactivate_plan' => array(
'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ),
),
),
);
}
}

View file

@ -7,7 +7,7 @@
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use Psr\Log\LoggerInterface;
use WC_Product;

View file

@ -14,6 +14,9 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* Class SavePaymentMethodsModule
*/
class SavePaymentMethodsModule implements ModuleInterface {
/**
@ -26,5 +29,8 @@ class SavePaymentMethodsModule implements ModuleInterface {
);
}
public function run(ContainerInterface $c): void {}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {}
}

View file

@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
$insert_after = function( array $array, string $key, array $new ): array {

View file

@ -43,7 +43,7 @@ class SavedPaymentCheckerModule implements ModuleInterface {
add_filter(
'woocommerce_paypal_payments_order_intent',
function( string $intent ) use ( $c ) {
$subscription_helper = $c->get( 'subscription.helper' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->current_product_is_subscription() ) {
@ -60,7 +60,7 @@ class SavedPaymentCheckerModule implements ModuleInterface {
add_action(
'woocommerce_paypal_payments_before_handle_payment_success',
function( WC_Order $wc_order ) use ( $c ) {
$subscription_helper = $c->get( 'subscription.helper' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
if ( $subscription_helper->has_subscription( $wc_order->get_id() ) ) {
@ -93,7 +93,7 @@ class SavedPaymentCheckerModule implements ModuleInterface {
add_action(
'woocommerce_email_before_order_table',
function( WC_Order $order ) use ( $c ) {
$subscription_helper = $c->get( 'subscription.helper' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
@ -119,7 +119,7 @@ class SavedPaymentCheckerModule implements ModuleInterface {
add_action(
'woocommerce_email_after_order_table',
function( WC_Order $order ) use ( $c ) {
$subscription_helper = $c->get( 'subscription.helper' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );

View file

@ -65,7 +65,7 @@ class StatusReportModule implements ModuleInterface {
$messages_apply = $c->get( 'button.helper.messages-apply' );
/* @var SubscriptionHelper $subscription_helper The subscription helper class. */
$subscription_helper = $c->get( 'subscription.helper' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
$last_webhook_storage = $c->get( 'webhook.last-webhook-storage' );
assert( $last_webhook_storage instanceof WebhookEventStorage );

View file

@ -31,7 +31,7 @@ return array(
},
'vaulting.credit-card-handler' => function( ContainerInterface $container ): VaultedCreditCardHandler {
return new VaultedCreditCardHandler(
$container->get( 'subscription.helper' ),
$container->get( 'wc-subscriptions.helper' ),
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.payer' ),

View file

@ -51,7 +51,7 @@ class VaultingModule implements ModuleInterface {
$listener->listen();
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
add_action(
'woocommerce_created_customer',
function( int $customer_id ) use ( $subscription_helper ) {

View file

@ -76,7 +76,7 @@ return array(
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
$state = $container->get( 'onboarding.state' );
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$environment = $container->get( 'onboarding.environment' );
@ -109,7 +109,7 @@ return array(
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
$state = $container->get( 'onboarding.state' );
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$vaulted_credit_card_handler = $container->get( 'vaulting.credit-card-handler' );
@ -137,7 +137,7 @@ return array(
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'subscription.helper' ),
$container->get( 'wc-subscriptions.helper' ),
$container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
$container->get( 'onboarding.environment' ),
$container->get( 'vaulting.repository.payment-token' ),
@ -148,7 +148,7 @@ return array(
$session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' );
$settings_status = $container->get( 'wcgateway.settings.status' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
return new DisableGateways( $session_handler, $settings, $settings_status, $subscription_helper );
},
@ -331,7 +331,7 @@ return array(
$settings = $container->get( 'wcgateway.settings' );
$environment = $container->get( 'onboarding.environment' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$order_helper = $container->get( 'api.order-helper' );
return new OrderProcessor(
$session_handler,
@ -359,7 +359,7 @@ return array(
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$notice = $container->get( 'wcgateway.notice.authorize-order-action' );
$settings = $container->get( 'wcgateway.settings' );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
return new AuthorizedPaymentsProcessor(
$order_endpoint,
$payments_endpoint,
@ -439,7 +439,7 @@ return array(
$onboarding_options_renderer = $container->get( 'onboarding.render-options' );
assert( $onboarding_options_renderer instanceof OnboardingOptionsRenderer );
$subscription_helper = $container->get( 'subscription.helper' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
$fields = array(

View file

@ -177,7 +177,7 @@ class WCGatewayModule implements ModuleInterface {
$assets = new SettingsPageAssets(
$c->get( 'wcgateway.url' ),
$c->get( 'ppcp.asset-version' ),
$c->get( 'subscription.helper' ),
$c->get( 'wc-subscriptions.helper' ),
$c->get( 'button.client_id_for_admin' ),
$c->get( 'api.shop.currency' ),
$c->get( 'api.shop.country' ),

View file

@ -12,5 +12,5 @@ namespace WooCommerce\PayPalCommerce\WcSubscriptions;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface {
return new SubscriptionModule();
return new WcSubscriptionsModule();
};

View file

@ -9,15 +9,17 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\PayPalSubscriptions\DeactivatePlanEndpoint;
use WooCommerce\PayPalCommerce\PayPalSubscriptions\SubscriptionsApiHandler;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
return array(
'subscription.helper' => static function ( ContainerInterface $container ): SubscriptionHelper {
'wc-subscriptions.helper' => static function ( ContainerInterface $container ): SubscriptionHelper {
return new SubscriptionHelper( $container->get( 'wcgateway.settings' ) );
},
'subscription.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
'wc-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$repository = $container->get( 'vaulting.repository.payment-token' );
$endpoint = $container->get( 'api.endpoint.order' );
@ -38,37 +40,9 @@ return array(
$authorized_payments_processor
);
},
'subscription.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
$factory = $container->get( 'api.factory.payment-token' );
$endpoint = $container->get( 'api.endpoint.payment-token' );
return new PaymentTokenRepository( $factory, $endpoint );
},
'subscription.api-handler' => static function( ContainerInterface $container ): SubscriptionsApiHandler {
return new SubscriptionsApiHandler(
$container->get( 'api.endpoint.catalog-products' ),
$container->get( 'api.factory.product' ),
$container->get( 'api.endpoint.billing-plans' ),
$container->get( 'api.factory.billing-cycle' ),
$container->get( 'api.factory.payment-preferences' ),
$container->get( 'api.shop.currency' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'subscription.module.url' => static function ( ContainerInterface $container ): string {
/**
* The path cannot be false.
*
* @psalm-suppress PossiblyFalseArgument
*/
return plugins_url(
'/modules/ppcp-subscription/',
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'subscription.deactivate-plan-endpoint' => static function ( ContainerInterface $container ): DeactivatePlanEndpoint {
return new DeactivatePlanEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.billing-plans' )
);
},
);

View file

@ -0,0 +1,330 @@
<?php
/**
* The subscription module.
*
* @package WooCommerce\PayPalCommerce\WcSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class SubscriptionModule
*/
class WcSubscriptionsModule implements ModuleInterface {
use TransactionIdHandlingTrait;
/**
* {@inheritDoc}
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
require __DIR__ . '/../services.php',
require __DIR__ . '/../extensions.php'
);
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
add_action(
'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID,
function ( $amount, $order ) use ( $c ) {
$this->renew( $order, $c );
},
10,
2
);
add_action(
'woocommerce_scheduled_subscription_payment_' . CreditCardGateway::ID,
function ( $amount, $order ) use ( $c ) {
$this->renew( $order, $c );
},
10,
2
);
add_action(
'woocommerce_subscription_payment_complete',
function ( $subscription ) use ( $c ) {
$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' );
$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 ) ) {
$order_repository = $c->get( 'api.repository.order' );
$order = $order_repository->for_wc_order( $parent_order );
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
$subscription->save();
}
}
}
}
);
add_filter(
'woocommerce_gateway_description',
function ( $description, $id ) use ( $c ) {
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$settings = $c->get( 'wcgateway.settings' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
return $this->display_saved_paypal_payments( $settings, (string) $id, $payment_token_repository, (string) $description, $subscription_helper );
},
10,
2
);
add_filter(
'woocommerce_credit_card_form_fields',
function ( $default_fields, $id ) use ( $c ) {
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$settings = $c->get( 'wcgateway.settings' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
return $this->display_saved_credit_cards( $settings, $id, $payment_token_repository, $default_fields, $subscription_helper );
},
20,
2
);
add_filter(
'ppcp_create_order_request_body_data',
function( array $data ) use ( $c ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) );
if ( ! $subscription_id ) {
return $data;
}
$subscription = wc_get_order( $subscription_id );
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
return $data;
}
if (
$wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID
&& isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN'
&& isset( $data['payment_source']['token']['source']->card )
) {
$data['payment_source'] = array(
'card' => array(
'vault_id' => $data['payment_source']['token']['id'],
'stored_credential' => array(
'payment_initiator' => 'MERCHANT',
'payment_type' => 'RECURRING',
'usage' => 'SUBSEQUENT',
),
),
);
$previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
if ( $previous_transaction_reference ) {
$data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference;
}
}
return $data;
}
);
}
/**
* Returns the key for the module.
*
* @return string|void
*/
public function getKey() {
}
/**
* Handles a Subscription product renewal.
*
* @param \WC_Order $order WooCommerce order.
* @param ContainerInterface|null $container The container.
* @return void
*/
protected function renew( $order, $container ) {
if ( ! ( $order instanceof \WC_Order ) ) {
return;
}
$handler = $container->get( 'wc-subscriptions.renewal-handler' );
$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
) {
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 );
}
}
/**
* Displays saved PayPal payments.
*
* @param Settings $settings The settings.
* @param string $id The payment gateway Id.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param string $description The payment gateway description.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @return string
*/
protected function display_saved_paypal_payments(
Settings $settings,
string $id,
PaymentTokenRepository $payment_token_repository,
string $description,
SubscriptionHelper $subscription_helper
): string {
if ( $settings->has( 'vault_enabled' )
&& $settings->get( 'vault_enabled' )
&& PayPalGateway::ID === $id
&& $subscription_helper->is_subscription_change_payment()
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) {
return esc_html__(
'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
}
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-paypal-payment" name="saved_paypal_payment">',
esc_html__( 'Select a saved PayPal payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->paypal ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s</option>',
$token->id(),
$token->source()->paypal->payer->email_address
);
}
}
$output .= '</select></p>';
return $output;
}
return $description;
}
/**
* Displays saved credit cards.
*
* @param Settings $settings The settings.
* @param string $id The payment gateway Id.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param array $default_fields Default payment gateway fields.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @return array|mixed|string
* @throws NotFoundException When setting was not found.
*/
protected function display_saved_credit_cards(
Settings $settings,
string $id,
PaymentTokenRepository $payment_token_repository,
array $default_fields,
SubscriptionHelper $subscription_helper
) {
if ( $settings->has( 'vault_enabled_dcc' )
&& $settings->get( 'vault_enabled_dcc' )
&& $subscription_helper->is_subscription_change_payment()
&& CreditCardGateway::ID === $id
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_card( $tokens ) ) {
$default_fields = array();
$default_fields['saved-credit-card'] = esc_html__(
'No Credit Card saved, in order to use a saved Credit Card you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
return $default_fields;
}
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-credit-card" name="saved_credit_card">',
esc_html__( 'Select a saved Credit Card payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s ...%3$s</option>',
$token->id(),
$token->source()->card->brand,
$token->source()->card->last_digits
);
}
}
$output .= '</select></p>';
$default_fields = array();
$default_fields['saved-credit-card'] = $output;
return $default_fields;
}
return $default_fields;
}
}

View file

@ -14,7 +14,7 @@
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install",
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install",
"install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install",
"install:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn install",
"install:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn install",
"install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install",
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
@ -27,7 +27,7 @@
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build",
"build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build",
"build:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run build",
"build:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn run build",
"build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build",
"build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build",
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
"build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build",
@ -39,7 +39,7 @@
"watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch",
"watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch",
"watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch",
"watch:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn run watch",
"watch:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run watch",
"watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch",
"watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch",
"watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",