Skip to content

Commit

Permalink
fix(esp-sync): schedule second sync upon subscription reactivation, j…
Browse files Browse the repository at this point in the history
…ust in case (#3603)

* fix(esp-sync): schedule second sync upon subscription reactivation, just in case

* test: add unit test for scheduled sync

* Revert "test: add unit test for scheduled sync"

This reverts commit ee581ed.

* feat: if passed data, log that, too

* fix: descended method

* refactor: scheduled sync upon subscription renewal via Data_Events

* refactor: update name of subscription_renewed data event, for clarity

* fix: remove unintentional changes

* docs: inline comment for clarification

Co-authored-by: leogermani <[email protected]>

---------

Co-authored-by: leogermani <[email protected]>
  • Loading branch information
dkoo and leogermani authored Dec 9, 2024
1 parent 69b8ac3 commit 9334295
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
8 changes: 7 additions & 1 deletion includes/cli/class-ras-esp-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ class RAS_ESP_Sync extends Reader_Activation\ESP_Sync {
* Log to WP CLI.
*
* @param string $message The message to log.
* @param array $data Optional. Additional data to log.
*/
protected static function log( $message ) {
protected static function log( $message, $data = [] ) {
WP_CLI::log( $message );
if ( ! empty( $data ) ) {
WP_CLI::log(
wp_json_encode( $data )
);
}
}

/**
Expand Down
19 changes: 19 additions & 0 deletions includes/data-events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,25 @@ When a non-donation subscription status changes, or when there's a subscription
| `user_id` | `int` |
| `email` | `string` |
| `subscription_id` | `int` |
| `product_ids` | `array` |
| `amount` | `float` |
| `currency` | `string` |
| `recurrence` | `string` |
| `status_before` | `string` |
| `status_after` | `string` |
| `user_first_name` | `string` |
| `user_last_name` | `string` |

### `subscription_renewal_attempt`

When a subscription of any type (donation or non-donation) renews, a renewal order gets created for it in Woo. This indicates a renewal attempt (at this point the order might still succeed or fail when collecting payment). We can trigger actions based on the renewal attempt, such as a scheduled contact sync to the ESP.

| Name | Type |
| ----------------- | -------- |
| `user_id` | `int` |
| `email` | `string` |
| `order_id` | `int` |
| `subscription_id` | `int` |
| `amount` | `float` |
| `currency` | `string` |
| `recurrence` | `string` |
Expand Down
39 changes: 38 additions & 1 deletion includes/data-events/connectors/class-esp-connector.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static function register_handlers() {
Data_Events::register_handler( [ __CLASS__, 'order_completed' ], 'order_completed' );
Data_Events::register_handler( [ __CLASS__, 'subscription_updated' ], 'donation_subscription_changed' );
Data_Events::register_handler( [ __CLASS__, 'subscription_updated' ], 'product_subscription_changed' );
Data_Events::register_handler( [ __CLASS__, 'subscription_renewal_attempt' ], 'subscription_renewal_attempt' );
Data_Events::register_handler( [ __CLASS__, 'newsletter_updated' ], 'newsletter_subscribed' );
Data_Events::register_handler( [ __CLASS__, 'newsletter_updated' ], 'newsletter_updated' );
Data_Events::register_handler( [ __CLASS__, 'network_new_reader' ], 'network_new_reader' );
Expand Down Expand Up @@ -140,7 +141,43 @@ public static function subscription_updated( $timestamp, $data, $client_id ) {
return;
}

self::sync( $contact, sprintf( 'RAS Woo Subscription updated. Status changed from %s to %s', $data['status_before'], $data['status_after'] ) );
self::sync(
$contact,
sprintf(
'RAS Woo Subscription updated. Status changed from %s to %s',
$data['status_before'],
$data['status_after']
)
);
}

/**
* Handle a subscription renewal attempt.
*
* @param int $timestamp Timestamp of the event.
* @param array $data Data associated with the event.
* @param int $client_id ID of the client that triggered the event.
*/
public static function subscription_renewal_attempt( $timestamp, $data, $client_id ) {
if ( empty( $data['subscription_id'] ) || empty( $data['user_id'] ) || empty( $data['email'] ) ) {
return;
}

/**
* When a renewal happens, it triggers two syncs to the ESP, one setting the subscription as on hold, and a
* second one setting it back to active. This sometimes creates a race condition on the ESP side.
* This third request will make sure the ESP always has the correct and most up to date data about the reader.
*/
self::schedule_sync(
$data['user_id'],
sprintf(
// Translators: %d is the subscription ID and %s is the customer's email address.
'RAS Woo subscription %d for %s renewed.',
$data['subscription_id'],
$data['email']
),
120 // Schedule an ESP sync in 2 minutes.
);
}

/**
Expand Down
19 changes: 19 additions & 0 deletions includes/data-events/listeners.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,25 @@ function( $product_id ) {
}
);

/**
* When a subscription of any type renews.
*/
Data_Events::register_listener(
'wcs_renewal_order_created',
'subscription_renewal_attempt',
function( $renewal_order, $subscription ) {
return [
'user_id' => $subscription->get_customer_id(),
'email' => $subscription->get_billing_email(),
'order_id' => $renewal_order->get_id(),
'subscription_id' => $subscription->get_id(),
'amount' => (float) $subscription->get_total(),
'currency' => $subscription->get_currency(),
'recurrence' => $subscription->get_billing_period(),
];
}
);

/**
* For when a non-donation subscription is switched (recurrence/amount change).
*/
Expand Down
56 changes: 55 additions & 1 deletion includes/reader-activation/sync/class-esp-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class ESP_Sync extends Sync {
* @var string
*/
protected static $context = 'ESP Sync';
/**
* Initialize hooks.
*/
public static function init_hooks() {
add_action( 'newspack_scheduled_esp_sync', [ __CLASS__, 'scheduled_sync' ], 10, 2 );
}

/**
* Whether contacts can be synced to the ESP.
Expand Down Expand Up @@ -82,7 +88,7 @@ public static function can_esp_sync( $return_errors = false ) {
*
* @return true|\WP_Error True if succeeded or WP_Error.
*/
protected static function sync( $contact, $context = '' ) {
public static function sync( $contact, $context = '' ) {
$can_sync = static::can_esp_sync( true );
if ( $can_sync->has_errors() ) {
return $can_sync;
Expand All @@ -109,6 +115,53 @@ protected static function sync( $contact, $context = '' ) {
return \is_wp_error( $result ) ? $result : true;
}

/**
* Schedule a future sync.
*
* @param int $user_id The user ID for the contact to sync.
* @param string $context The context of the sync.
* @param int $delay The delay in seconds.
*/
public static function schedule_sync( $user_id, $context, $delay ) {
// Schedule another sync in $delay number of seconds.
if ( ! is_int( $delay ) ) {
return;
}

$user = get_userdata( $user_id );
if ( ! $user ) {
return;
}

static::log(
sprintf(
// Translators: %s is the email address of the contact to synced.
__( 'Scheduling secondary sync for contact %s.', 'newspack-plugin' ),
$user->data->user_email
),
[
'user_email' => $user->data->user_email,
'user_id' => $user_id,
'context' => $context,
]
);
\wp_schedule_single_event( \time() + $delay, 'newspack_scheduled_esp_sync', [ $user_id, $context ] );
}

/**
* Handle a scheduled sync event.
*
* @param int $user_id The user ID for the contact to sync.
* @param string $context The context of the sync.
*/
public static function scheduled_sync( $user_id, $context ) {
$contact = Sync\WooCommerce::get_contact_from_customer( new \WC_Customer( $user_id ) );
if ( ! $contact ) {
return;
}
self::sync( $contact, $context );
}

/**
* Given a user ID or WooCommerce Order, sync that reader's contact data to
* the connected ESP.
Expand Down Expand Up @@ -167,3 +220,4 @@ public static function sync_contact( $user_id_or_order, $is_dry_run = false ) {
return $result;
}
}
ESP_Sync::init_hooks();
11 changes: 10 additions & 1 deletion includes/reader-activation/sync/class-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,18 @@ class Sync {
* Log a message to the Newspack Logger.
*
* @param string $message The message to log.
* @param array $data Optional. Additional data to log.
*/
protected static function log( $message ) {
protected static function log( $message, $data = [] ) {
Logger::log( $message, 'NEWSPACK-SYNC' );
if ( ! empty( $data ) ) {
Logger::newspack_log(
'newspack_sync',
$message,
$data,
'debug'
);
}
}

/**
Expand Down

0 comments on commit 9334295

Please sign in to comment.