Skip to content

Commit

Permalink
fix(ras): only sync spend total and last payment amounts for complete…
Browse files Browse the repository at this point in the history
…d orders (#2886)

* fix(ras): only sync spend total and last payment amounts for completed orders

* fix: no need to manually add last order amount to total

* fix: logic for updating upon subscription status change

* fix: apply ex- status only for cancelled or expired subs

* refactor: syntax fixes
  • Loading branch information
dkoo authored Jan 31, 2024
1 parent 496723a commit 68aaf39
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 68 deletions.
12 changes: 5 additions & 7 deletions includes/data-events/connectors/class-activecampaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ public static function reader_logged_in( $timestamp, $data, $client_id ) {
return;
}

$last_order = $customer->get_last_order();
$contact = WooCommerce_Connection::get_contact_from_order( $last_order );
$contact = WooCommerce_Connection::get_contact_from_customer( $customer );

self::put( $contact );
}
Expand All @@ -126,7 +125,7 @@ public static function order_completed( $timestamp, $data, $client_id ) {
}

$order_id = $data['platform_data']['order_id'];
$contact = WooCommerce_Connection::get_contact_from_order( $order_id, false, true );
$contact = WooCommerce_Connection::get_contact_from_order( $order_id, $data['referer'], true );

if ( ! $contact ) {
return;
Expand All @@ -143,7 +142,7 @@ public static function order_completed( $timestamp, $data, $client_id ) {
* @param int $client_id ID of the client that triggered the event.
*/
public static function subscription_updated( $timestamp, $data, $client_id ) {
if ( empty( $data['subscription_id'] ) || empty( $data['status_before'] ) || empty( $data['status_after'] ) ) {
if ( empty( $data['status_before'] ) || empty( $data['status_after'] ) || empty( $data['user_id'] ) ) {
return;
}

Expand All @@ -157,9 +156,8 @@ public static function subscription_updated( $timestamp, $data, $client_id ) {
return;
}

$subscription = \wcs_get_subscription( $data['subscription_id'] );
$order = $subscription->get_last_order( 'all' );
$contact = WooCommerce_Connection::get_contact_from_order( $order );
$customer = new \WC_Customer( $data['user_id'] );
$contact = WooCommerce_Connection::get_contact_from_customer( $customer );

if ( ! $contact ) {
return;
Expand Down
12 changes: 5 additions & 7 deletions includes/data-events/connectors/class-mailchimp.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ public static function reader_logged_in( $timestamp, $data, $client_id ) {
return;
}

$last_order = $customer->get_last_order();
$contact = WooCommerce_Connection::get_contact_from_order( $last_order );
$contact = WooCommerce_Connection::get_contact_from_customer( $customer );

self::put( $contact );
}
Expand All @@ -275,7 +274,7 @@ public static function order_completed( $timestamp, $data, $client_id ) {
}

$order_id = $data['platform_data']['order_id'];
$contact = WooCommerce_Connection::get_contact_from_order( $order_id, false, true );
$contact = WooCommerce_Connection::get_contact_from_order( $order_id, $data['referer'], true );

if ( ! $contact ) {
return;
Expand All @@ -292,7 +291,7 @@ public static function order_completed( $timestamp, $data, $client_id ) {
* @param int $client_id ID of the client that triggered the event.
*/
public static function subscription_updated( $timestamp, $data, $client_id ) {
if ( empty( $data['subscription_id'] ) || empty( $data['status_before'] ) || empty( $data['status_after'] ) ) {
if ( empty( $data['status_before'] ) || empty( $data['status_after'] ) || empty( $data['user_id'] ) ) {
return;
}

Expand All @@ -306,9 +305,8 @@ public static function subscription_updated( $timestamp, $data, $client_id ) {
return;
}

$subscription = \wcs_get_subscription( $data['subscription_id'] );
$order = $subscription->get_last_order( 'all' );
$contact = WooCommerce_Connection::get_contact_from_order( $order );
$customer = new \WC_Customer( $data['user_id'] );
$contact = WooCommerce_Connection::get_contact_from_customer( $customer );

if ( ! $contact ) {
return;
Expand Down
187 changes: 133 additions & 54 deletions includes/reader-revenue/woocommerce/class-woocommerce-connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,32 +90,58 @@ public static function order_paid( $order_id ) {
}

/**
* Get the contact data from a WooCommerce order.
* Get the last successful order for a given customer.
*
* @param \WC_Customer $customer Customer object.
*
* @return \WC_Order|false Order object or false.
*/
public static function get_last_successful_order( $customer ) {
if ( ! is_a( $customer, 'WC_Customer' ) ) {
return false;
}

// See https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query for query args.
$args = [
'customer_id' => $customer->get_id(),
'status' => [ 'wc-completed' ],
'limit' => 1,
'order' => 'DESC',
'orderby' => 'date',
'return' => 'objects',
];

$orders = wc_get_orders( $args );

if ( ! empty( $orders ) ) {
return reset( $orders );
}

return false;
}

/**
* Get data about a customer's order to sync to the connected ESP.
*
* @param \WC_Order|int $order WooCommerce order or order ID.
* @param bool|string $payment_page_url Payment page URL. If not provided, checkout URL will be used.
* @param bool $is_new Whether the order is new and should count towards the contact's total spent value.
* @param bool $is_new Whether the order is new and should count as the last payment amount.
*
* @return array|false Contact data or false.
* @return array Contact order metadata.
*/
public static function get_contact_from_order( $order, $payment_page_url = false, $is_new = false ) {
public static function get_contact_order_metadata( $order, $payment_page_url = false, $is_new = false ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
$order = new \WC_Order( $order );
}

if ( ! self::should_sync_order( $order ) ) {
return;
return [];
}

$user_id = $order->get_customer_id();
if ( ! $user_id ) {
return false;
}
// Only update last payment data if new payment has been received.
$payment_received = $is_new && $order->has_status( [ 'processing', 'completed' ] );

$customer = new \WC_Customer( $user_id );

$metadata[ Newspack_Newsletters::get_metadata_key( 'account' ) ] = $order->get_customer_id();
$metadata[ Newspack_Newsletters::get_metadata_key( 'registration_date' ) ] = $customer->get_date_created()->date( Newspack_Newsletters::METADATA_DATE_FORMAT );
$metadata = [];

$referer_from_order = $order->get_meta( '_newspack_referer' );
if ( empty( $referer_from_order ) ) {
Expand All @@ -132,62 +158,55 @@ public static function get_contact_from_order( $order, $payment_page_url = false
}
}

$order_subscriptions = wcs_get_subscriptions_for_order( $order->get_id(), [ 'order_type' => 'any' ] );
$order_subscriptions = \wcs_get_subscriptions_for_order( $order->get_id(), [ 'order_type' => 'any' ] );
$is_donation_order = Donations::is_donation_order( $order );

// One-time transaction.
if ( empty( $order_subscriptions ) ) {
if ( $is_donation_order ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Donor';
}
$metadata[ Newspack_Newsletters::get_metadata_key( 'total_paid' ) ] = (float) $customer->get_total_spent();

if ( $is_new && 'pending' === $order->get_status() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'total_paid' ) ] += (float) $order->get_total();
}

$metadata[ Newspack_Newsletters::get_metadata_key( 'product_name' ) ] = '';
$order_items = $order->get_items();
if ( $order_items ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'product_name' ) ] = reset( $order_items )->get_name();
}
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_amount' ) ] = $order->get_total();
$order_date_paid = $order->get_date_paid();
if ( null !== $order_date_paid ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_date' ) ] = $order_date_paid->date( Newspack_Newsletters::METADATA_DATE_FORMAT );
if ( $payment_received && ! empty( $order_date_paid ) ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_amount' ) ] = \wc_format_localized_price( $order->get_total() );
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_date' ) ] = $order_date_paid->date( Newspack_Newsletters::METADATA_DATE_FORMAT );
}

// Subscription transaction.
} else {
$current_subscription = reset( $order_subscriptions );

if ( $is_donation_order ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Donor';
if ( 'active' === $current_subscription->get_status() || 'pending' === $current_subscription->get_status() ) {
if ( 'month' === $current_subscription->get_billing_period() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Monthly Donor';
}

if ( 'year' === $current_subscription->get_billing_period() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Yearly Donor';
}
} else {
if ( 'month' === $current_subscription->get_billing_period() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Ex-Monthly Donor';
}

if ( 'year' === $current_subscription->get_billing_period() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = 'Ex-Yearly Donor';
}
$donor_status = 'Donor';
if ( 'month' === $current_subscription->get_billing_period() ) {
$donor_status = 'Monthly ' . $donor_status;
}
if ( 'year' === $current_subscription->get_billing_period() ) {
$donor_status = 'Yearly ' . $donor_status;
}

// If the subscription has moved to a cancelled or expired status.
if ( $current_subscription->has_status( [ 'cancelled', 'expired' ] ) ) {
$donor_status = 'Ex-' . $donor_status;
}
$metadata[ Newspack_Newsletters::get_metadata_key( 'membership_status' ) ] = $donor_status;
}

$metadata[ Newspack_Newsletters::get_metadata_key( 'sub_start_date' ) ] = $current_subscription->get_date( 'start' );
$metadata[ Newspack_Newsletters::get_metadata_key( 'sub_end_date' ) ] = $current_subscription->get_date( 'end' ) ? $current_subscription->get_date( 'end' ) : '';
$metadata[ Newspack_Newsletters::get_metadata_key( 'billing_cycle' ) ] = $current_subscription->get_billing_period();
$metadata[ Newspack_Newsletters::get_metadata_key( 'recurring_payment' ) ] = $current_subscription->get_total();
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_date' ) ] = $current_subscription->get_date( 'last_order_date_paid' ) ? $current_subscription->get_date( 'last_order_date_paid' ) : gmdate( Newspack_Newsletters::METADATA_DATE_FORMAT );
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_amount' ) ] = $current_subscription->get_total();
$metadata[ Newspack_Newsletters::get_metadata_key( 'sub_start_date' ) ] = $current_subscription->get_date( 'start' );
$metadata[ Newspack_Newsletters::get_metadata_key( 'sub_end_date' ) ] = $current_subscription->get_date( 'end' ) ? $current_subscription->get_date( 'end' ) : '';
$metadata[ Newspack_Newsletters::get_metadata_key( 'billing_cycle' ) ] = $current_subscription->get_billing_period();
$metadata[ Newspack_Newsletters::get_metadata_key( 'recurring_payment' ) ] = $current_subscription->get_total();

if ( $payment_received ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_amount' ) ] = \wc_format_localized_price( $current_subscription->get_total() );
$metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_date' ) ] = $current_subscription->get_date( 'last_order_date_paid' ) ? $current_subscription->get_date( 'last_order_date_paid' ) : gmdate( Newspack_Newsletters::METADATA_DATE_FORMAT );
}

// When a WC Subscription is terminated, the next payment date is set to 0. We don't want to sync that – the next payment date should remain as it was
// in the event of cancellation.
Expand All @@ -196,12 +215,6 @@ public static function get_contact_from_order( $order, $payment_page_url = false
$metadata[ Newspack_Newsletters::get_metadata_key( 'next_payment_date' ) ] = $next_payment_date;
}

$metadata[ Newspack_Newsletters::get_metadata_key( 'total_paid' ) ] = (float) $customer->get_total_spent();

if ( $is_new && 'pending' === $order->get_status() ) {
$metadata[ Newspack_Newsletters::get_metadata_key( 'total_paid' ) ] += (float) $current_subscription->get_total();
}

$metadata[ Newspack_Newsletters::get_metadata_key( 'product_name' ) ] = '';
if ( $current_subscription ) {
$subscription_order_items = $current_subscription->get_items();
Expand All @@ -211,11 +224,49 @@ public static function get_contact_from_order( $order, $payment_page_url = false
}
}

$first_name = $order->get_billing_first_name();
$last_name = $order->get_billing_last_name();
return $metadata;
}

/**
* Get data for a customer to sync to the connected ESP.
*
* @param \WC_Customer $customer Customer object.
* @param \WC_Order|false $order Order object to sync with. If not given, the last successful order will be used.
* @param bool|string $payment_page_url Payment page URL. If not provided, checkout URL will be used.
* @param bool $is_new Whether the order is new and should count as the last payment amount.
*
* @return array|false Contact data or false.
*/
public static function get_contact_from_customer( $customer, $order = false, $payment_page_url = false, $is_new = false ) {
if ( ! is_a( $customer, 'WC_Customer' ) ) {
return false;
}

$metadata = [];
$order_metadata = [];

$metadata[ Newspack_Newsletters::get_metadata_key( 'account' ) ] = $customer->get_id();
$metadata[ Newspack_Newsletters::get_metadata_key( 'registration_date' ) ] = $customer->get_date_created()->date( Newspack_Newsletters::METADATA_DATE_FORMAT );
$metadata[ Newspack_Newsletters::get_metadata_key( 'total_paid' ) ] = \wc_format_localized_price( $customer->get_total_spent() );

if ( ! $order ) {
$order = self::get_last_successful_order( $customer );
}

if ( $order ) {
$order_metadata = self::get_contact_order_metadata( $order, $payment_page_url, $is_new );
} else {
// If the customer has no successful orders, ensure their spend totals are correct.
$order_metadata[ Newspack_Newsletters::get_metadata_key( 'last_payment_amount' ) ] = \wc_format_localized_price( '0.00' );
}

$metadata = array_merge( $metadata, $order_metadata );

$first_name = $customer->get_billing_first_name();
$last_name = $customer->get_billing_last_name();
$full_name = trim( "$first_name $last_name" );
$contact = [
'email' => $order->get_billing_email(),
'email' => $customer->get_billing_email(),
'metadata' => array_filter( $metadata ),
];
if ( ! empty( $full_name ) ) {
Expand All @@ -224,6 +275,30 @@ public static function get_contact_from_order( $order, $payment_page_url = false
return array_filter( $contact );
}

/**
* Get the contact data from a WooCommerce order.
*
* @param \WC_Order|int $order WooCommerce order or order ID.
* @param bool|string $payment_page_url Payment page URL. If not provided, checkout URL will be used.
* @param bool $is_new Whether the order is new and should count as the last payment amount.
*
* @return array|false Contact data or false.
*/
public static function get_contact_from_order( $order, $payment_page_url = false, $is_new = false ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
$order = new \WC_Order( $order );
}

if ( ! self::should_sync_order( $order ) ) {
return;
}

$user_id = $order->get_customer_id();
$customer = new \WC_Customer( $user_id );

return self::get_contact_from_customer( $customer, $order, $payment_page_url, $is_new );
}

/**
* Should an order be synchronized with the integrations?
*
Expand All @@ -234,6 +309,10 @@ public static function should_sync_order( $order ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
return false;
}
// If the order lacks a customer.
if ( ! $order->get_customer_id() ) {
return [];
}
if ( $order->get_meta( '_subscription_switch' ) ) {
// This is a "switch" order, which is just recording a subscription update. It has value of 0 and
// should not be synced anywhere.
Expand Down

0 comments on commit 68aaf39

Please sign in to comment.