Allow customer suspend and activate subscription

This commit is contained in:
Emili Castells Guasch 2023-03-06 16:41:18 +01:00
parent 6400e14874
commit 5a230a7669
5 changed files with 214 additions and 19 deletions

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -222,6 +223,13 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.billing-subscriptions' => static function( ContainerInterface $container ): BillingSubscriptions {
return new BillingSubscriptions(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.repository.application-context' => static function( ContainerInterface $container ) : ApplicationContextRepository {
$settings = $container->get( 'wcgateway.settings' );

View file

@ -0,0 +1,137 @@
<?php
/**
* The Billing Subscriptions endpoint.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class BillingSubscriptions
*/
class BillingSubscriptions {
use RequestTrait;
/**
* The host.
*
* @var string
*/
private $host;
/**
* The bearer.
*
* @var Bearer
*/
private $bearer;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* BillingSubscriptions constructor.
*
* @param string $host The host.
* @param Bearer $bearer The bearer.
* @param LoggerInterface $logger The logger.
*/
public function __construct( string $host, Bearer $bearer, LoggerInterface $logger ) {
$this->host = $host;
$this->bearer = $bearer;
$this->logger = $logger;
}
/**
* Suspends a subscription.
*
* @param string $id Subscription ID.
* @return void
*
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function suspend( string $id ):void {
$data = array(
'reason' => 'Cancelled by customer',
);
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id . '/suspend';
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $data ),
);
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
throw new RuntimeException( 'Not able to create plan.' );
}
$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 204 !== $status_code ) {
throw new PayPalApiException(
$json,
$status_code
);
}
}
/**
* Activates a subscription.
*
* @param string $id Subscription ID.
* @return void
*
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function activate( string $id ): void {
$data = array(
'reason' => 'Reactivated by customer',
);
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id . '/activate';
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $data ),
);
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
throw new RuntimeException( 'Not able to create plan.' );
}
$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 204 !== $status_code ) {
throw new PayPalApiException(
$json,
$status_code
);
}
}
}

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription;
use WC_Product_Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
@ -164,22 +165,22 @@ class SubscriptionModule implements ModuleInterface {
$billing_plans_endpoint = $c->get( 'api.endpoint.billing-plans' );
assert( $billing_plans_endpoint instanceof BillingPlans );
if($product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' )) {
if($product->get_meta( '_subscription_price' ) === $product->get_meta('ppcp_subscription_plan')->billing_cycles[0]->pricing_scheme->fixed_price->value) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
if ( $product->get_meta( '_subscription_price' ) === $product->get_meta( 'ppcp_subscription_plan' )->billing_cycles[0]->pricing_scheme->fixed_price->value ) {
return;
}
$billing_cycles = array(
'pricing_scheme' => array(
'fixed_price' => array(
'value' => $product->get_meta( '_subscription_price' ),
'value' => $product->get_meta( '_subscription_price' ),
),
),
);
try {
$billing_plans_endpoint->update_pricing(
$product->get_meta('ppcp_subscription_plan')->id,
$product->get_meta( 'ppcp_subscription_plan' )->id,
$billing_cycles
);
} catch ( RuntimeException $exception ) {
@ -267,22 +268,25 @@ class SubscriptionModule implements ModuleInterface {
add_action(
'add_meta_boxes',
function( string $post_type ) use($c) {
function( string $post_type ) use ( $c ) {
if ( $post_type === 'product' ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
$product = wc_get_product( $post_id );
if (is_a($product, WC_Product_Subscription::class)) {
$settings = $c->get('wcgateway.settings');
assert($settings instanceof Settings);
if ($settings->get('subscriptions_mode') && $settings->get('subscriptions_mode') === 'subscriptions_api') {
$subscription_product = $product->get_meta('ppcp_subscription_product');
$subscription_plan = $product->get_meta('ppcp_subscription_plan');
add_meta_box('ppcp_subscription', __('PayPal Subscription', 'woocommerce-paypal-payments'),
function () use ($subscription_product, $subscription_plan) {
$product = wc_get_product( $post_id );
if ( is_a( $product, WC_Product_Subscription::class ) ) {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $settings->get( 'subscriptions_mode' ) && $settings->get( 'subscriptions_mode' ) === 'subscriptions_api' ) {
$subscription_product = $product->get_meta( 'ppcp_subscription_product' );
$subscription_plan = $product->get_meta( 'ppcp_subscription_plan' );
add_meta_box(
'ppcp_subscription',
__( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
function () use ( $subscription_product, $subscription_plan ) {
echo '<label><input type="checkbox" name="ppcp_connect_subscriptions_api" checked="checked">Connect to Subscriptions API</label>';
if ($subscription_product && $subscription_plan) {
echo '<p>Product ID: ' . esc_attr($subscription_product->id) . '</p>';
echo '<p>Plan ID: ' . esc_attr($subscription_plan->id) . '</p>';
if ( $subscription_product && $subscription_plan ) {
echo '<p>Product ID: ' . esc_attr( $subscription_product->id ) . '</p>';
echo '<p>Plan ID: ' . esc_attr( $subscription_plan->id ) . '</p>';
}
},
$post_type,
@ -310,6 +314,52 @@ class SubscriptionModule implements ModuleInterface {
10,
2
);
add_action(
'woocommerce_customer_changed_subscription_to_cancelled',
function( $subscription ) use ( $c ) {
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( $subscription_id ) {
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
try {
$subscriptions_endpoint->suspend( $subscription_id );
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
}
}
}
);
add_action(
'woocommerce_customer_changed_subscription_to_active',
function( $subscription ) use ( $c ) {
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( $subscription_id ) {
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
try {
$subscriptions_endpoint->activate( $subscription_id );
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$logger->error( 'Could not active subscription product on PayPal. ' . $error );
}
}
}
);
}
/**

View file

@ -2,7 +2,7 @@ require('dotenv').config({ path: '.env.e2e' });
const config = {
testDir: './tests/playwright',
timeout: 30000,
timeout: 40000,
use: {
baseURL: process.env.BASEURL,
},

View file

@ -109,7 +109,7 @@ test.describe('Subscriptions Customer', () => {
await popup.fill('#password', CUSTOMER_PASSWORD);
await popup.locator('#btnLogin').click();
await popup.locator('text=Continue').click();
await popup.locator('text=Agree and Subscribe').click();
await popup.locator('text=Agree & Subscribe').click();
await page.waitForTimeout(10000);