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..a444d02 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,39 @@ public function process_payment($order_id) ]; } + /** + * 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(); + } + + // 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); + 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 ); + } + } + + 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..f702f57 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.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 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);