--- a/peachpay-for-woocommerce/core/payments/convesiopay/gateways/class-peachpay-convesiopay-card-gateway.php
+++ b/peachpay-for-woocommerce/core/payments/convesiopay/gateways/class-peachpay-convesiopay-card-gateway.php
@@ -47,7 +47,20 @@
$this->method_title = sprintf( __( '%s via ConvesioPay (PeachPay)', 'peachpay-for-woocommerce' ), 'Card' );
$this->method_description = 'Accept card payments through ConvesioPay. Note: ConvesioPay only supports USD currency.';
$this->has_fields = true;
- $this->supports = array( 'products', 'refunds', 'blocks' );
+ $this->supports = array(
+ 'products',
+ 'refunds',
+ 'blocks',
+ 'tokenization',
+ 'subscriptions',
+ 'multiple_subscriptions',
+ 'subscription_cancellation',
+ 'subscription_suspension',
+ 'subscription_reactivation',
+ 'subscription_amount_changes',
+ 'subscription_date_changes',
+ 'subscription_payment_method_change_customer',
+ );
// Set up basic icon (no function dependencies)
$this->icon = '';
@@ -68,6 +81,26 @@
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ // Subscription renewal support
+ $gateway = $this;
+ add_action(
+ 'woocommerce_scheduled_subscription_payment_' . $this->id,
+ function ( $renewal_total, $renewal_order ) use ( $gateway ) {
+ if ( ! function_exists( 'wcs_get_subscriptions_for_renewal_order' ) ) {
+ return;
+ }
+ $subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order );
+ $subscription = array_pop( $subscriptions );
+ if ( ! $subscription ) {
+ return;
+ }
+ $parent_order = wc_get_order( $subscription->get_parent_id() );
+ $gateway->process_subscription_renewal( $parent_order, $renewal_order, $renewal_total );
+ },
+ 10,
+ 2
+ );
+
// Initialize form fields
$this->init_form_fields();
// Mark as initialized
@@ -236,6 +269,7 @@
* Get transaction ID from request - unified method for both classic and blocks checkout
*/
private function get_transaction_id_from_request() {
+ $transaction_id = '';
// Method 1: Classic checkout - check POST data
if ( isset( $_POST['peachpay_transaction_id'] ) && !empty( $_POST['peachpay_transaction_id'] ) ) {
@@ -614,7 +648,8 @@
'currency' => 'USD', // ConvesioPay only supports USD
'email' => $order->get_billing_email(),
'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
- );
+ 'storePaymentMethod' => true, // Enable Card On File for subscription renewals
+ );
// Make API call to ConvesioPay
$api_url = $config['api_url'] . '/payments';
@@ -664,6 +699,37 @@
// Store ConvesioPay payment ID for later capture/cancel operations
$order->update_meta_data( '_convesiopay_payment_id', $transaction_id );
+
+ // Store customer ID and payment method for subscription renewals.
+ // NOTE: The storedPaymentMethodId is typically NOT returned in the initial /payments
+ // API response. It is provided in the Payment Webhook inside paymentMethodDetails.
+ // The webhook handler (class-peachpay-convesiopay-webhook.php) will populate these
+ // fields when processing the payment.succeeded webhook event. This code serves as
+ // a fallback in case the API response format changes in the future.
+ $convesiopay_customer_id = $response_data['customerId'] ?? '';
+ $convesiopay_stored_payment_method_id = $response_data['storedPaymentMethodId'] ?? '';
+
+ if ( ! empty( $convesiopay_customer_id ) ) {
+ $order->update_meta_data( '_convesiopay_customer_id', $convesiopay_customer_id );
+ // Also store on user meta for future orders
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_customer_id', $convesiopay_customer_id );
+ }
+ }
+
+ // Store the storedPaymentMethodId for subscription renewals (if present in response)
+ if ( ! empty( $convesiopay_stored_payment_method_id ) ) {
+ $order->update_meta_data( '_convesiopay_stored_payment_method_id', $convesiopay_stored_payment_method_id );
+ // Also store on user meta for subscription renewals
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_stored_payment_method_id', $convesiopay_stored_payment_method_id );
+ // Store payment method details for display
+ $payment_details = $response_data['paymentMethodDetails'] ?? array();
+ update_user_meta( $user_id, '_convesiopay_payment_method_details', $payment_details );
+ }
+ }
if ( $status === 'Succeeded' ) {
// Payment captured immediately
@@ -977,4 +1043,86 @@
private function is_blocks_supported() {
return class_exists( 'AutomatticWooCommerceBlocksPaymentsIntegrationsAbstractPaymentMethodType' );
}
+
+ /**
+ * Process subscription renewal payment.
+ * Called by WooCommerce Subscriptions when a renewal is due.
+ *
+ * @param WC_Order $parent_order The original subscription order.
+ * @param WC_Order $renewal_order The renewal order to charge.
+ * @param float $renewal_total The amount to charge.
+ */
+ public function process_subscription_renewal( $parent_order, $renewal_order, $renewal_total ) {
+ try {
+ // Get stored payment data from parent order
+ $customer_id = $parent_order->get_meta( '_convesiopay_customer_id' );
+ $stored_payment_method_id = $parent_order->get_meta( '_convesiopay_stored_payment_method_id' );
+
+ if ( empty( $customer_id ) || empty( $stored_payment_method_id ) ) {
+ throw new Exception( __( 'Missing stored payment method for subscription renewal.', 'peachpay-for-woocommerce' ) );
+ }
+
+ $config = $this->get_convesiopay_config();
+ if ( empty( $config['secret_key'] ) ) {
+ throw new Exception( __( 'ConvesioPay is not properly configured.', 'peachpay-for-woocommerce' ) );
+ }
+
+ // Prepare renewal payment request using stored-card endpoint
+ $request_data = array(
+ 'integration' => $config['integration_name'] ?? 'PeachPay',
+ 'returnUrl' => $this->get_return_url( $renewal_order ),
+ 'orderNumber' => strval( $renewal_order->get_id() ),
+ 'amount' => (int) round( $renewal_total * 100 ),
+ 'currency' => $renewal_order->get_currency(),
+ 'customer' => array(
+ 'id' => $customer_id,
+ 'storedPaymentMethodId' => $stored_payment_method_id,
+ ),
+ );
+
+ // Call ConvesioPay Card On File endpoint
+ $response = wp_remote_post( $config['api_url'] . '/payments/stored-card', array(
+ 'headers' => array(
+ 'Authorization' => $config['secret_key'],
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => wp_json_encode( $request_data ),
+ 'timeout' => 30,
+ ) );
+
+ if ( is_wp_error( $response ) ) {
+ throw new Exception( 'Network error: ' . $response->get_error_message() );
+ }
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+ $response_body = wp_remote_retrieve_body( $response );
+ $response_data = json_decode( $response_body, true );
+
+ $success_statuses = array( 'Succeeded', 'Pending', 'Authorized', 'Authorised' );
+ $status = $response_data['status'] ?? '';
+ $payment_id = $response_data['id'] ?? '';
+
+ if ( $response_code === 200 && in_array( $status, $success_statuses, true ) ) {
+ // Payment successful
+ $renewal_order->update_meta_data( '_convesiopay_payment_id', $payment_id );
+ $renewal_order->update_meta_data( '_convesiopay_customer_id', $customer_id );
+ $renewal_order->update_meta_data( '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
+ $renewal_order->set_transaction_id( $payment_id );
+ $renewal_order->payment_complete( $payment_id );
+ $renewal_order->add_order_note( sprintf(
+ __( 'ConvesioPay subscription renewal successful. Payment ID: %s', 'peachpay-for-woocommerce' ),
+ $payment_id
+ ) );
+ } else {
+ $error_message = $response_data['message'] ?? $response_data['body']['message'] ?? 'Unknown error';
+ throw new Exception( 'Renewal payment failed: ' . $error_message );
+ }
+ } catch ( Exception $e ) {
+ $renewal_order->update_status( 'failed', sprintf(
+ __( 'ConvesioPay renewal failed: %s', 'peachpay-for-woocommerce' ),
+ $e->getMessage()
+ ) );
+ }
+ }
+
}
No newline at end of file
--- a/peachpay-for-woocommerce/core/payments/convesiopay/gateways/class-peachpay-convesiopay-unified-gateway.php
+++ b/peachpay-for-woocommerce/core/payments/convesiopay/gateways/class-peachpay-convesiopay-unified-gateway.php
@@ -49,7 +49,20 @@
$this->method_title = sprintf( __( '%s (PeachPay)', 'peachpay-for-woocommerce' ), 'ConvesioPay' );
$this->method_description = 'Accept all payment methods through ConvesioPay unified checkout. Note: ConvesioPay only supports USD currency';
$this->has_fields = true;
- $this->supports = array( 'products', 'refunds', 'blocks' );
+ $this->supports = array(
+ 'products',
+ 'refunds',
+ 'blocks',
+ 'tokenization',
+ 'subscriptions',
+ 'multiple_subscriptions',
+ 'subscription_cancellation',
+ 'subscription_suspension',
+ 'subscription_reactivation',
+ 'subscription_amount_changes',
+ 'subscription_date_changes',
+ 'subscription_payment_method_change_customer',
+ );
// Set up basic icon (no function dependencies)
$this->icon = '';
@@ -77,6 +90,30 @@
// Additional CSS hiding method
add_action( 'admin_head', array( $this, 'hide_gateway_with_css' ) );
+
+ // Subscription renewal support - route to Card Gateway
+ add_action(
+ 'woocommerce_scheduled_subscription_payment_' . $this->id,
+ function ( $renewal_total, $renewal_order ) {
+ if ( ! function_exists( 'wcs_get_subscriptions_for_renewal_order' ) ) {
+ return;
+ }
+ $gateways = WC()->payment_gateways->payment_gateways();
+ if ( isset( $gateways['peachpay_convesiopay_card'] ) ) {
+ $subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order );
+ $subscription = array_pop( $subscriptions );
+ if ( ! $subscription ) {
+ return;
+ }
+ $parent_order = wc_get_order( $subscription->get_parent_id() );
+ $gateways['peachpay_convesiopay_card']->process_subscription_renewal( $parent_order, $renewal_order, $renewal_total );
+ } else {
+ $renewal_order->update_status( 'failed', __( 'ConvesioPay Card gateway not found.', 'peachpay-for-woocommerce' ) );
+ }
+ },
+ 10,
+ 2
+ );
// Enable gateway by default if not set and ConvesioPay is connected
if ( PeachPay_ConvesioPay_Integration::connected() ) {
--- a/peachpay-for-woocommerce/core/payments/convesiopay/routes/class-peachpay-convesiopay-webhook.php
+++ b/peachpay-for-woocommerce/core/payments/convesiopay/routes/class-peachpay-convesiopay-webhook.php
@@ -440,6 +440,32 @@
$order->update_meta_data( '_convesiopay_payment_method', $payment_method );
}
+ // Extract and store subscription-related tokens for Card On File renewals
+ // customerId is at root level, storedPaymentMethodId is inside paymentMethodDetails
+ $customer_id = $payment_data['customerId'] ?? '';
+ $payment_method_details = $payment_data['paymentMethodDetails'] ?? array();
+ $stored_payment_method_id = $payment_method_details['storedPaymentMethodId'] ?? '';
+
+ if ( ! empty( $customer_id ) ) {
+ $order->update_meta_data( '_convesiopay_customer_id', $customer_id );
+ // Also store on user meta for future subscription renewals
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_customer_id', $customer_id );
+ }
+ }
+
+ if ( ! empty( $stored_payment_method_id ) ) {
+ $order->update_meta_data( '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
+ // Also store on user meta for subscription renewals
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
+ // Store payment method details for display (card brand, last4, etc.)
+ update_user_meta( $user_id, '_convesiopay_payment_method_details', $payment_method_details );
+ }
+ }
+
// Set payment method title based on actual payment method used
$payment_title = $this->get_payment_method_title( $payment_method );
$order->set_payment_method_title( $payment_title );
@@ -649,6 +675,32 @@
$order->update_meta_data( '_convesiopay_payment_method', $payment_method );
}
+ // Extract and store subscription-related tokens for Card On File renewals
+ // customerId is at root level, storedPaymentMethodId is inside paymentMethodDetails
+ $customer_id = $payment_data['customerId'] ?? '';
+ $payment_method_details = $payment_data['paymentMethodDetails'] ?? array();
+ $stored_payment_method_id = $payment_method_details['storedPaymentMethodId'] ?? '';
+
+ if ( ! empty( $customer_id ) ) {
+ $order->update_meta_data( '_convesiopay_customer_id', $customer_id );
+ // Also store on user meta for future subscription renewals
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_customer_id', $customer_id );
+ }
+ }
+
+ if ( ! empty( $stored_payment_method_id ) ) {
+ $order->update_meta_data( '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
+ // Also store on user meta for subscription renewals
+ $user_id = $order->get_user_id();
+ if ( $user_id ) {
+ update_user_meta( $user_id, '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
+ // Store payment method details for display (card brand, last4, etc.)
+ update_user_meta( $user_id, '_convesiopay_payment_method_details', $payment_method_details );
+ }
+ }
+
// Set payment method title based on actual payment method used (with Authorized suffix)
$payment_title = $this->get_payment_method_title( $payment_method, true );
$order->set_payment_method_title( $payment_title );
--- a/peachpay-for-woocommerce/peachpay.php
+++ b/peachpay-for-woocommerce/peachpay.php
@@ -3,7 +3,7 @@
* Plugin Name: PeachPay — Payments & Express Checkout for WooCommerce (supports Stripe, PayPal, Square, Authorize.net)
* Plugin URI: https://woocommerce.com/products/peachpay
* Description: Connect and manage all your payment methods, offer shoppers a beautiful Express Checkout, and reduce cart abandonment.
- * Version: 1.119.8
+ * Version: 1.119.9
* Text Domain: peachpay-for-woocommerce
* Domain Path: /languages
* Author: PeachPay, Inc.