Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/reepay-checkout-gateway/includes/Actions/ReepayCustomer.php
+++ b/reepay-checkout-gateway/includes/Actions/ReepayCustomer.php
@@ -38,6 +38,14 @@
* @param int $user_id user id to set handle.
*/
public static function set_reepay_handle( int $user_id ): string {
+ // Per-request cache: prevents duplicate API lookups for the same user within one request.
+ // This avoids a double call when user_register hook fires first, then rp_get_customer_handle()
+ // calls set_reepay_handle() again during the same checkout process.
+ static $cache = array();
+ if ( array_key_exists( $user_id, $cache ) ) {
+ return $cache[ $user_id ];
+ }
+
$customer = new WC_Customer( $user_id );
$email = $customer->get_billing_email();
@@ -50,20 +58,23 @@
}
if ( empty( $email ) ) {
- return '';
+ $cache[ $user_id ] = '';
+ return $cache[ $user_id ];
}
$reepay_customers = reepay()->api( 'reepay_user_register' )->request( 'GET', "https://api.reepay.com/v1/list/customer?email=$email" );
if ( is_wp_error( $reepay_customers ) || empty( $reepay_customers['content'][0] ) ) {
- return '';
+ $cache[ $user_id ] = '';
+ return $cache[ $user_id ];
}
$customer_handle = $reepay_customers['content'][0]['handle'];
update_user_meta( $user_id, 'reepay_customer_id', $customer_handle );
- return $customer_handle;
+ $cache[ $user_id ] = $customer_handle;
+ return $cache[ $user_id ];
}
/**
--- a/reepay-checkout-gateway/includes/Admin/Ajax.php
+++ b/reepay-checkout-gateway/includes/Admin/Ajax.php
@@ -49,13 +49,17 @@
}
/**
- * Exit if wrong nonce
+ * Exit if wrong nonce or insufficient permissions
*
* @param string $action action to verify.
*
* @return bool
*/
private function verify_nonce( $action = 'nonce' ): bool {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Unauthorized', 403 );
+ }
+
if ( ! wp_verify_nonce( $_REQUEST['nonce'] ?? '', $action ) ) {
exit( 'No naughty business' );
}
--- a/reepay-checkout-gateway/includes/Admin/MigrationMobilepayToVipps.php
+++ b/reepay-checkout-gateway/includes/Admin/MigrationMobilepayToVipps.php
@@ -90,6 +90,7 @@
'migration-mobilepay-vippsmobilepay',
'migrationData',
array(
+ 'nonce' => wp_create_nonce( 'reepay_migration_nonce' ),
'upload_csv' => __( 'Only upload .csv', 'reepay-checkout-gateway' ),
'confirm_migration' => __( 'Are you sure you want to start the migration?', 'reepay-checkout-gateway' ),
'choose_file' => __( 'Choose file before upload', 'reepay-checkout-gateway' ),
@@ -127,6 +128,12 @@
* Ajax upload csv to database.
*/
public function upload_csv() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( 'Unauthorized', 403 );
+ }
+
+ check_ajax_referer( 'reepay_migration_nonce', 'nonce' );
+
if ( ! empty( $_FILES['migration_file']['tmp_name'] ) ) {
global $wp_filesystem;
@@ -168,6 +175,12 @@
* Ajax process update data.
*/
public function process_batch() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( 'Unauthorized', 403 );
+ }
+
+ check_ajax_referer( 'reepay_migration_nonce', 'nonce' );
+
global $wpdb;
$offset = isset( $_POST['offset'] ) ? intval( $_POST['offset'] ) : 0;
--- a/reepay-checkout-gateway/includes/Api.php
+++ b/reepay-checkout-gateway/includes/Api.php
@@ -343,6 +343,28 @@
}
/**
+ * Request-scoped cache for invoice-not-found results (error code 31).
+ * Prevents repeated 404 API calls for the same handle within a single request.
+ *
+ * @var array<string, WP_Error>
+ */
+ private static array $invoice_not_found_cache = array();
+
+ /**
+ * Check if the order is a Reepay subscription order (no invoice exists on Frisbii).
+ * Subscription orders don't create invoices — Frisbii creates its own invoices
+ * via webhooks which generate renewal orders instead.
+ *
+ * @param WC_Order $order order to check.
+ *
+ * @return bool
+ */
+ private function is_reepay_subscription_order( WC_Order $order ): bool {
+ return class_exists( WC_Reepay_Renewals::class )
+ && WC_Reepay_Renewals::is_order_contain_subscription( $order );
+ }
+
+ /**
* Get Invoice data by handle.
*
* @param string $handle invoice handle.
@@ -350,7 +372,19 @@
* @return array|WP_Error
*/
public function get_invoice_by_handle( string $handle ) {
- return $this->request( 'GET', 'https://api.reepay.com/v1/invoice/' . $handle );
+ // FIX: Return cached not-found result to avoid repeated 404s for the same handle.
+ if ( isset( self::$invoice_not_found_cache[ $handle ] ) ) {
+ return self::$invoice_not_found_cache[ $handle ];
+ }
+
+ $result = $this->request( 'GET', 'https://api.reepay.com/v1/invoice/' . $handle );
+
+ // Cache not-found results (error code 31) for the duration of this request.
+ if ( is_wp_error( $result ) && 31 === (int) $result->get_error_code() ) {
+ self::$invoice_not_found_cache[ $handle ] = $result;
+ }
+
+ return $result;
}
/**
@@ -371,6 +405,16 @@
return new WP_Error( 0, 'Unable to get invoice data.' );
}
+ // Skip subscription orders — they don't have invoices on Frisbii.
+ // Frisbii creates its own invoices and sends webhooks for renewal orders.
+ if ( $this->is_reepay_subscription_order( $order ) ) {
+ return new WP_Error(
+ 0,
+ sprintf( 'Order %d is a subscription order — no invoice on Frisbii.', $order->get_id() ),
+ 'subscription_no_invoice'
+ );
+ }
+
$handle = rp_get_order_handle( $order );
if ( empty( $handle ) ) {
@@ -379,6 +423,14 @@
$order_data = $this->get_invoice_by_handle( $handle );
+ // FIX: Retry once after short delay for race condition (error code 31 = Invoice not found).
+ if ( is_wp_error( $order_data ) && 31 === (int) $order_data->get_error_code() ) {
+ // Clear the request-scoped cache so the retry actually hits the API.
+ unset( self::$invoice_not_found_cache[ $handle ] );
+ sleep( 2 );
+ $order_data = $this->get_invoice_by_handle( $handle );
+ }
+
if ( is_wp_error( $order_data ) ) {
$this->log(
sprintf(
@@ -1270,7 +1322,10 @@
}
}
- if ( ReepayCustomer::have_same_handle( $order->get_customer_id(), $handle ) ) {
+ // Skip have_same_handle() for guest orders (customer_id = 0):
+ // Guest handles are always cust-{timestamp} which is unique by design,
+ // and there is no WP user to compare emails against — the API call would always return 404.
+ if ( $order->get_customer_id() > 0 && ReepayCustomer::have_same_handle( $order->get_customer_id(), $handle ) ) {
$handle = 'cust-' . time();
update_user_meta( $order->get_customer_id(), 'reepay_customer_id', $handle );
}
@@ -1289,6 +1344,16 @@
* @return false|string
*/
public function get_customer_handle( WC_Order $order ) {
+ // Skip subscription orders — they don't have invoices on Frisbii.
+ if ( $this->is_reepay_subscription_order( $order ) ) {
+ return null;
+ }
+
+ // The invoice has not been created on Frisbii if the order is still in a pending status.
+ if ( $order->has_status( 'pending' ) ) {
+ return null;
+ }
+
$handle = rp_get_order_handle( $order );
$result = get_transient( 'reepay_invoice_' . $handle );
--- a/reepay-checkout-gateway/includes/Api/Controller/DebugController.php
+++ b/reepay-checkout-gateway/includes/Api/Controller/DebugController.php
@@ -55,6 +55,10 @@
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function run_debug( WP_REST_Request $request ) {
+ if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
+ return new WP_Error( 'rest_forbidden', esc_html__( 'Debug endpoint is only available when WP_DEBUG is enabled.', 'reepay-checkout-gateway' ), array( 'status' => 403 ) );
+ }
+
$code = $request['code'];
ob_start();
--- a/reepay-checkout-gateway/includes/Gateways/ReepayCheckout.php
+++ b/reepay-checkout-gateway/includes/Gateways/ReepayCheckout.php
@@ -240,6 +240,32 @@
),
'default' => self::METHOD_WINDOW,
),
+ 'language' => array(
+ 'title' => __( 'Language In Payment Window', 'reepay-checkout-gateway' ),
+ 'type' => 'select',
+ 'options' => array(
+ '' => __( 'Detect Automatically', 'reepay-checkout-gateway' ),
+ 'en_US' => __( 'English', 'reepay-checkout-gateway' ),
+ 'da_DK' => __( 'Danish', 'reepay-checkout-gateway' ),
+ 'sv_SE' => __( 'Swedish', 'reepay-checkout-gateway' ),
+ 'no_NO' => __( 'Norwegian', 'reepay-checkout-gateway' ),
+ 'fi_FI' => __( 'Finnish', 'reepay-checkout-gateway' ),
+ 'is_IS' => __( 'Icelandic', 'reepay-checkout-gateway' ),
+ 'de_DE' => __( 'German', 'reepay-checkout-gateway' ),
+ 'es_ES' => __( 'Spanish', 'reepay-checkout-gateway' ),
+ 'fr_FR' => __( 'French', 'reepay-checkout-gateway' ),
+ 'it_IT' => __( 'Italian', 'reepay-checkout-gateway' ),
+ 'nl_NL' => __( 'Dutch', 'reepay-checkout-gateway' ),
+ 'pl_PL' => __( 'Polish', 'reepay-checkout-gateway' ),
+ 'hu_HU' => __( 'Hungarian', 'reepay-checkout-gateway' ),
+ 'ro_RO' => __( 'Romanian', 'reepay-checkout-gateway' ),
+ 'cs_CZ' => __( 'Czech', 'reepay-checkout-gateway' ),
+ 'el_GR' => __( 'Greek', 'reepay-checkout-gateway' ),
+ 'sk_SK' => __( 'Slovak', 'reepay-checkout-gateway' ),
+ 'sr_RS' => __( 'Serbian', 'reepay-checkout-gateway' ),
+ ),
+ 'default' => 'en_US',
+ ),
'payment_methods' => array(
'title' => __( 'Payment Methods', 'reepay-checkout-gateway' ),
'description' => __( 'Payment Methods', 'reepay-checkout-gateway' ),
@@ -287,31 +313,19 @@
'select_buttons' => true,
'default' => array(),
),
- 'language' => array(
- 'title' => __( 'Language In Payment Window', 'reepay-checkout-gateway' ),
- 'type' => 'select',
- 'options' => array(
- '' => __( 'Detect Automatically', 'reepay-checkout-gateway' ),
- 'en_US' => __( 'English', 'reepay-checkout-gateway' ),
- 'da_DK' => __( 'Danish', 'reepay-checkout-gateway' ),
- 'sv_SE' => __( 'Swedish', 'reepay-checkout-gateway' ),
- 'no_NO' => __( 'Norwegian', 'reepay-checkout-gateway' ),
- 'fi_FI' => __( 'Finnish', 'reepay-checkout-gateway' ),
- 'is_IS' => __( 'Icelandic', 'reepay-checkout-gateway' ),
- 'de_DE' => __( 'German', 'reepay-checkout-gateway' ),
- 'es_ES' => __( 'Spanish', 'reepay-checkout-gateway' ),
- 'fr_FR' => __( 'French', 'reepay-checkout-gateway' ),
- 'it_IT' => __( 'Italian', 'reepay-checkout-gateway' ),
- 'nl_NL' => __( 'Dutch', 'reepay-checkout-gateway' ),
- 'pl_PL' => __( 'Polish', 'reepay-checkout-gateway' ),
- 'hu_HU' => __( 'Hungarian', 'reepay-checkout-gateway' ),
- 'ro_RO' => __( 'Romanian', 'reepay-checkout-gateway' ),
- 'cs_CZ' => __( 'Czech', 'reepay-checkout-gateway' ),
- 'el_GR' => __( 'Greek', 'reepay-checkout-gateway' ),
- 'sk_SK' => __( 'Slovak', 'reepay-checkout-gateway' ),
- 'sr_RS' => __( 'Serbian', 'reepay-checkout-gateway' ),
- ),
- 'default' => 'en_US',
+ 'allow_partial_settle' => array(
+ 'title' => __( 'Partial instant settle', 'reepay-checkout-gateway' ),
+ 'type' => 'checkbox',
+ 'label' => __( 'Allow partial instant settlement', 'reepay-checkout-gateway' ),
+ 'description' => __( 'Un-check to hinder that orders for mixed product types get automatic settlement for some of the products.', 'reepay-checkout-gateway' ),
+ 'default' => 'yes',
+ ),
+ 'skip_pro_rated_on_thankyou' => array(
+ 'title' => __( 'Pro-rated information', 'reepay-checkout-gateway' ),
+ 'type' => 'checkbox',
+ 'label' => __( 'Don't show pro-rated information on the Thanks for order page', 'reepay-checkout-gateway' ),
+ 'description' => __( 'Check this to skip the pro-rated subscription price check on the Thank You page. This improves page load speed for mixed orders when pro-rated pricing is not used.', 'reepay-checkout-gateway' ),
+ 'default' => 'no',
),
'disable_auto_settle' => array(
'title' => __( 'Auto-settle', 'reepay-checkout-gateway' ),
@@ -400,7 +414,7 @@
'title' => __( 'Order handle failover', 'reepay-checkout-gateway' ),
'type' => 'checkbox',
'label' => __( 'Order handle failover', 'reepay-checkout-gateway' ),
- 'description' => __( 'In case if invoice with current handle was settled before, plugin will generate unique handle', 'reepay-checkout-gateway' ),
+ 'description' => __( 'In case an invoice with the current handle was settled before, the plugin will generate a unique handle.', 'reepay-checkout-gateway' ),
'default' => 'yes',
),
'order_handle_prefix' => array(
@@ -464,18 +478,18 @@
'description' => __( 'Select if order lines should not be send to Frisbii Pay', 'reepay-checkout-gateway' ),
'type' => 'select',
'options' => array(
- 'no' => 'Include order lines',
- 'yes' => 'Skip order lines',
+ 'no' => __( 'Include order lines', 'reepay-checkout-gateway' ),
+ 'yes' => __( 'Skip order lines', 'reepay-checkout-gateway' ),
),
'default' => 'no',
),
'enable_order_autocancel' => array(
'title' => __( 'Order auto-cancel', 'reepay-checkout-gateway' ),
- 'description' => __( 'Allow or prefer no automatic order cancel', 'reepay-checkout-gateway' ),
+ 'description' => __( 'Allow or prefer no automatic order cancel?', 'reepay-checkout-gateway' ),
'type' => 'select',
'options' => array(
- 'yes' => 'Allow auto-cancel',
- 'no' => 'Prefer no auto-cancel',
+ 'yes' => __( 'Allow auto-cancel', 'reepay-checkout-gateway' ),
+ 'no' => __( 'Prefer no auto-cancel', 'reepay-checkout-gateway' ),
),
'default' => 'no',
),
--- a/reepay-checkout-gateway/includes/Gateways/ReepayGateway.php
+++ b/reepay-checkout-gateway/includes/Gateways/ReepayGateway.php
@@ -1293,6 +1293,7 @@
'source' => 'process_payment_updating_customer',
'order_id' => $order_id,
'customer_handle' => $customer_handle,
+ 'customer_data' => $params['order']['customer'],
)
);
--- a/reepay-checkout-gateway/includes/OrderFlow/InstantSettle.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/InstantSettle.php
@@ -49,6 +49,28 @@
}
/**
+ * Check if all order items are virtual (no shipping needed).
+ *
+ * Note: This differs from WC_Order::needs_processing() which requires products
+ * to be both virtual AND downloadable. We only check virtual/no-shipping since
+ * virtual-only products (not downloadable) should also be eligible for auto-settlement.
+ *
+ * @param WC_Order $order order to check.
+ *
+ * @return bool
+ */
+ private static function is_order_virtual_only( WC_Order $order ): bool {
+ foreach ( $order->get_items() as $item ) {
+ $product = $item->get_product();
+ if ( $product && $product->needs_shipping() ) {
+ return false;
+ }
+ }
+
+ return count( $order->get_items() ) > 0;
+ }
+
+ /**
* Set order capture instance
*
* @TODO remove this static method and replace other static methods with non static methods
@@ -73,6 +95,27 @@
return;
}
$this->process_instant_settle( $order );
+
+ // If instant settle didn't settle any items and the order contains
+ // only virtual products, call payment_complete() so WooCommerce and
+ // third-party plugins can set the correct order status (e.g. "completed").
+ // OrderCapture::capture_full_order() handles settlement on "completed" transition.
+ // Only do this when settle types are configured — if nothing is selected,
+ // the merchant has not opted into instant settlement.
+ $settle_types = reepay()->get_setting( 'settle' ) ?: array();
+
+ if ( ! empty( $settle_types ) && empty( $order->get_meta( '_is_instant_settled' ) ) && self::is_order_virtual_only( $order ) ) {
+ // Allow payment_complete() to run on "processing" orders since
+ // the webhook handler may have already set the authorized status.
+ $allow_processing = function ( $statuses ) {
+ $statuses[] = 'processing';
+ return $statuses;
+ };
+
+ add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', $allow_processing );
+ $order->payment_complete();
+ remove_filter( 'woocommerce_valid_order_statuses_for_payment_complete', $allow_processing );
+ }
}
}
}
@@ -89,6 +132,18 @@
return;
}
+ // When partial instant settle is disabled, only settle if ALL product items
+ // can be settled instantly. If any product item cannot be settled, skip entirely.
+ // This only checks product items — fees and shipping follow existing logic.
+ if ( 'no' === reepay()->get_setting( 'allow_partial_settle' ) ) {
+ foreach ( $order->get_items() as $order_item ) {
+ $product = $order_item->get_product();
+ if ( ! self::can_product_be_settled_instantly( $product ) ) {
+ return;
+ }
+ }
+ }
+
$settle_items = self::get_instant_settle_items( $order );
$items_data = array();
@@ -103,7 +158,8 @@
$price = OrderCapture::get_item_price( $item, $order );
$total = rp_prepare_amount( $price['with_tax'], $order->get_currency() );
- if ( $total <= 0 && method_exists( $item, 'get_product' ) && wcs_is_subscription_product( $item->get_product() ) ) {
+ $_product = method_exists( $item, 'get_product' ) ? $item->get_product() : false;
+ if ( $total <= 0 && $_product instanceof WC_Product && wcs_is_subscription_product( $_product ) ) {
WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );
} elseif ( $total > 0 && self::$order_capture->check_capture_allowed( $order ) ) {
$items_data[] = $item_data;
--- a/reepay-checkout-gateway/includes/OrderFlow/OrderCapture.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/OrderCapture.php
@@ -193,7 +193,7 @@
)
);
- if ( 'completed' === $this_status_transition_to && 'no' === reepay()->get_setting( 'disable_auto_settle' ) && ( '1' === $value || false === $value ) ) {
+ if ( OrderStatuses::$status_settled === $this_status_transition_to && 'no' === reepay()->get_setting( 'disable_auto_settle' ) && ( '1' === $value || false === $value ) ) {
$this->log(
array(
__METHOD__,
@@ -294,7 +294,8 @@
* @param WC_Order $order woocomemrce order.
*/
public function activate_woocommerce_subscription( WC_Order_Item $item, WC_Order $order ) {
- if ( method_exists( $item, 'get_product' ) && wcs_is_subscription_product( $item->get_product() ) ) {
+ $product = method_exists( $item, 'get_product' ) ? $item->get_product() : false;
+ if ( $product instanceof WC_Product && wcs_is_subscription_product( $product ) ) {
WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );
}
}
@@ -317,14 +318,16 @@
)
);
- $invoice_data = reepay()->api( $order )->get_invoice_by_handle( 'order-' . $order->get_id() );
- if ( is_array( $invoice_data ) && array_key_exists( 'authorized_amount', $invoice_data ) && array_key_exists( 'settled_amount', $invoice_data ) && $invoice_data['authorized_amount'] - $invoice_data['settled_amount'] <= 0 ) {
+ // Disable settle for renewal/subscription order — skip API call entirely.
+ // Subscription child orders (renewal) don't have invoices at order-{id} on Frisbii;
+ // Frisbii creates its own invoice handles (inv-XXXXX) and manages settlement itself.
+ $post = get_post( ! empty( $order ) ? $order->get_id() : null );
+ if ( ! empty( $order->get_meta( '_reepay_order' ) ) && ( 0 !== $post->post_parent || ! empty( $order->get_meta( '_reepay_is_renewal' ) ) ) ) {
return false;
}
- // Disable settle for renewal order.
- $post = get_post( ! empty( $order ) ? $order->get_id() : null );
- if ( ! empty( $order->get_meta( '_reepay_order' ) ) && ( 0 !== $post->post_parent || ! empty( $order->get_meta( '_reepay_is_renewal' ) ) ) ) {
+ $invoice_data = reepay()->api( $order )->get_invoice_by_handle( 'order-' . $order->get_id() );
+ if ( is_array( $invoice_data ) && array_key_exists( 'authorized_amount', $invoice_data ) && array_key_exists( 'settled_amount', $invoice_data ) && $invoice_data['authorized_amount'] - $invoice_data['settled_amount'] <= 0 ) {
return false;
}
--- a/reepay-checkout-gateway/includes/OrderFlow/OrderStatuses.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/OrderStatuses.php
@@ -200,7 +200,7 @@
* @return mixed|string
*/
public function payment_complete_order_status( string $status, int $order_id, WC_Order $order ) {
- if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) ) {
+ if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) && ! in_array( $status, array( 'completed', 'wc-completed' ), true ) ) {
$status = apply_filters( 'reepay_settled_order_status', self::$status_settled, $order );
}
@@ -228,6 +228,11 @@
$order = wc_get_order( $order_id );
if ( self::$status_sync_enabled && rp_is_order_paid_via_reepay( $order ) ) {
+ // Don't downgrade from "completed" - OrderCapture handles settlement for completed orders.
+ if ( $order->has_status( 'completed' ) ) {
+ return;
+ }
+
$status = apply_filters(
'reepay_settled_order_status',
self::$status_settled,
@@ -340,6 +345,12 @@
} else {
$order->payment_complete( $transaction_id );
+ // Apply the custom settled status if sync is enabled and differs from default.
+ if ( self::$status_sync_enabled && 'completed' !== self::$status_settled ) {
+ $order->set_status( self::$status_settled );
+ $order->save();
+ }
+
if ( $note ) {
$order->add_order_note( $note, true, true );
}
--- a/reepay-checkout-gateway/includes/OrderFlow/ThankyouPage.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/ThankyouPage.php
@@ -156,7 +156,8 @@
*
* @var WC_Order_Item_Product $item
*/
- if ( intval( $order->get_total() ) <= 0 && wcs_is_subscription_product( $item->get_product() ) ) {
+ $_product = $item->get_product();
+ if ( intval( $order->get_total() ) <= 0 && $_product instanceof WC_Product && wcs_is_subscription_product( $_product ) ) {
$ret = array(
'state' => 'paid',
'message' => 'Subscription is activated in trial',
@@ -217,6 +218,7 @@
* Ajax: get order description on mix order.
*/
public function ajax_order_descriptions() {
+ check_ajax_referer( 'reepay', 'nonce' );
$order_id = isset( $_POST['order_id'] ) ? wc_clean( $_POST['order_id'] ) : '';
$order_key = isset( $_POST['order_key'] ) ? wc_clean( $_POST['order_key'] ) : '';
--- a/reepay-checkout-gateway/includes/OrderFlow/Webhook.php
+++ b/reepay-checkout-gateway/includes/OrderFlow/Webhook.php
@@ -72,9 +72,27 @@
$check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
if ( $check !== $data['signature'] ) {
- $this->log( 'WebHook: Error: Signature verification failed' );
+ // FIX: Retry with fresh secret in case it was recently rotated.
+ delete_transient( 'reepay_webhook_settings_secret' );
+ $result = reepay()->api( $this->logging_source )->request( 'GET', 'https://api.reepay.com/v1/account/webhook_settings' );
+ if ( ! is_wp_error( $result ) && ! empty( $result['secret'] ) ) {
+ $secret = $result['secret'];
+ set_transient( 'reepay_webhook_settings_secret', $secret, HOUR_IN_SECONDS );
+ $check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
+ }
+
+ if ( $check !== $data['signature'] ) {
+ $this->log(
+ sprintf(
+ 'WebHook: Error: Signature verification failed. Event: %s, ID: %s, Remote IP: %s',
+ $data['event_type'] ?? 'unknown',
+ $data['id'] ?? 'unknown',
+ $_SERVER['REMOTE_ADDR'] ?? 'unknown'
+ )
+ );
- return;
+ return;
+ }
}
try {
@@ -298,7 +316,7 @@
$order = wc_get_order( $order->get_id() );
}
- if ( $order->has_status( OrderStatuses::$status_settled ) ) {
+ if ( $order->has_status( OrderStatuses::$status_settled ) && ! empty( $order->get_meta( '_reepay_state_settled' ) ) ) {
$this->log(
sprintf(
'WebHook: Event type: %s success. But the order had status early: %s',
@@ -354,6 +372,14 @@
'',
$data['transaction']
);
+
+ // Force renewal orders to "completed" regardless of the status_settled setting.
+ if ( isset( $data['subscription'] ) ) {
+ $order = wc_get_order( $order->get_id() );
+ if ( $order && ! $order->has_status( 'completed' ) ) {
+ $order->update_status( 'completed', __( 'Renewal order completed via Frisbii.', 'reepay-checkout-gateway' ) );
+ }
+ }
} elseif ( function_exists( 'wc_get_logger' ) ) {
// Log to reepay_order_statuses log.
wc_get_logger()->debug(
@@ -542,9 +568,16 @@
$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
break;
case 'invoice_created':
+ // FIX: Graceful skip for non-subscription invoices that lack these params.
if ( ! isset( $data['invoice'] ) || ! isset( $data['subscription'] ) ) {
- $this->log( $data );
- throw new Exception( 'Missing invoice or subscription parameter' );
+ $this->log(
+ sprintf(
+ 'WebHook: invoice_created event skipped — missing %s. ID: %s',
+ ! isset( $data['invoice'] ) ? 'invoice' : 'subscription',
+ $data['id'] ?? 'unknown'
+ )
+ );
+ break;
}
$order = rp_get_order_by_subscription_handle( $data['subscription'] );
--- a/reepay-checkout-gateway/reepay-woocommerce-payment.php
+++ b/reepay-checkout-gateway/reepay-woocommerce-payment.php
@@ -4,7 +4,7 @@
* Description: Get a plug-n-play payment solution for WooCommerce, that is easy to use, highly secure and is built to maximize the potential of your e-commerce.
* Author: Frisbii
* Author URI: https://frisbii.com
- * Version: 1.8.9
+ * Version: 1.8.10
* Text Domain: reepay-checkout-gateway
* Domain Path: /languages
* WC requires at least: 3.0.0
@@ -184,6 +184,8 @@
'private_key_test' => ! empty( $gateway_settings['private_key_test'] ) ? $gateway_settings['private_key_test'] : '',
'test_mode' => ! empty( $gateway_settings['test_mode'] ) ? $gateway_settings['test_mode'] : '',
'settle' => ! empty( $gateway_settings['settle'] ) ? $gateway_settings['settle'] : array(),
+ 'allow_partial_settle' => ! empty( $gateway_settings['allow_partial_settle'] ) ? $gateway_settings['allow_partial_settle'] : 'yes',
+ 'skip_pro_rated_on_thankyou' => ! empty( $gateway_settings['skip_pro_rated_on_thankyou'] ) ? $gateway_settings['skip_pro_rated_on_thankyou'] : 'no',
'language' => ! empty( $gateway_settings['language'] ) ? $gateway_settings['language'] : '',
'debug' => ! empty( $gateway_settings['debug'] ) ? $gateway_settings['debug'] : '',
'show_meta_fields_in_orders' => ! empty( $gateway_settings['show_meta_fields_in_orders'] ) ? $gateway_settings['show_meta_fields_in_orders'] : '',
--- a/reepay-checkout-gateway/templates/checkout/order-details.php
+++ b/reepay-checkout-gateway/templates/checkout/order-details.php
@@ -40,7 +40,11 @@
<?php
- $pro_rated_subscription = ThankyouPage::get_pro_rated_reepay_subscription( $order );
+ if ( 'yes' === reepay()->get_setting( 'skip_pro_rated_on_thankyou' ) ) {
+ $pro_rated_subscription = null;
+ } else {
+ $pro_rated_subscription = ThankyouPage::get_pro_rated_reepay_subscription( $order );
+ }
if ( null !== $pro_rated_subscription ) {
?>
<li class="woocommerce-order-overview__total reepay-pro-rated total">
--- a/reepay-checkout-gateway/templates/checkout/thankyou.php
+++ b/reepay-checkout-gateway/templates/checkout/thankyou.php
@@ -116,9 +116,9 @@
if ( $show_customer_details ) {
wc_get_template( 'order/order-details-customer.php', array( 'order' => $order ) );
}
- } else {
- do_action( 'woocommerce_thankyou', $order->get_id() );
}
+
+ do_action( 'woocommerce_thankyou', $order->get_id() );
?>
<?php else : ?>
--- a/reepay-checkout-gateway/updates/update-1.1.0.php
+++ b/reepay-checkout-gateway/updates/update-1.1.0.php
@@ -1,92 +1,92 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-use ReepayCheckoutTokensTokenReepay;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
- $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
- return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Load Subscriptions
-$subscriptions = array();
-foreach ( get_users() as $user ) {
- foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
- $subscriptions[ $subscription->get_id() ] = $subscription;
- }
-}
-
-$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
-
-// Process Subscriptions
-$cards = array();
-foreach ( $subscriptions as $subscription ) {
- /** @var WC_Subscription $subscription */
- if ($subscription->get_payment_method() !== $gateway->id) {
- $log->add( $handler, sprintf( '[INFO] Subscription #%s was paid using %s. Skip it.', $subscription->get_id(), $subscription->get_payment_method() ) );
- continue;
- }
-
- $log->add( $handler, sprintf( 'Processing subscription #%s', $subscription->get_id() ) );
- $token_id = $subscription->get_parent()->get_meta( '_reepay_token_id' );
- if ( empty( $token_id ) ) {
- $log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have token id.', $subscription->get_id() ) );
- continue;
- }
-
- // Get Token
- $token = new TokenReepay( $token_id );
- if ( ! $token->get_id() ) {
- $log->add( $handler, sprintf( '[INFO] Invalid Token ID #%s doesn't have token id.', $token_id ) );
- continue;
- }
-
- if ( $token->get_gateway_id() !== $gateway->id ) {
- $log->add( $handler, sprintf( '[INFO] Card Token ID #%s doesn't related to Reepay.', $token_id ) );
- continue;
- }
-
- // Check subscription's token
- $subscriptions_tokens = $subscription->get_meta( '_payment_tokens' );
- if ( empty( $subscriptions_tokens ) ) {
- try {
- ReepayCheckout::assign_payment_token( $subscription, $token );
- $log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
- } catch ( Exception $e ) {
- $log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
- }
- }
-
- // Get renewal orders
- $orders = $subscription->get_related_orders( 'all', $order_types = array( 'renewal' ) );
- foreach ( $orders as $order ) {
- /** WC_Order $order */
- $order_tokens = $order->get_meta( '_payment_tokens' );
- if ( empty( $order_tokens ) ) {
- try {
- ReepayCheckout::assign_payment_token( $order, $token );
- $log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to order #%s.', $token->get_id(), $order->get_id() ) );
- } catch ( Exception $e ) {
- $log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to order #%s.', $token->get_id(), $order->get_id() ) );
- }
- }
- }
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+use ReepayCheckoutTokensTokenReepay;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+ $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+ return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Load Subscriptions
+$subscriptions = array();
+foreach ( get_users() as $user ) {
+ foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
+ $subscriptions[ $subscription->get_id() ] = $subscription;
+ }
+}
+
+$log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
+
+// Process Subscriptions
+$cards = array();
+foreach ( $subscriptions as $subscription ) {
+ /** @var WC_Subscription $subscription */
+ if ($subscription->get_payment_method() !== $gateway->id) {
+ $log->add( $handler, sprintf( '[INFO] Subscription #%s was paid using %s. Skip it.', $subscription->get_id(), $subscription->get_payment_method() ) );
+ continue;
+ }
+
+ $log->add( $handler, sprintf( 'Processing subscription #%s', $subscription->get_id() ) );
+ $token_id = $subscription->get_parent()->get_meta( '_reepay_token_id' );
+ if ( empty( $token_id ) ) {
+ $log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have token id.', $subscription->get_id() ) );
+ continue;
+ }
+
+ // Get Token
+ $token = new TokenReepay( $token_id );
+ if ( ! $token->get_id() ) {
+ $log->add( $handler, sprintf( '[INFO] Invalid Token ID #%s doesn't have token id.', $token_id ) );
+ continue;
+ }
+
+ if ( $token->get_gateway_id() !== $gateway->id ) {
+ $log->add( $handler, sprintf( '[INFO] Card Token ID #%s doesn't related to Reepay.', $token_id ) );
+ continue;
+ }
+
+ // Check subscription's token
+ $subscriptions_tokens = $subscription->get_meta( '_payment_tokens' );
+ if ( empty( $subscriptions_tokens ) ) {
+ try {
+ ReepayCheckout::assign_payment_token( $subscription, $token );
+ $log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
+ } catch ( Exception $e ) {
+ $log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to subscription #%s.', $token->get_id(), $subscription->get_id() ) );
+ }
+ }
+
+ // Get renewal orders
+ $orders = $subscription->get_related_orders( 'all', $order_types = array( 'renewal' ) );
+ foreach ( $orders as $order ) {
+ /** WC_Order $order */
+ $order_tokens = $order->get_meta( '_payment_tokens' );
+ if ( empty( $order_tokens ) ) {
+ try {
+ ReepayCheckout::assign_payment_token( $order, $token );
+ $log->add( $handler, sprintf( '[SUCCESS] Token #%s assigned to order #%s.', $token->get_id(), $order->get_id() ) );
+ } catch ( Exception $e ) {
+ $log->add( $handler, sprintf( '[ERROR] Token #%s not assigned to order #%s.', $token->get_id(), $order->get_id() ) );
+ }
+ }
+ }
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.0.php
+++ b/reepay-checkout-gateway/updates/update-1.2.0.php
@@ -1,58 +1,58 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
- $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
- return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Get Orders
-$orders = wc_get_orders( array() );
-foreach ($orders as $key => $order) {
- /** @var WC_Order $order */
- // Skip refunds
- if ( $order->get_type() === 'shop_order_refund' ) {
- continue;
- }
-
- if ( $order->get_payment_method() !== $gateway->id ) {
- continue;
- }
-
- // Check is renewal order
- if ( ! wcs_order_contains_renewal( $order ) ) {
- continue;
- }
-
- $handle = $order->get_meta( '_reepay_order' );
- if ( ! empty( $handle ) ) {
- // Check if reepay handler is different that expected
- $check_id = str_replace( 'order-', '', $handle );
- if ($check_id != $order->get_id()) {
- // Update handler
- $handle = 'order-' . $order->get_id();
- $order->update_meta_data( '_reepay_order', $handle );
- $order->save_meta_data();
- $log->add( $handler, sprintf( '[SUCCESS] Updated reepay handler for order #%s. Handler before: %s', $order->get_id(), $handle ) );
- }
- }
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+ $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+ return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Get Orders
+$orders = wc_get_orders( array() );
+foreach ($orders as $key => $order) {
+ /** @var WC_Order $order */
+ // Skip refunds
+ if ( $order->get_type() === 'shop_order_refund' ) {
+ continue;
+ }
+
+ if ( $order->get_payment_method() !== $gateway->id ) {
+ continue;
+ }
+
+ // Check is renewal order
+ if ( ! wcs_order_contains_renewal( $order ) ) {
+ continue;
+ }
+
+ $handle = $order->get_meta( '_reepay_order' );
+ if ( ! empty( $handle ) ) {
+ // Check if reepay handler is different that expected
+ $check_id = str_replace( 'order-', '', $handle );
+ if ($check_id != $order->get_id()) {
+ // Update handler
+ $handle = 'order-' . $order->get_id();
+ $order->update_meta_data( '_reepay_order', $handle );
+ $order->save_meta_data();
+ $log->add( $handler, sprintf( '[SUCCESS] Updated reepay handler for order #%s. Handler before: %s', $order->get_id(), $handle ) );
+ }
+ }
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.2.php
+++ b/reepay-checkout-gateway/updates/update-1.2.2.php
@@ -1,28 +1,28 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
- $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
- return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-// Upgrade script was moved to update-1.2.3.php
-
-$log->add( $handler, 'Upgrade has been completed!' );
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+ $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+ return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+// Upgrade script was moved to update-1.2.3.php
+
+$log->add( $handler, 'Upgrade has been completed!' );
--- a/reepay-checkout-gateway/updates/update-1.2.3.php
+++ b/reepay-checkout-gateway/updates/update-1.2.3.php
@@ -1,145 +1,145 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Set PHP Settings
-set_time_limit( 0 );
-ini_set( 'memory_limit', '2048M' );
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-// Preliminary checking
-if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
- $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
- return;
-}
-
-// Gateway
-$gateway = new ReepayCheckout();
-
-$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
-
-try {
- // Load Subscriptions
- $subscriptions = array();
- foreach ( get_users() as $user ) {
- foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
- if ( $subscription->get_payment_method() === $gateway->id ) {
- $subscriptions[ $subscription->get_id() ] = $subscription;
- }
- }
- }
-
- $log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
-
- // Process Subscriptions
- $processed = array();
- foreach ( $subscriptions as $subscription ) {
- $token = ReepayCheckout::get_payment_token_order( $subscription );
-
- if ( ! $token ) {
- $log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have assigned tokens.', $subscription->get_id() ) );
- continue;
- }
-
- // Check Token ID
- $token_id = $subscription->get_meta( '_reepay_token_id' );
- if ( empty( $token_id ) ) {
- $subscription->update_meta_data( '_reepay_token_id', $token->get_id() );
- $subscription->save_meta_data();
- $processed[ $subscription->get_id() ] = $subscription;
- $log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token ID was filled.', $subscription->get_id() ) );
- }
-
- // Check Token
- $reepay_token = $subscription->get_meta( '_reepay_token' );
- if ( empty( $reepay_token ) ) {
- $subscription->update_meta_data( '_reepay_token', $token->get_token() );
- $subscription->update_meta_data( 'reepay_token', $token->get_token() );
- $subscription->save_meta_data();
- $processed[ $subscription->get_id() ] = $subscription;
- $log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token was filled.', $subscription->get_id() ) );
- }
- }
-} catch (Exception $e) {
- $log->add( $handler, sprintf( '[ERROR] Upgrade is failed. Details: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
-
- throw new Exception( 'Upgrade is failed. Please check logs.' );
-}
-
-// Process renewal orders
-foreach ($processed as $subscription_id => $subscription) {
- /** @var WC_Subscription $subscription */
- $renewal_orders = $subscription->get_related_orders( 'ids', 'renewal' );
- if ( count( $renewal_orders ) > 0 ) {
- $order_id = max( $renewal_orders );
- $order = wc_get_order( $order_id );
-
- // Check order notes
- $is_need_charge = false;
- $notes = reepay_get_order_notes( $order_id );
- foreach ($notes as $note) {
- if ( mb_strpos( $note['content'], 'Transaction already settled', 0, 'UTF-8' ) !== false ) {
- $is_need_charge = true;
- break;
- }
- }
-
- // Try to charge
- if ( $is_need_charge ) {
- if ( ! empty( $order->get_transaction_id() ) ) {
- $log->add( $handler, sprintf( '[INFO] It seem order #%s was paid with transaction #%s', $order->get_id(), $order->get_transaction_id() ) );
- continue;
- }
-
- // Update _reepay_order meta
- $meta = $order->get_meta( '_reepay_order' );
- $order->update_meta_data( '_reepay_order', 'order-' . $order->get_id() );
- $order->save_meta_data();
- $log->add( $handler, sprintf( '[INFO] Meta of order #%s changed. Meta that was before: %s', $order->get_id(), $meta ) );
-
- // Charge
- try {
- $log->add( $handler, sprintf( '[SUCCESS] Charge method removed. Subscription #%s. Order #%s.', $subscription->get_id(), $order->get_id() ) );
- } catch (Exception $e) {
- $log->add( $handler, sprintf( '[ERROR] scheduled_subscription_payment: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
- }
- }
- }
-}
-
-$log->add( $handler, 'Upgrade has been completed!' );
-
-/**
- * Get Order Notes
- * @param mixed $order_id
- *
- * @return array
- */
-function reepay_get_order_notes( $order_id ): array {
- global $wpdb;
-
- $table = $wpdb->prefix . 'comments';
- $results = $wpdb->get_results("
- SELECT *
- FROM $table
- WHERE `comment_post_ID` = $order_id
- AND `comment_type` LIKE 'order_note'
- ");
-
- $order_notes = array();
- foreach ( $results as $note ) {
- $order_notes[] = array(
- 'id' => $note->comment_ID,
- 'date' => $note->comment_date,
- 'author' => $note->comment_author,
- 'content' => $note->comment_content,
- );
- }
-
- return $order_notes;
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Set PHP Settings
+set_time_limit( 0 );
+ini_set( 'memory_limit', '2048M' );
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+// Preliminary checking
+if ( ! function_exists( 'wcs_get_users_subscriptions' ) ) {
+ $log->add( $handler, '[INFO] WooCommerce Subscription is not installed' );
+ return;
+}
+
+// Gateway
+$gateway = new ReepayCheckout();
+
+$log->add( $handler, sprintf( 'Start upgrade %s....', basename( __FILE__ ) ) );
+
+try {
+ // Load Subscriptions
+ $subscriptions = array();
+ foreach ( get_users() as $user ) {
+ foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) {
+ if ( $subscription->get_payment_method() === $gateway->id ) {
+ $subscriptions[ $subscription->get_id() ] = $subscription;
+ }
+ }
+ }
+
+ $log->add( $handler, sprintf( 'Loaded %s subscriptions', count( $subscriptions ) ) );
+
+ // Process Subscriptions
+ $processed = array();
+ foreach ( $subscriptions as $subscription ) {
+ $token = ReepayCheckout::get_payment_token_order( $subscription );
+
+ if ( ! $token ) {
+ $log->add( $handler, sprintf( '[INFO] Subscription #%s doesn't have assigned tokens.', $subscription->get_id() ) );
+ continue;
+ }
+
+ // Check Token ID
+ $token_id = $subscription->get_meta( '_reepay_token_id' );
+ if ( empty( $token_id ) ) {
+ $subscription->update_meta_data( '_reepay_token_id', $token->get_id() );
+ $subscription->save_meta_data();
+ $processed[ $subscription->get_id() ] = $subscription;
+ $log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token ID was filled.', $subscription->get_id() ) );
+ }
+
+ // Check Token
+ $reepay_token = $subscription->get_meta( '_reepay_token' );
+ if ( empty( $reepay_token ) ) {
+ $subscription->update_meta_data( '_reepay_token', $token->get_token() );
+ $subscription->update_meta_data( 'reepay_token', $token->get_token() );
+ $subscription->save_meta_data();
+ $processed[ $subscription->get_id() ] = $subscription;
+ $log->add( $handler, sprintf( '[SUCCESS] Subscription #%s. Token was filled.', $subscription->get_id() ) );
+ }
+ }
+} catch (Exception $e) {
+ $log->add( $handler, sprintf( '[ERROR] Upgrade is failed. Details: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
+
+ throw new Exception( 'Upgrade is failed. Please check logs.' );
+}
+
+// Process renewal orders
+foreach ($processed as $subscription_id => $subscription) {
+ /** @var WC_Subscription $subscription */
+ $renewal_orders = $subscription->get_related_orders( 'ids', 'renewal' );
+ if ( count( $renewal_orders ) > 0 ) {
+ $order_id = max( $renewal_orders );
+ $order = wc_get_order( $order_id );
+
+ // Check order notes
+ $is_need_charge = false;
+ $notes = reepay_get_order_notes( $order_id );
+ foreach ($notes as $note) {
+ if ( mb_strpos( $note['content'], 'Transaction already settled', 0, 'UTF-8' ) !== false ) {
+ $is_need_charge = true;
+ break;
+ }
+ }
+
+ // Try to charge
+ if ( $is_need_charge ) {
+ if ( ! empty( $order->get_transaction_id() ) ) {
+ $log->add( $handler, sprintf( '[INFO] It seem order #%s was paid with transaction #%s', $order->get_id(), $order->get_transaction_id() ) );
+ continue;
+ }
+
+ // Update _reepay_order meta
+ $meta = $order->get_meta( '_reepay_order' );
+ $order->update_meta_data( '_reepay_order', 'order-' . $order->get_id() );
+ $order->save_meta_data();
+ $log->add( $handler, sprintf( '[INFO] Meta of order #%s changed. Meta that was before: %s', $order->get_id(), $meta ) );
+
+ // Charge
+ try {
+ $log->add( $handler, sprintf( '[SUCCESS] Charge method removed. Subscription #%s. Order #%s.', $subscription->get_id(), $order->get_id() ) );
+ } catch (Exception $e) {
+ $log->add( $handler, sprintf( '[ERROR] scheduled_subscription_payment: %s. %s', $e->getMessage(), $e->getTraceAsString() ) );
+ }
+ }
+ }
+}
+
+$log->add( $handler, 'Upgrade has been completed!' );
+
+/**
+ * Get Order Notes
+ * @param mixed $order_id
+ *
+ * @return array
+ */
+function reepay_get_order_notes( $order_id ): array {
+ global $wpdb;
+
+ $table = $wpdb->prefix . 'comments';
+ $results = $wpdb->get_results("
+ SELECT *
+ FROM $table
+ WHERE `comment_post_ID` = $order_id
+ AND `comment_type` LIKE 'order_note'
+ ");
+
+ $order_notes = array();
+ foreach ( $results as $note ) {
+ $order_notes[] = array(
+ 'id' => $note->comment_ID,
+ 'date' => $note->comment_date,
+ 'author' => $note->comment_author,
+ 'content' => $note->comment_content,
+ );
+ }
+
+ return $order_notes;
+}
--- a/reepay-checkout-gateway/updates/update-1.2.9.php
+++ b/reepay-checkout-gateway/updates/update-1.2.9.php
@@ -1,75 +1,75 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( empty( $is_webhook_configured ) &&
- ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
-) {
- // Check the webhooks settings
- try {
- $result = reepay()->api( $gateway )->request('GET', 'https://api.reepay.com/v1/account/webhook_settings' );
- if ( is_wp_error( $result ) ) {
- /** @var WP_Error $result */
- throw new Exception( $result->get_error_message(), $result->get_error_code() );
- }
-
- // The webhook settings
- $urls = $result['urls'];
- $alert_emails = $result['alert_emails'];
-
- // The webhook settings of the payment plugin
- $webhook_url = WC()->api_request_url( get_class( $gateway ) );
- $alert_email = '';
- if ( ! empty( $gateway->failed_webhooks_email ) &&
- is_email( $gateway->failed_webhooks_email )
- ) {
- $alert_email = $gateway->failed_webhooks_email;
- }
-
- // Verify the webhook settings
- if ( in_array( $webhook_url, $urls ) &&
- ( empty( $alert_email ) || in_array( $alert_email, $alert_emails ) )
- ) {
- // Webhook has been configured before
- $gateway->update_option( 'is_webhook_configured', 'yes' );
- } else {
- // Update the webhook settings
- try {
- $urls[] = $webhook_url;
-
- if ( ! empty( $alert_email ) && is_email( $alert_email ) ) {
- $alert_emails[] = $alert_email;
- }
-
- $data = array(
- 'urls' => array_unique( $urls ),
- 'disabled' => false,
- 'alert_emails' => array_unique( $alert_emails )
- );
-
- $result = reepay()->api( $gateway )->request('PUT', 'https://api.reepay.com/v1/account/webhook_settings', $data);
- if ( is_wp_error( $result ) ) {
- /** @var WP_Error $result */
- throw new Exception( $result->get_error_message(), $result->get_error_code() );
- }
-
- $log->log( $handler, sprintf( 'WebHook has been successfully created/updated: %s', var_export( $result, true ) ) );
- $gateway->update_option( 'is_webhook_configured', 'yes' );
- } catch ( Exception $e ) {
- $gateway->update_option( 'is_webhook_configured', 'no' );
- $log->log( $handler, sprintf( 'WebHook creation/update has been failed: %s', var_export( $result, true ) ) );
- }
- }
- } catch ( Exception $e ) {
- $gateway->update_option( 'is_webhook_configured', 'no' );
- $log->log( $handler, 'Unable to retrieve the webhook settings. Wrong api credentials?' );
- }
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( empty( $is_webhook_configured ) &&
+ ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
+) {
+ // Check the webhooks settings
+ try {
+ $result = reepay()->api( $gateway )->request('GET', 'https://api.reepay.com/v1/account/webhook_settings' );
+ if ( is_wp_error( $result ) ) {
+ /** @var WP_Error $result */
+ throw new Exception( $result->get_error_message(), $result->get_error_code() );
+ }
+
+ // The webhook settings
+ $urls = $result['urls'];
+ $alert_emails = $result['alert_emails'];
+
+ // The webhook settings of the payment plugin
+ $webhook_url = WC()->api_request_url( get_class( $gateway ) );
+ $alert_email = '';
+ if ( ! empty( $gateway->failed_webhooks_email ) &&
+ is_email( $gateway->failed_webhooks_email )
+ ) {
+ $alert_email = $gateway->failed_webhooks_email;
+ }
+
+ // Verify the webhook settings
+ if ( in_array( $webhook_url, $urls ) &&
+ ( empty( $alert_email ) || in_array( $alert_email, $alert_emails ) )
+ ) {
+ // Webhook has been configured before
+ $gateway->update_option( 'is_webhook_configured', 'yes' );
+ } else {
+ // Update the webhook settings
+ try {
+ $urls[] = $webhook_url;
+
+ if ( ! empty( $alert_email ) && is_email( $alert_email ) ) {
+ $alert_emails[] = $alert_email;
+ }
+
+ $data = array(
+ 'urls' => array_unique( $urls ),
+ 'disabled' => false,
+ 'alert_emails' => array_unique( $alert_emails )
+ );
+
+ $result = reepay()->api( $gateway )->request('PUT', 'https://api.reepay.com/v1/account/webhook_settings', $data);
+ if ( is_wp_error( $result ) ) {
+ /** @var WP_Error $result */
+ throw new Exception( $result->get_error_message(), $result->get_error_code() );
+ }
+
+ $log->log( $handler, sprintf( 'WebHook has been successfully created/updated: %s', var_export( $result, true ) ) );
+ $gateway->update_option( 'is_webhook_configured', 'yes' );
+ } catch ( Exception $e ) {
+ $gateway->update_option( 'is_webhook_configured', 'no' );
+ $log->log( $handler, sprintf( 'WebHook creation/update has been failed: %s', var_export( $result, true ) ) );
+ }
+ }
+ } catch ( Exception $e ) {
+ $gateway->update_option( 'is_webhook_configured', 'no' );
+ $log->log( $handler, 'Unable to retrieve the webhook settings. Wrong api credentials?' );
+ }
+}
--- a/reepay-checkout-gateway/updates/update-1.4.54.php
+++ b/reepay-checkout-gateway/updates/update-1.4.54.php
@@ -1,17 +1,17 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( empty( $is_webhook_configured ) &&
- ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
-) {
- $gateway->is_webhook_configured();
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( empty( $is_webhook_configured ) &&
+ ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test ) )
+) {
+ $gateway->is_webhook_configured();
+}
--- a/reepay-checkout-gateway/updates/update-1.4.70.php
+++ b/reepay-checkout-gateway/updates/update-1.4.70.php
@@ -1,16 +1,16 @@
-<?php
-
-use ReepayCheckoutGatewaysReepayCheckout;
-
-defined( 'ABSPATH' ) || exit;
-
-// Logger
-$log = new WC_Logger();
-$handler = 'wc-reepay-update';
-
-$gateway = new ReepayCheckout();
-$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
-if ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test )
-) {
- $gateway->is_webhook_configured();
-}
+<?php
+
+use ReepayCheckoutGatewaysReepayCheckout;
+
+defined( 'ABSPATH' ) || exit;
+
+// Logger
+$log = new WC_Logger();
+$handler = 'wc-reepay-update';
+
+$gateway = new ReepayCheckout();
+$is_webhook_configured = $gateway->get_option( 'is_webhook_configured' );
+if ( ! empty( $gateway->private_key ) || ! empty( $gateway->private_key_test )
+) {
+ $gateway->is_webhook_configured();
+}