From 5ac2c865beaace78b4e782b643f8241102e22ca0 Mon Sep 17 00:00:00 2001 From: James Dunn Date: Sat, 23 Apr 2022 23:47:57 +0900 Subject: [PATCH 1/6] happy case - single subscription order WIP implementation --- Dockerfile | 3 + class-wc-gateway-komoju.php | 103 +++++++++++++++--- docker-compose.yml | 2 +- .../class-wc-gateway-komoju-ipn-handler.php | 11 ++ .../komoju-php/lib/komoju/KomojuApi.php | 5 + 5 files changed, 109 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 86dacf2..0552ddc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,14 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends unzip wget gettext vim \ && wget https://downloads.wordpress.org/plugin/woocommerce.${woocommerce_version}.zip -O /tmp/woocommerce.zip \ && wget https://downloads.wordpress.org/plugin/relative-url.0.1.7.zip -O /tmp/relative-url.zip \ + && wget https://github.com/wp-premium/woocommerce-subscriptions/archive/refs/tags/3.0.1.zip -O /tmp/woocommerce-subscriptions.zip \ && cd /usr/src/wordpress/wp-content/plugins \ && unzip /tmp/woocommerce.zip \ && unzip /tmp/relative-url.zip \ + && unzip /tmp/woocommerce-subscriptions.zip \ && rm /tmp/woocommerce.zip \ && rm /tmp/relative-url.zip \ + && rm /tmp/woocommerce-subscriptions.zip \ && rm -rf /var/lib/apt/lists/* COPY --from=composer /usr/bin/composer /usr/bin/composer diff --git a/class-wc-gateway-komoju.php b/class-wc-gateway-komoju.php index bca47a9..00a57ed 100755 --- a/class-wc-gateway-komoju.php +++ b/class-wc-gateway-komoju.php @@ -43,6 +43,20 @@ public function __construct() $this->secretKey = $this->get_option('secretKey'); $this->webhookSecretToken = $this->get_option('webhookSecretToken'); $this->komoju_api = new KomojuApi($this->secretKey); + + // enable subscriptions + $this->supports = array( + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_changes', + 'subscription_payment_method_change_admin', + 'multiple_subscriptions', + ); + self::$log_enabled = $this->debug; // Load the settings. @@ -58,6 +72,7 @@ public function __construct() // Filters // Actions add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']); + add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'process_subscription' ), 10, 3 ); if (!$this->is_valid_for_use()) { $this->enabled = 'no'; WC_Gateway_Komoju::log('is not valid for use. No IPN set.'); @@ -134,6 +149,7 @@ public function process_payment($order_id) $payment_method = [sanitize_text_field($_POST['komoju-method'])]; $return_url = $this->get_mydefault_api_url(); + // construct line items $line_items = []; foreach ($order->get_items() as $item) { @@ -172,21 +188,44 @@ public function process_payment($order_id) ]; } - // new session + // --- construct a KOMOJU payment session --- // $komoju_api = $this->komoju_api; - $komoju_request = $komoju_api->createSession([ - 'return_url' => $return_url, - 'default_locale' => $this->get_locale_or_fallback(), - 'payment_types' => $payment_method, - 'payment_data' => [ - 'amount' => $order->get_total(), - 'currency' => get_woocommerce_currency(), - 'external_order_num' => $this->external_order_num($order), - 'billing_address' => $billing_address, - 'shipping_address' => $shipping_address, - ], - 'line_items' => $line_items, - ]); + + // check if order contains a subscription - will need customer mode on KOMOJU to process + if ( class_exists("WC_Subscriptions_Order") && WC_Subscriptions_Order::order_contains_subscription( $order_id )) { + // customer mode session + + $return_url_with_id = add_query_arg( array('external_order_num' => $order_id), $return_url ); + $komoju_request = $komoju_api->createSession([ + 'mode' => 'customer', + 'return_url' => $return_url_with_id, + 'default_locale' => $this->get_locale_or_fallback(), + 'payment_types' => $payment_method, + 'payment_data' => [ + 'amount' => $order->get_total(), + 'currency' => get_woocommerce_currency(), + 'external_order_num' => $this->external_order_num($order), + 'billing_address' => $billing_address, + 'shipping_address' => $shipping_address, + ], + 'line_items' => $line_items, + ]); + } else { + // regular session + $komoju_request = $komoju_api->createSession([ + 'return_url' => $return_url, + 'default_locale' => $this->get_locale_or_fallback(), + 'payment_types' => $payment_method, + 'payment_data' => [ + 'amount' => $order->get_total(), + 'currency' => get_woocommerce_currency(), + 'external_order_num' => $this->external_order_num($order), + 'billing_address' => $billing_address, + 'shipping_address' => $shipping_address, + ], + 'line_items' => $line_items, + ]); + } return [ 'result' => 'success', @@ -194,6 +233,42 @@ public function process_payment($order_id) ]; } + /** + * Process an incoming subscription charge + */ + public function process_subscription($amount_to_charge, $order) { + // NOTE: this seems ridiculous but I couldn't figure out another way to get the subscription from the order here + foreach ( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ) as $subscription ) { + $parent_order_id = $subscription->get_parent_id(); + } + + // fetch token from the subscription's parent order + $token = get_post_meta($parent_order_id, 'komoju_payment_token', true); + if (empty($token)) { + $this->log('ERROR: token missing on subscription payment metadata'); + WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order); + return; + } + try { + $komoju_request = $this->create_komoju_payment($token, $order); + var_dump($komoju_request); + WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ); + } catch (Exception $e) { + var_dump($e); + $order->add_order_note("KOMOJU Subscription payment failed"); + WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order ); + } + } + + public function create_komoju_payment($customer, $order) { + return $this->komoju_api->createPayment([ + 'amount' => $order->get_total(), + 'external_order_num' => $this->external_order_num($order), + 'currency' => get_woocommerce_currency(), + 'customer' => $customer, + ]); + } + /** * Payment form on checkout page */ diff --git a/docker-compose.yml b/docker-compose.yml index 928d5ae..a442d9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: context: . dockerfile: Dockerfile args: - woocommerce_version: 4.0.1 + woocommerce_version: 4.1.0 ports: - "8000:80" restart: always diff --git a/includes/class-wc-gateway-komoju-ipn-handler.php b/includes/class-wc-gateway-komoju-ipn-handler.php index c5f2744..1a6bd88 100755 --- a/includes/class-wc-gateway-komoju-ipn-handler.php +++ b/includes/class-wc-gateway-komoju-ipn-handler.php @@ -41,6 +41,17 @@ public function check_response() // callback from session page if (isset($_GET['session_id'])) { $session = $this->get_session($_GET['session_id']); + // session was customer mode + if (!is_null($session->customer_id)) { + $external_order_num_param = $_GET['external_order_num']; // only passed in customer mode + update_post_meta($external_order_num_param, 'komoju_payment_token', wc_clean($session->customer_id)); + // create initial payment for parent order of subscription + $this->gateway->create_komoju_payment($session->customer_id, wc_get_order($external_order_num_param)); + $success_url = $this->gateway->get_return_url($order); + wp_redirect($success_url); + exit; + } + $order = $this->get_order_from_komoju_session($session, $this->invoice_prefix); // null payment on a session indicates incomplete payment flow diff --git a/komoju-php/komoju-php/lib/komoju/KomojuApi.php b/komoju-php/komoju-php/lib/komoju/KomojuApi.php index 61ea91e..c2df658 100644 --- a/komoju-php/komoju-php/lib/komoju/KomojuApi.php +++ b/komoju-php/komoju-php/lib/komoju/KomojuApi.php @@ -29,6 +29,11 @@ public function session($sessionUuid) return $this->get('/api/v1/sessions/' . $sessionUuid); } + public function createPayment($payload) + { + return $this->post('/api/v1/payments', $payload); + } + private function get($uri) { $ch = curl_init($this->endpoint . $uri); From 7d95df7fcaf43cec86ded7731726dc4669e1e281 Mon Sep 17 00:00:00 2001 From: James Dunn Date: Sun, 24 Apr 2022 00:15:37 +0900 Subject: [PATCH 2/6] remvoe some debug code --- class-wc-gateway-komoju.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/class-wc-gateway-komoju.php b/class-wc-gateway-komoju.php index 00a57ed..a444d02 100755 --- a/class-wc-gateway-komoju.php +++ b/class-wc-gateway-komoju.php @@ -237,7 +237,6 @@ public function process_payment($order_id) * Process an incoming subscription charge */ public function process_subscription($amount_to_charge, $order) { - // NOTE: this seems ridiculous but I couldn't figure out another way to get the subscription from the order here foreach ( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ) as $subscription ) { $parent_order_id = $subscription->get_parent_id(); } @@ -251,10 +250,8 @@ public function process_subscription($amount_to_charge, $order) { } try { $komoju_request = $this->create_komoju_payment($token, $order); - var_dump($komoju_request); WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ); } catch (Exception $e) { - var_dump($e); $order->add_order_note("KOMOJU Subscription payment failed"); WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order ); } From d30e80786d37bc7583a7a083fd8d28dd499f4515 Mon Sep 17 00:00:00 2001 From: James Dunn Date: Mon, 16 May 2022 02:37:09 +0900 Subject: [PATCH 3/6] testing instructions --- docker-compose.yml | 2 +- docs/dev_setup.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a442d9a..f702f57 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: context: . dockerfile: Dockerfile args: - woocommerce_version: 4.1.0 + woocommerce_version: 4.4.0 ports: - "8000:80" restart: always diff --git a/docs/dev_setup.md b/docs/dev_setup.md index 6250565..8787edd 100644 --- a/docs/dev_setup.md +++ b/docs/dev_setup.md @@ -81,3 +81,14 @@ To be able to test the checkout you will first need to have a purchasable produc If you go to http://127.0.0.1:8000/?post_type=product you should be able to see the shop, with an item for purchase you can use to test the Komoju integration. + +## Testing subscriptions + +1. Follow the setup instructions above +2. Confirm that the woocommerce subscriptions plugin appears on the list of installed plugins, and that it is enabled. +3. Create a new product in the same manner as above, but this time choose "Simple Subscription" for product type +4. select the price and billing cadence +5. go through the normal checkout flow for the store, with the new subscription product in the cart +6. complete payment flow +7. go to the "subscriptions" tab under woocommerce category on the admin and confirm the presence of the subscription +8. to test subscription renewals - go to the newly created subscription in the subscriptions tab, and select "Process renewal" under subscription actions at the top right of the page From b68927ca5283fed1018cf6136994e9aa946804da Mon Sep 17 00:00:00 2001 From: Nigel Baillie Date: Mon, 27 Jun 2022 15:27:10 +0900 Subject: [PATCH 4/6] Only support subscription for CC and paypay --- class-wc-gateway-komoju.php | 13 ------------- includes/class-wc-gateway-komoju-single-slug.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/class-wc-gateway-komoju.php b/class-wc-gateway-komoju.php index 6f8f152..743155e 100755 --- a/class-wc-gateway-komoju.php +++ b/class-wc-gateway-komoju.php @@ -44,19 +44,6 @@ public function __construct() $this->komoju_api = new KomojuApi($this->secretKey); self::$log_enabled = $this->debug; - // enable subscriptions - array_push($this->supports, - 'subscriptions', - 'subscription_cancellation', - 'subscription_suspension', - 'subscription_reactivation', - 'subscription_amount_changes', - 'subscription_date_changes', - 'subscription_payment_method_changes', - 'subscription_payment_method_change_admin', - 'multiple_subscriptions', - ); - // Load the settings. $this->init_form_fields(); $this->init_settings(); diff --git a/includes/class-wc-gateway-komoju-single-slug.php b/includes/class-wc-gateway-komoju-single-slug.php index 524a2c2..75d18b1 100644 --- a/includes/class-wc-gateway-komoju-single-slug.php +++ b/includes/class-wc-gateway-komoju-single-slug.php @@ -31,6 +31,20 @@ public function __construct($payment_method) $this->supports[] = 'refunds'; } + if (in_array($slug, ['credit_card', 'paypay'])) { + array_push($this->supports, + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_changes', + 'subscription_payment_method_change_admin', + 'multiple_subscriptions', + ); + } + parent::__construct(); } From 1af24d351c02b21c9bf193f6fff71ad1a9fd1d35 Mon Sep 17 00:00:00 2001 From: Nigel Baillie Date: Mon, 27 Jun 2022 15:32:10 +0900 Subject: [PATCH 5/6] Linter fix --- class-wc-gateway-komoju.php | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/class-wc-gateway-komoju.php b/class-wc-gateway-komoju.php index 743155e..d87b3b2 100755 --- a/class-wc-gateway-komoju.php +++ b/class-wc-gateway-komoju.php @@ -57,7 +57,7 @@ public function __construct() // Filters // Actions add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']); - add_action('woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'process_subscription' ), 10, 3); + add_action('woocommerce_scheduled_subscription_payment_' . $this->id, [$this, 'process_subscription'], 10, 3); if ($this->id === 'komoju') { include_once 'includes/class-wc-gateway-komoju-ipn-handler.php'; @@ -132,7 +132,6 @@ public function process_payment($order_id, $payment_type = null) $payment_type = sanitize_text_field($_POST['komoju-method']); } - // construct line items $line_items = []; foreach ($order->get_items() as $item) { @@ -194,7 +193,7 @@ public function process_payment($order_id, $payment_type = null) ], 'line_items' => $line_items, ]; - if ( class_exists("WC_Subscriptions_Order") && WC_Subscriptions_Order::order_contains_subscription( $order_id )) { + if (class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($order_id)) { $session_params['mode'] = 'customer'; } @@ -213,11 +212,12 @@ public function process_payment($order_id, $payment_type = null) } /** - * Process an incoming subscription charge + * Process an incoming subscription charge */ - public function process_subscription($amount_to_charge, $order) { - foreach ( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ) as $subscription ) { - $parent_order_id = $subscription->get_parent_id(); + public function process_subscription($amount_to_charge, $order) + { + foreach (wcs_get_subscriptions_for_order($order, ['order_type' => 'any']) as $subscription) { + $parent_order_id = $subscription->get_parent_id(); } // fetch token from the subscription's parent order @@ -225,24 +225,26 @@ public function process_subscription($amount_to_charge, $order) { if (empty($token)) { $this->log('ERROR: token missing on subscription payment metadata'); WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order); + return; } try { $komoju_request = $this->create_komoju_payment($token, $order); - WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ); + WC_Subscriptions_Manager::process_subscription_payments_on_order($order); } catch (Exception $e) { - $order->add_order_note("KOMOJU Subscription payment failed"); - WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order ); + $order->add_order_note('KOMOJU Subscription payment failed'); + WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order); } } - public function create_komoju_payment($customer, $order) { - return $this->komoju_api->createPayment([ - 'amount' => $order->get_total(), - 'external_order_num' => $this->external_order_num($order), - 'currency' => get_woocommerce_currency(), - 'customer' => $customer, - ]); + public function create_komoju_payment($customer, $order) + { + return $this->komoju_api->createPayment([ + 'amount' => $order->get_total(), + 'external_order_num' => $this->external_order_num($order), + 'currency' => get_woocommerce_currency(), + 'customer' => $customer, + ]); } /** From 051576622a1fa83f2f730689213e33cec7766ab3 Mon Sep 17 00:00:00 2001 From: Nigel Baillie Date: Mon, 27 Jun 2022 15:38:01 +0900 Subject: [PATCH 6/6] Use cents for subscription charges --- class-wc-gateway-komoju.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/class-wc-gateway-komoju.php b/class-wc-gateway-komoju.php index d87b3b2..36a1079 100755 --- a/class-wc-gateway-komoju.php +++ b/class-wc-gateway-komoju.php @@ -239,10 +239,12 @@ public function process_subscription($amount_to_charge, $order) public function create_komoju_payment($customer, $order) { + $currency = $order->get_currency(); + return $this->komoju_api->createPayment([ - 'amount' => $order->get_total(), + 'amount' => self::to_cents($order->get_total(), $currency), 'external_order_num' => $this->external_order_num($order), - 'currency' => get_woocommerce_currency(), + 'currency' => $currency, 'customer' => $customer, ]); }