From 72be73c5dcf6c250fd9204d2ecd64d419b6f6c59 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 28 Mar 2023 12:38:43 +0200 Subject: [PATCH] Update plan --- .../src/Endpoint/BillingPlans.php | 57 +++++++++++-------- .../src/SubscriptionModule.php | 6 +- .../src/SubscriptionsApiHandler.php | 22 ++++++- tests/playwright/subscriptions-api.spec.js | 26 +++++++-- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/BillingPlans.php b/modules/ppcp-api-client/src/Endpoint/BillingPlans.php index e33d44ea9..24a576001 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingPlans.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingPlans.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; +use WooCommerce\PayPalCommerce\ApiClient\Entity\BillingCycle; use WooCommerce\PayPalCommerce\ApiClient\Entity\Plan; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; @@ -130,38 +131,46 @@ class BillingPlans { return $this->plan_factory->from_paypal_response($json); } - /** - * Updates a subscription plan. - * - * @param string $billing_plan_id Billing plan ID. - * @param array $billing_cycles Billing cycles. - * - * @return void - * - * @throws RuntimeException If the request fails. - * @throws PayPalApiException If the request fails. - */ - public function update_pricing(string $billing_plan_id, array $billing_cycles):void { + public function plan(string $id): Plan { + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $id; + $args = array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'Prefer' => 'return=representation' + ), + ); + + $response = $this->request( $url, $args ); + if ( is_wp_error( $response ) || ! is_array( $response ) ) { + throw new RuntimeException( 'Not able to get product.' ); + } + + $json = json_decode( $response['body'] ); + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( 200 !== $status_code ) { + throw new PayPalApiException( + $json, + $status_code + ); + } + + return $this->plan_factory->from_paypal_response($json); + } + + public function update_pricing(string $id, BillingCycle $billing_cycle): void { $data = array( "pricing_schemes" => array( (object)array( "billing_cycle_sequence" => 1, - "pricing_scheme" => array( - "fixed_price" => array( - "value" => $billing_cycles['pricing_scheme']['fixed_price']['value'], - "currency_code" => "USD" - ), - "roll_out_strategy" => array( - "effective_time" => "2022-11-01T00:00:00Z", - "process_change_from" => "NEXT_PAYMENT" - ), - ), + "pricing_scheme" => $billing_cycle->pricing_scheme(), ), ), ); $bearer = $this->bearer->bearer(); - $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $billing_plan_id . '/update-pricing-schemes'; + $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $id . '/update-pricing-schemes'; $args = array( 'method' => 'POST', 'headers' => array( @@ -173,7 +182,7 @@ class BillingPlans { $response = $this->request( $url, $args ); if ( is_wp_error( $response ) || ! is_array( $response ) ) { - throw new RuntimeException( 'Not able to create plan.' ); + throw new RuntimeException( 'Could not update pricing.' ); } $json = json_decode( $response['body'] ); diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index 00ecc4cc1..345afc754 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -169,7 +169,7 @@ class SubscriptionModule implements ModuleInterface { if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) { $subscriptions_api_handler->update_product($product); - $subscriptions_api_handler->update_plan(); + $subscriptions_api_handler->update_plan($product); return; } @@ -264,7 +264,7 @@ class SubscriptionModule implements ModuleInterface { $subscription_plan_name = $product->get_meta('_ppcp_subscription_plan_name'); echo ''; } diff --git a/modules/ppcp-subscription/src/SubscriptionsApiHandler.php b/modules/ppcp-subscription/src/SubscriptionsApiHandler.php index 054bdaee7..b702aac0d 100644 --- a/modules/ppcp-subscription/src/SubscriptionsApiHandler.php +++ b/modules/ppcp-subscription/src/SubscriptionsApiHandler.php @@ -140,7 +140,27 @@ class SubscriptionsApiHandler { } } - public function update_plan() { + public function update_plan(WC_Product $product) { + try { + $subscription_plan_id = $product->get_meta('ppcp_subscription_plan')['id'] ?? ''; + if ($subscription_plan_id) { + $subscription_plan = $this->billing_plans_endpoint->plan($subscription_plan_id); + $price = $subscription_plan->billing_cycles()[0]->pricing_scheme()['fixed_price']['value'] ?? ''; + if($price && round($price, 2) !== round($product->get_price(), 2)) { + $this->billing_plans_endpoint->update_pricing( + $subscription_plan_id, + $this->billing_cycle_factory->from_wc_product($product) + ); + } + } + } catch (RuntimeException $exception) { + $error = $exception->getMessage(); + if ( is_a( $exception, PayPalApiException::class ) ) { + $error = $exception->get_details( $error ); + } + + $this->logger->error( 'Could not update subscription plan on PayPal. ' . $error ); + } } } diff --git a/tests/playwright/subscriptions-api.spec.js b/tests/playwright/subscriptions-api.spec.js index 5ef95e9ad..aa24a9aae 100644 --- a/tests/playwright/subscriptions-api.spec.js +++ b/tests/playwright/subscriptions-api.spec.js @@ -7,7 +7,8 @@ const { } = process.env; test.describe.serial('Subscriptions Merchant', () => { - const title = (Math.random() + 1).toString(36).substring(7); + const productTitle = (Math.random() + 1).toString(36).substring(7); + const planName = (Math.random() + 1).toString(36).substring(7); let product_id = ''; let plan_id = ''; @@ -15,9 +16,11 @@ test.describe.serial('Subscriptions Merchant', () => { await loginAsAdmin(page); await page.goto('/wp-admin/post-new.php?post_type=product'); - await page.fill('#title', title); + await page.fill('#title', productTitle); await page.selectOption('select#product-type', 'subscription'); await page.fill('#_subscription_price', '10'); + await page.locator('#ppcp_enable_subscription_product').check(); + await page.fill('#ppcp_subscription_plan_name', planName); await Promise.all([ page.waitForNavigation(), @@ -37,7 +40,7 @@ test.describe.serial('Subscriptions Merchant', () => { const productList = await products.json(); const product = productList.products.find((p) => { - return p.name === title; + return p.name === productTitle; }); await expect(product.id).toBeTruthy; @@ -64,8 +67,9 @@ test.describe.serial('Subscriptions Merchant', () => { await loginAsAdmin(page); await page.goto('/wp-admin/edit.php?post_type=product'); - await page.getByRole('link', { name: title, exact: true }).click(); + await page.getByRole('link', { name: productTitle, exact: true }).click(); + await page.fill('#title', `Updated ${productTitle}`); await page.fill('#_subscription_price', '20'); await Promise.all([ @@ -76,6 +80,20 @@ test.describe.serial('Subscriptions Merchant', () => { const message = await page.locator('.notice-success'); await expect(message).toContainText('Product updated.'); + const products = await request.get('https://api.sandbox.paypal.com/v1/catalogs/products?page_size=100&page=1&total_required=true', { + headers: { + 'Authorization': AUTHORIZATION, + 'Content-Type': 'application/json' + } + }); + expect(products.ok()).toBeTruthy(); + + const productList = await products.json(); + const product = productList.products.find((p) => { + return p.name === `Updated ${productTitle}`; + }); + await expect(product.id).toBeTruthy; + const plan = await request.get(`https://api.sandbox.paypal.com/v1/billing/plans/${plan_id}`, { headers: { 'Authorization': AUTHORIZATION,