diff --git a/includes/class-wc-payment-gateways.php b/includes/class-wc-payment-gateways.php index 274db37a187..9903c3e3113 100644 --- a/includes/class-wc-payment-gateways.php +++ b/includes/class-wc-payment-gateways.php @@ -75,7 +75,11 @@ class WC_Payment_Gateways { if ( 'US' === WC()->countries->get_base_country() ) { if ( class_exists( 'WC_Subscriptions_Order' ) || class_exists( 'WC_Pre_Orders_Order' ) ) { - $load_gateways[] = 'WC_Addons_Gateway_Simplify_Commerce'; + if ( ! function_exists( 'wcs_create_renewal_order' ) ) { // Subscriptions < 2.0 + $load_gateways[] = 'WC_Addons_Gateway_Simplify_Commerce_Deprecated'; + } else { + $load_gateways[] = 'WC_Addons_Gateway_Simplify_Commerce'; + } } else { $load_gateways[] = 'WC_Gateway_Simplify_Commerce'; } diff --git a/includes/gateways/simplify-commerce-deprecated/class-wc-addons-gateway-simplify-commerce-deprecated.php b/includes/gateways/simplify-commerce-deprecated/class-wc-addons-gateway-simplify-commerce-deprecated.php new file mode 100644 index 00000000000..874d962429d --- /dev/null +++ b/includes/gateways/simplify-commerce-deprecated/class-wc-addons-gateway-simplify-commerce-deprecated.php @@ -0,0 +1,177 @@ +id, array( $this, 'process_scheduled_subscription_payment' ), 10, 3 ); + add_filter( 'woocommerce_subscriptions_renewal_order_meta_query', array( $this, 'remove_renewal_order_meta' ), 10, 4 ); + add_action( 'woocommerce_subscriptions_changed_failing_payment_method_' . $this->id, array( $this, 'change_failing_payment_method' ), 10, 3 ); + } + } + + /** + * Store the customer and card IDs on the order and subscriptions in the order + * + * @param int $order_id + * @param string $customer_id + * @return array + */ + protected function save_subscription_meta( $order_id, $customer_id ) { + update_post_meta( $order_id, '_simplify_customer_id', wc_clean( $customer_id ) ); + } + + /** + * process_subscription_payment function. + * + * @param WC_order $order + * @param integer $amount (default: 0) + * @uses Simplify_BadRequestException + * @return bool|WP_Error + */ + public function process_subscription_payment( $order, $amount = 0 ) { + if ( 0 == $amount ) { + // Payment complete + $order->payment_complete(); + + return true; + } + + if ( $amount * 100 < 50 ) { + return new WP_Error( 'simplify_error', __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce' ) ); + } + + $order_items = $order->get_items(); + $order_item = array_shift( $order_items ); + $subscription_name = sprintf( __( '%s - Subscription for "%s"', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order_item['name'] ) . ' ' . sprintf( __( '(Order #%s)', 'woocommerce' ), $order->get_order_number() ); + + $customer_id = get_post_meta( $order->id, '_simplify_customer_id', true ); + + if ( ! $customer_id ) { + return new WP_Error( 'simplify_error', __( 'Customer not found', 'woocommerce' ) ); + } + + try { + // Charge the customer + $payment = Simplify_Payment::createPayment( array( + 'amount' => $amount * 100, // In cents + 'customer' => $customer_id, + 'description' => trim( substr( $subscription_name, 0, 1024 ) ), + 'currency' => strtoupper( get_woocommerce_currency() ), + 'reference' => $order->id, + 'card.addressCity' => $order->billing_city, + 'card.addressCountry' => $order->billing_country, + 'card.addressLine1' => $order->billing_address_1, + 'card.addressLine2' => $order->billing_address_2, + 'card.addressState' => $order->billing_state, + 'card.addressZip' => $order->billing_postcode + ) ); + + } catch ( Exception $e ) { + + $error_message = $e->getMessage(); + + if ( $e instanceof Simplify_BadRequestException && $e->hasFieldErrors() && $e->getFieldErrors() ) { + $error_message = ''; + foreach ( $e->getFieldErrors() as $error ) { + $error_message .= ' ' . $error->getFieldName() . ': "' . $error->getMessage() . '" (' . $error->getErrorCode() . ')'; + } + } + + $order->add_order_note( sprintf( __( 'Simplify payment error: %s', 'woocommerce' ), $error_message ) ); + + return new WP_Error( 'simplify_payment_declined', $e->getMessage(), array( 'status' => $e->getCode() ) ); + } + + if ( 'APPROVED' == $payment->paymentStatus ) { + // Payment complete + $order->payment_complete( $payment->id ); + + // Add order note + $order->add_order_note( sprintf( __( 'Simplify payment approved (ID: %s, Auth Code: %s)', 'woocommerce' ), $payment->id, $payment->authCode ) ); + + return true; + } else { + $order->add_order_note( __( 'Simplify payment declined', 'woocommerce' ) ); + + return new WP_Error( 'simplify_payment_declined', __( 'Payment was declined - please try another card.', 'woocommerce' ) ); + } + } + + /** + * process_scheduled_subscription_payment function. + * + * @param float $amount_to_charge The amount to charge. + * @param WC_Order $order The WC_Order object of the order which the subscription was purchased in. + * @param int $product_id The ID of the subscription product for which this payment relates. + */ + public function process_scheduled_subscription_payment( $amount_to_charge, $order, $product_id ) { + $result = $this->process_subscription_payment( $order, $amount_to_charge ); + + if ( is_wp_error( $result ) ) { + WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order, $product_id ); + } else { + WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ); + } + } + + /** + * Don't transfer customer meta when creating a parent renewal order. + * + * @param string $order_meta_query MySQL query for pulling the metadata + * @param int $original_order_id Post ID of the order being used to purchased the subscription being renewed + * @param int $renewal_order_id Post ID of the order created for renewing the subscription + * @param string $new_order_role The role the renewal order is taking, one of 'parent' or 'child' + * @return string + */ + public function remove_renewal_order_meta( $order_meta_query, $original_order_id, $renewal_order_id, $new_order_role ) { + if ( 'parent' == $new_order_role ) { + $order_meta_query .= " AND `meta_key` NOT LIKE '_simplify_customer_id' "; + } + + return $order_meta_query; + } + + /** + * Check if order contains subscriptions. + * + * @param int $order_id + * @return bool + */ + protected function order_contains_subscription( $order_id ) { + return class_exists( 'WC_Subscriptions_Order' ) && ( WC_Subscriptions_Order::order_contains_subscription( $order_id ) || WC_Subscriptions_Renewal_Order::is_renewal( $order_id ) ); + } + + /** + * Update the customer_id for a subscription after using Simplify to complete a payment to make up for + * an automatic renewal payment which previously failed. + * + * @param WC_Order $original_order The original order in which the subscription was purchased. + * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). + * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() + */ + public function change_failing_payment_method( $original_order, $renewal_order, $subscription_key ) { + $new_customer_id = get_post_meta( $renewal_order->id, '_simplify_customer_id', true ); + + update_post_meta( $original_order->id, '_simplify_customer_id', $new_customer_id ); + } +} diff --git a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php index a1387ed97dc..09a24b13a80 100644 --- a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php +++ b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php @@ -23,9 +23,14 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { parent::__construct(); if ( class_exists( 'WC_Subscriptions_Order' ) ) { - add_action( 'scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 3 ); - add_filter( 'woocommerce_subscriptions_renewal_order_meta_query', array( $this, 'remove_renewal_order_meta' ), 10, 4 ); - add_action( 'woocommerce_subscriptions_changed_failing_payment_method_' . $this->id, array( $this, 'update_failing_payment_method' ), 10, 3 ); + add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 ); + add_action( 'woocommerce_subscription_failing_payment_method_updated_' . $this->id, array( $this, 'update_failing_payment_method' ), 10, 2 ); + + add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 ); + + // Allow store managers to manually set Simplify as the payment method on a subscription + add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 ); + add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 ); } if ( class_exists( 'WC_Pre_Orders_Order' ) ) { @@ -60,7 +65,7 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { * @return bool */ protected function order_contains_subscription( $order_id ) { - return class_exists( 'WC_Subscriptions_Order' ) && ( WC_Subscriptions_Order::order_contains_subscription( $order_id ) || WC_Subscriptions_Renewal_Order::is_renewal( $order_id ) ); + return function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ); } /** @@ -103,18 +108,14 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { ) ); if ( is_object( $customer ) && '' != $customer->id ) { - $customer_id = wc_clean( $customer->id ); - - // Store the customer ID in the order - update_post_meta( $order->id, '_simplify_customer_id', $customer_id ); + $this->save_subscription_meta( $order->id, $customer->id ); } else { $error_msg = __( 'Error creating user in Simplify Commerce.', 'woocommerce' ); throw new Simplify_ApiException( $error_msg ); } - $initial_payment = WC_Subscriptions_Order::get_total_initial_payment( $order ); - $payment_response = $this->process_subscription_payment( $order, $initial_payment ); + $payment_response = $this->process_subscription_payment( $order, $order->get_total() ); if ( is_wp_error( $payment_response ) ) { throw new Exception( $payment_response->get_error_message() ); @@ -145,6 +146,24 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { } } + /** + * Store the customer and card IDs on the order and subscriptions in the order + * + * @param int $order_id + * @param string $customer_id + */ + protected function save_subscription_meta( $order_id, $customer_id ) { + + $customer_id = wc_clean( $customer_id ); + + update_post_meta( $order_id, '_simplify_customer_id', $customer_id ); + + // Also store it on the subscriptions being purchased in the order + foreach( wcs_get_subscriptions_for_order( $order_id ) as $subscription ) { + update_post_meta( $subscription->id, '_simplify_customer_id', $customer_id ); + } + } + /** * Process the pre-order * @@ -239,7 +258,7 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { $order = wc_get_order( $order_id ); // Processing subscription - if ( 'standard' == $this->mode && $this->order_contains_subscription( $order->id ) ) { + if ( 'standard' == $this->mode && ( $this->order_contains_subscription( $order->id ) || ( function_exists( 'wcs_is_subscription' ) && wcs_is_subscription( $order_id ) ) ) ) { return $this->process_subscription( $order, $cart_token ); // Processing pre-order @@ -272,10 +291,6 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { return new WP_Error( 'simplify_error', __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce' ) ); } - $order_items = $order->get_items(); - $order_item = array_shift( $order_items ); - $subscription_name = sprintf( __( '%s - Subscription for "%s"', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order_item['name'] ) . ' ' . sprintf( __( '(Order #%s)', 'woocommerce' ), $order->get_order_number() ); - $customer_id = get_post_meta( $order->id, '_simplify_customer_id', true ); if ( ! $customer_id ) { @@ -287,7 +302,7 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { $payment = Simplify_Payment::createPayment( array( 'amount' => $amount * 100, // In cents 'customer' => $customer_id, - 'description' => trim( substr( $subscription_name, 0, 1024 ) ), + 'description' => sprintf( __( '%s - Order #%s', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order->get_order_number() ), 'currency' => strtoupper( get_woocommerce_currency() ), 'reference' => $order->id, 'card.addressCity' => $order->billing_city, @@ -333,48 +348,76 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { * scheduled_subscription_payment function. * * @param float $amount_to_charge The amount to charge. - * @param WC_Order $order The WC_Order object of the order which the subscription was purchased in. - * @param int $product_id The ID of the subscription product for which this payment relates. + * @param WC_Order $renewal_order A WC_Order object created to record the renewal payment. */ - public function scheduled_subscription_payment( $amount_to_charge, $order, $product_id ) { - $result = $this->process_subscription_payment( $order, $amount_to_charge ); + public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) { + $result = $this->process_subscription_payment( $renewal_order, $amount_to_charge ); if ( is_wp_error( $result ) ) { - WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order, $product_id ); - } else { - WC_Subscriptions_Manager::process_subscription_payments_on_order( $order ); + $renewal_order->update_status( 'failed', sprintf( __( 'Simplify Transaction Failed (%s)', 'woocommerce' ), $result->get_error_message() ) ); } } - /** - * Don't transfer customer meta when creating a parent renewal order. - * - * @param string $order_meta_query MySQL query for pulling the metadata - * @param int $original_order_id Post ID of the order being used to purchased the subscription being renewed - * @param int $renewal_order_id Post ID of the order created for renewing the subscription - * @param string $new_order_role The role the renewal order is taking, one of 'parent' or 'child' - * @return string - */ - public function remove_renewal_order_meta( $order_meta_query, $original_order_id, $renewal_order_id, $new_order_role ) { - if ( 'parent' == $new_order_role ) { - $order_meta_query .= " AND `meta_key` NOT LIKE '_simplify_customer_id' "; - } - - return $order_meta_query; - } - /** * Update the customer_id for a subscription after using Simplify to complete a payment to make up for * an automatic renewal payment which previously failed. * - * @param WC_Order $original_order The original order in which the subscription was purchased. + * @param WC_Subscription $subscription The subscription for which the failing payment method relates. * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). - * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() */ - public function update_failing_payment_method( $original_order, $renewal_order, $subscription_key ) { - $new_customer_id = get_post_meta( $renewal_order->id, '_simplify_customer_id', true ); + public function update_failing_payment_method( $subscription, $renewal_order ) { + update_post_meta( $original_order->id, '_simplify_customer_id', get_post_meta( $renewal_order->id, '_simplify_customer_id', true ) ); + } - update_post_meta( $original_order->id, '_simplify_customer_id', $new_customer_id ); + /** + * Include the payment meta data required to process automatic recurring payments so that store managers can + * manually set up automatic recurring payments for a customer via the Edit Subscription screen in Subscriptions v2.0+. + * + * @since 2.4 + * @param array $payment_meta associative array of meta data required for automatic payments + * @param WC_Subscription $subscription An instance of a subscription object + * @return array + */ + public function add_subscription_payment_meta( $payment_meta, $subscription ) { + + $payment_meta[ $this->id ] = array( + 'post_meta' => array( + '_simplify_customer_id' => array( + 'value' => get_post_meta( $subscription->id, '_simplify_customer_id', true ), + 'label' => 'Simplify Customer ID', + ), + ), + ); + + return $payment_meta; + } + + /** + * Validate the payment meta data required to process automatic recurring payments so that store managers can + * manually set up automatic recurring payments for a customer via the Edit Subscription screen in Subscriptions 2.0+. + * + * @since 2.4 + * @param string $payment_method_id The ID of the payment method to validate + * @param array $payment_meta associative array of meta data required for automatic payments + * @return array + */ + public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) { + if ( $this->id === $payment_method_id ) { + if ( ! isset( $payment_meta['post_meta']['_simplify_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_simplify_customer_id']['value'] ) ) { + throw new Exception( 'A "_simplify_customer_id" value is required.' ); + } + } + } + + /** + * Don't transfer customer meta to resubscribe orders. + * + * @access public + * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription + * @return void + */ + public function delete_resubscribe_meta( $resubscribe_order ) { + delete_post_meta( $resubscribe_order->id, '_simplify_customer_id' ); } /** diff --git a/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php b/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php index f48d0504831..9515cd255ea 100644 --- a/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php +++ b/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php @@ -31,8 +31,11 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway { 'subscription_reactivation', 'subscription_suspension', 'subscription_amount_changes', - 'subscription_payment_method_change', + 'subscription_payment_method_change', // Subscriptions 1.n compatibility + 'subscription_payment_method_change_customer', + 'subscription_payment_method_change_admin', 'subscription_date_changes', + 'multiple_subscriptions', 'default_credit_card_form', 'refunds', 'pre-orders'