Skip to content

Commit

Permalink
Merge pull request #3 from woocommerce/add/transaction-event
Browse files Browse the repository at this point in the history
Added support for $transaction event
  • Loading branch information
millerf authored Nov 13, 2024
2 parents 1936568 + dc87146 commit 581922d
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 35 deletions.
83 changes: 83 additions & 0 deletions src/inc/sift-object-validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,29 @@ class SiftObjectValidator {
'$other',
);


const SUPPORTED_TRANSACTION_STATUS = array(
'$success',
'$pending',
'$failure',
);

// See https://developers.sift.com/docs/curl/events-api/reserved-events/transaction
const SUPPORTED_TRANSACTION_TYPE = array(
'$sale',
'$authorize',
'$capture',
'$void',
'$refund',
'$deposit',
'$withdrawal',
'$transfer',
'$buy',
'$sell',
'$send',
'$receive',
);

const ORDER_STATUSES = array(
'$approved',
'$canceled',
Expand Down Expand Up @@ -1171,6 +1194,29 @@ public static function validate_discount( $data ) {
return true;
}


/**
* Validate AddPromotion.
*
* @param array $data The add_promotion to validate.
*
* @return true
* @throws \Exception If the add_promotion is invalid.
*/
public static function validate_add_promotion( $data ) {
$validator_map = array(
'$user_id' => array( __CLASS__, 'validate_id' ),
'$promotions' => static::validate_array_fn( array( __CLASS__, 'validate_promotion' ) ),
'$browser' => array( __CLASS__, 'validate_browser' ),
);
try {
static::validate( $data, $validator_map );
} catch ( \Exception $e ) {
throw new \Exception( 'invalid promotion: ' . esc_html( $e->getMessage() ) );
}
return true;
}

/**
* Validate Promotion.
*
Expand Down Expand Up @@ -1582,4 +1628,41 @@ public static function validate_order_status( array $data ) {

return true;
}

/**
* Validate the order status event.
*
* @param array $data The event to validate.
*
* @return true
* @throws \Exception If the event is invalid.
*/
public static function validate_transaction( array $data ) {
$validator_map = array(
'$user_id' => array( __CLASS__, 'validate_id' ),
'$amount' => 'is_int',
'$currency_code' => array( __CLASS__, 'validate_currency_code' ),
'$order_id' => 'is_string',
'$transaction_type' => self::SUPPORTED_TRANSACTION_TYPE,
'$transaction_status' => self::SUPPORTED_TRANSACTION_STATUS,
);

try {
static::validate( $data, $validator_map );
// Required fields for order status: $user_id, $order_id, $order_status
if ( empty( $data['$user_id'] ) ) {
throw new \Exception( 'missing $user_id' );
}
if ( empty( $data['$amount'] ) ) {
throw new \Exception( 'missing $amount' );
}
if ( empty( $data['$currency_code'] ) ) {
throw new \Exception( 'missing $currency_code' );
}
} catch ( \Exception $e ) {
throw new \Exception( 'Invalid $order_status event: ' . esc_html( $e->getMessage() ) );
}

return true;
}
}
120 changes: 107 additions & 13 deletions src/inc/woocommerce-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class Events {
public static $to_send = array();

const SUPPORTED_STATUS_CHANGES = array(
const SUPPORTED_WOO_ORDER_STATUS_CHANGES = array(
'pending',
'processing',
'on-hold',
Expand All @@ -38,14 +38,15 @@ public static function hooks() {
add_action( 'woocommerce_add_to_cart', array( static::class, 'add_to_cart' ), 100 );
add_action( 'woocommerce_remove_cart_item', array( static::class, 'remove_from_cart' ), 100, 2 );
add_action( 'woocommerce_update_order', array( static::class, 'update_order' ), 100, 2 );
add_action( 'woocommerce_order_applied_coupon', array( static::class, 'add_promotion' ), 100, 2 );

/**
* We need to break this out into separate actions so we have the $status_transition available.
*
* This limits the number of supported status transitions so if we have an unsupported transition we need to
* log it.
*/
foreach ( self::SUPPORTED_STATUS_CHANGES as $status ) {
foreach ( self::SUPPORTED_WOO_ORDER_STATUS_CHANGES as $status ) {
add_action( 'woocommerce_order_status_' . $status, array( static::class, 'change_order_status' ), 100, 3 );
}
// For unsupported actions.
Expand Down Expand Up @@ -82,6 +83,38 @@ public static function logout( string $user_id ) {
);
}

/**
* Adds $add_promotion event
*
* @link https://developers.sift.com/docs/curl/events-api/reserved-events/add-promotion
*
* @param \WC_Coupon $coupon Coupon used.
* @param \WC_Order $order Order.
*
* @return void
*/
public static function add_promotion( \WC_Coupon $coupon, \WC_Order $order ): void {

$properties = array(
'$user_id' => $order->get_user_id(),
'$promotions' => array(
'$promotion_id' => $coupon->get_id(),
),
'$ip' => self::get_client_ip(),
'$browser' => self::get_client_browser(),
'$time' => intval( 1000 * microtime( true ) ),
);

try {
SiftObjectValidator::validate_add_promotion( $properties );
} catch ( \Exception $e ) {
wc_get_logger()->error( esc_html( $e->getMessage() ) );
return;
}

self::add( '$add_promotion', $properties );
}

/**
* Adds the login success event
*
Expand Down Expand Up @@ -186,7 +219,7 @@ public static function create_account( string $user_id ) {
'$name' => $user->display_name,
'$phone' => $user ? get_user_meta( $user->ID, 'billing_phone', true ) : null,
// '$referrer_user_id' => ??? -- required for detecting referral fraud, but non-standard to woocommerce.
// '$payment_methods' => self::get_customer_payment_methods( $user->ID ),
'$payment_methods' => self::get_customer_payment_methods( $user->ID ),
'$billing_address' => self::get_customer_address( $user->ID, 'billing' ),
'$shipping_address' => self::get_customer_address( $user->ID, 'shipping' ),
'$browser' => self::get_client_browser(),
Expand Down Expand Up @@ -236,6 +269,7 @@ public static function update_account( string $user_id, ?\WP_User $old_user_data
'$phone' => $user ? get_user_meta( $user->ID, 'billing_phone', true ) : null,
// '$referrer_user_id' => ??? -- required for detecting referral fraud, but non-standard to woocommerce.
// '$payment_methods' => self::get_customer_payment_methods( $user->ID ),
'$payment_methods' => self::get_customer_payment_methods( $user->ID ),
'$billing_address' => self::get_customer_address( $user->ID, 'billing' ),
'$shipping_address' => self::get_customer_address( $user->ID, 'shipping' ),
'$browser' => self::get_client_browser(),
Expand Down Expand Up @@ -354,7 +388,7 @@ public static function add_to_cart( string $cart_item_key ) {
'$item_id' => $cart_item_key,
'$sku' => $product->get_sku(),
'$product_title' => $product->get_title(),
'$price' => $product->get_price() * 1000000, // $39.99
'$price' => self::get_transaction_micros( floatval( $product->get_price() ) ),
'$currency_code' => get_woocommerce_currency(),
'$quantity' => $cart_item['quantity'],
'$category' => $category,
Expand Down Expand Up @@ -403,7 +437,7 @@ public static function remove_from_cart( string $cart_item_key, \WC_Cart $cart )
'$item_id' => $product->get_id(),
'$sku' => $product->get_sku(),
'$product_title' => $product->get_title(),
'$price' => $product->get_price() * 1000000, // $39.99
'$price' => self::get_transaction_micros( floatval( $product->get_price() ) ),
'$currency_code' => get_woocommerce_currency(),
'$quantity' => $cart_item['quantity'],
'$tags' => wp_list_pluck( get_the_terms( $product->get_id(), 'product_tag' ), 'name' ),
Expand Down Expand Up @@ -469,7 +503,7 @@ public static function update_order( string $order_id, \WC_Order $order ) {
'$item_id' => (string) $product->get_id(),
'$sku' => $product->get_sku(),
'$product_title' => $product->get_name(),
'$price' => $product->get_price() * 1000000, // $39.99
'$price' => self::get_transaction_micros( floatval( $product->get_price() ) ),
'$currency_code' => $order->get_currency(), // For the order specifically, not the whole store.
'$quantity' => $item->get_quantity(),
'$category' => $category,
Expand All @@ -488,7 +522,7 @@ public static function update_order( string $order_id, \WC_Order $order ) {
'$order_id' => $order_id,
'$verification_phone_number'
=> $order->get_billing_phone(),
'$amount' => intval( $order->get_total() * 1000000 ), // Gotta multiply it up to give an integer.
'$amount' => self::get_transaction_micros( $order->get_total() ),
'$currency_code' => get_woocommerce_currency(),
'$items' => $items,
'$shipping_method' => $physical_or_electronic,
Expand Down Expand Up @@ -530,7 +564,7 @@ public static function update_order( string $order_id, \WC_Order $order ) {
* @return void
*/
public static function maybe_log_change_order_status( string $order_id, string $from, string $to ) {
if ( ! in_array( $to, self::SUPPORTED_STATUS_CHANGES, true ) ) {
if ( ! in_array( $to, self::SUPPORTED_WOO_ORDER_STATUS_CHANGES, true ) ) {
wc_get_logger()->error(
sprintf(
'Unsupported status change from %s to %s for order %s.',
Expand All @@ -542,6 +576,40 @@ public static function maybe_log_change_order_status( string $order_id, string $
}
}

/**
* Adds the event for transaction
*
* @link https://developers.sift.com/docs/curl/events-api/reserved-events/transaction
*
* @param \WC_Order $order Order.
* @param string $status Transaction status.
* @param string $transaction_type Transaction type.
*
* @return void
*/
public static function transaction( \WC_Order $order, string $status, string $transaction_type ) {
$properties = array(
'$user_id' => (string) $order->get_user_id(),
'$amount' => self::get_transaction_micros( $order->get_total() ), // Gotta multiply it up to give an integer.
'$currency_code' => $order->get_currency(),
'$order_id' => (string) $order->get_id(),
'$transaction_type' => $transaction_type,
'$transaction_status' => $status,
'$time' => intval( 1000 * microtime( true ) ),
);

try {
SiftObjectValidator::validate_transaction( $properties );
} catch ( \Exception $e ) {
wc_get_logger()->error( esc_html( $e->getMessage() ) );

return;
}

self::add( '$transaction', $properties );
}


/**
* Adds the event for the order status update
*
Expand All @@ -555,10 +623,6 @@ public static function maybe_log_change_order_status( string $order_id, string $
* @return void
*/
public static function change_order_status( string $order_id, \WC_Order $order, array $status_transition ) {
if ( ! in_array( $status_transition['to'], self::SUPPORTED_STATUS_CHANGES, true ) ) {
self::maybe_log_change_order_status( $order_id, $status_transition['from'], $status_transition['to'] );
return;
}

$properties = array(
'$user_id' => (string) $order->get_user_id(),
Expand All @@ -578,13 +642,21 @@ public static function change_order_status( string $order_id, \WC_Order $order,
case 'processing':
case 'on-hold':
$properties['$order_status'] = '$held';
self::transaction( $order, '$pending', '$sale' );
break;
case 'completed':
$properties['$order_status'] = '$fulfilled';

// When the status is completed, we also queue the $transaction event
self::transaction( $order, '$success', '$sale' );
break;
case 'cancelled':
case 'refunded':
self::transaction( $order, '$failure', '$refund' );
$properties['$order_status'] = '$canceled';
break;
case 'cancelled':
case 'failed':
self::transaction( $order, '$failure', '$sale' );
$properties['$order_status'] = '$canceled';
break;
}
Expand Down Expand Up @@ -832,4 +904,26 @@ public static function get_customer_payment_methods( int $user_id ) {

return $payment_methods ?? null;
}

/**
* Return the amount of transaction "micros"
*
* @link https://developers.sift.com/docs/curl/events-api/reserved-events/transaction in the $amount
*
* @param float $price The price to format.
*
* @return integer
*/
public static function get_transaction_micros( float $price ) {
$currencies_without_decimals = array( 'JPY' );

$current_currency = get_woocommerce_currency();

if ( in_array( $current_currency, $currencies_without_decimals, true ) ) {
return intval( $price * 1000000 );
}

// For currencies with decimals
return intval( $price * 10000 );
}
}
16 changes: 16 additions & 0 deletions tests/OrderStatusEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ public function test_change_order_status() {
wp_delete_user( $user_id );
}

/**
* Test get_transaction_micros
*
* @return void
*/
public static function test_get_transaction_micros_based_on_currency() {
update_option( 'woocommerce_currency', 'USD' );

static::assertEquals( Events::get_transaction_micros( 39.00 ), 390000 );

update_option( 'woocommerce_currency', 'JPY' );

static::assertEquals( Events::get_transaction_micros( 39 ), 39000000 );
}


/**
* Assert $order_status event is triggered.
*
Expand Down
22 changes: 0 additions & 22 deletions tests/SampleTest.php

This file was deleted.

0 comments on commit 581922d

Please sign in to comment.