Initial commit
This commit is contained in:
+752
@@ -0,0 +1,752 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Modules\AbandonCart\Drivers\FluentCart;
|
||||
|
||||
use FluentCart\Api\StoreSettings;
|
||||
use FluentCart\App\Helpers\AddressHelper;
|
||||
use FluentCart\App\Models\Cart;
|
||||
use FluentCrm\App\Modules\AbandonCart\AbandonCartModel;
|
||||
use FluentCrm\App\Modules\AbandonCart\AbCartHelper;
|
||||
use FluentCrm\App\Models\FunnelSubscriber;
|
||||
use FluentCrm\App\Models\Subscriber;
|
||||
use FluentCrm\Framework\Support\Arr;
|
||||
|
||||
class FluentCartTrackingInit
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// Funnel Automations
|
||||
(new FluentCartAutomationTrigger())->register();
|
||||
|
||||
// Checkout Frontend - inject tracking script
|
||||
add_action('fluent_cart/after_checkout_page', [$this, 'addAbandonScript']);
|
||||
|
||||
// AJAX handler for GDPR opt-out
|
||||
add_action('wp_ajax_fc_ab_fct_cart_skip', [$this, 'handleAjaxOptOut']);
|
||||
add_action('wp_ajax_nopriv_fc_ab_fct_cart_skip', [$this, 'handleAjaxOptOut']);
|
||||
|
||||
// Sync abandoned cart when FluentCart saves checkout data
|
||||
add_filter('fluent_cart/checkout/after_patch_checkout_data_fragments', function ($fragments, $data) {
|
||||
$this->maybeSyncCart(Arr::get($data, 'cart'));
|
||||
return $fragments;
|
||||
}, 99, 2);
|
||||
|
||||
// Sync abandoned cart when cart amounts change (item add/remove, coupon)
|
||||
add_action('fluent_cart/checkout/cart_amount_updated', function ($data) {
|
||||
$this->maybeSyncCart(Arr::get($data, 'cart'));
|
||||
}, 99);
|
||||
|
||||
// Sync abandoned cart on form data change (alternative save path)
|
||||
add_action('fluent_cart/checkout/form_data_changed', function ($data) {
|
||||
$this->maybeSyncCart(Arr::get($data, 'cart'));
|
||||
}, 99);
|
||||
|
||||
// Cart recovery URL handler
|
||||
// URL: example.com/?fluentcrm=1&route=general&handler=fc_cart_fluent_cart&fc_ab_hash=xyz
|
||||
add_action('fluent_crm/handle_frontend_for_fc_cart_fluent_cart', function ($data) {
|
||||
add_action('template_redirect', function () use ($data) {
|
||||
$this->maybeRestoreCart($data);
|
||||
}, 1);
|
||||
});
|
||||
|
||||
// Order lifecycle - link cart to order when created
|
||||
add_action('fluent_cart/order_created', [$this, 'handleOrderCreated'], 1);
|
||||
|
||||
// Order paid - mark cart as recovered
|
||||
add_action('fluent_cart/order_paid', [$this, 'handleOrderPaid'], 1);
|
||||
|
||||
// Order status changes
|
||||
add_action('fluent_cart/order_status_changed', [$this, 'handleOrderStatusChanged'], 10);
|
||||
|
||||
// Push contextual smart codes for this provider
|
||||
add_filter('fluent_crm_funnel_context_smart_codes', [$this, 'pushContextCodes'], 1, 2);
|
||||
|
||||
// Parse the context codes
|
||||
add_filter('fluent_crm/smartcode_group_callback_ab_cart_fluent_cart', [$this, 'parseSmartCodes'], 10, 4);
|
||||
}
|
||||
|
||||
public function addAbandonScript()
|
||||
{
|
||||
if (!AbCartHelper::willCartTrack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_COOKIE['fc_ab_cart_skip_track']) && $_COOKIE['fc_ab_cart_skip_track'] == 'yes') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'fluent_crm-abandon-cart-fct',
|
||||
FLUENTCRM_PLUGIN_URL . 'app/Modules/AbandonCart/Drivers/FluentCart/assets/fc-cart-abandon-fluent-cart.js',
|
||||
[],
|
||||
FLUENTCRM_PLUGIN_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script('fluent_crm-abandon-cart-fct', 'fc_ab_fct_cart', [
|
||||
'nonce' => wp_create_nonce('fc_ab_fct_cart_nonce'),
|
||||
'__gdpr_message' => AbCartHelper::getGDPRMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handleAjaxOptOut()
|
||||
{
|
||||
$nonce = Arr::get($_REQUEST, '_nonce');
|
||||
if (!wp_verify_nonce($nonce, 'fc_ab_fct_cart_nonce')) {
|
||||
wp_send_json([
|
||||
'message' => __('Security check failed. Invalid nonce.', 'fluent-crm')
|
||||
], 403);
|
||||
}
|
||||
|
||||
$record = $this->getCurrentRecord();
|
||||
|
||||
if ($record) {
|
||||
$record->optOut();
|
||||
}
|
||||
|
||||
$cookieDays = (int)apply_filters('fluent_crm/ab_cart_opt_out_cookie_validity', 7);
|
||||
setcookie('fc_ab_cart_skip_track', 'yes', time() + (86400 * $cookieDays), COOKIEPATH, COOKIE_DOMAIN);
|
||||
|
||||
wp_send_json([
|
||||
'message' => __('You have opted out from cart tracking', 'fluent-crm')
|
||||
]);
|
||||
}
|
||||
|
||||
public function maybeSyncCart($fctCart)
|
||||
{
|
||||
if (!$fctCart || !AbCartHelper::willCartTrack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$fctCart->email || empty($fctCart->cart_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$billingEmail = $fctCart->email;
|
||||
|
||||
if (isset($_COOKIE['fc_ab_cart_skip_track']) && $_COOKIE['fc_ab_cart_skip_track'] == 'yes') {
|
||||
$record = $this->getCurrentRecord($billingEmail, $fctCart->cart_hash);
|
||||
if ($record && $record->status !== 'opt_out') {
|
||||
$record->status = 'opt_out';
|
||||
$record->save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$checkoutData = $fctCart->checkout_data ?: [];
|
||||
|
||||
// Calculate totals for the table columns (FluentCart stores amounts in cents).
|
||||
// $subtotal is the items' price BEFORE any discount.
|
||||
$subtotal = $fctCart->getItemsSubtotal();
|
||||
|
||||
// Coupon and per-item manual discounts are stored on each cart item as
|
||||
// discount_total (manual_discount + coupon_discount). The old code only read
|
||||
// custom_checkout_data.discount_total, which is empty for normal frontend
|
||||
// coupons, so the coupon was never reflected in the cart total. Sum the per-item
|
||||
// discounts plus any checkout-level (manual/upgrade/prorate) discounts.
|
||||
$itemsDiscountTotal = array_sum(array_map(function ($item) {
|
||||
return (int)Arr::get($item, 'discount_total', 0);
|
||||
}, $fctCart->cart_data ?: []));
|
||||
|
||||
$discountTotal = $itemsDiscountTotal
|
||||
+ (int)Arr::get($checkoutData, 'manual_discount.amount', 0)
|
||||
+ (int)Arr::get($checkoutData, 'upgrade_discount.amount', 0)
|
||||
+ (int)Arr::get($checkoutData, 'prorate_credit.amount', 0);
|
||||
|
||||
$shippingTotal = (int)$fctCart->getShippingTotal();
|
||||
$taxTotal = (int)Arr::get($checkoutData, 'tax_data.tax_total', 0);
|
||||
|
||||
// Use FluentCart's authoritative total so the displayed Cart Total matches the
|
||||
// checkout exactly (coupons, fees, shipping and tax all included).
|
||||
$total = (int)$fctCart->getEstimatedTotal();
|
||||
|
||||
if ($total <= 0) {
|
||||
$record = $this->getCurrentRecord($billingEmail);
|
||||
if ($record) {
|
||||
$record->delete();
|
||||
setcookie('fc_ab_fct_cart_token', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$currency = '';
|
||||
if (class_exists('\FluentCart\Api\CurrencySettings')) {
|
||||
$currency = \FluentCart\Api\CurrencySettings::get('currency') ?: 'USD';
|
||||
}
|
||||
|
||||
$contact = FluentCrmApi('contacts')->getContact($billingEmail);
|
||||
$fullName = $fctCart->full_name ?? trim($fctCart->first_name . ' ' . $fctCart->last_name);
|
||||
if (!$fullName && $contact) {
|
||||
$fullName = trim($contact->first_name . ' ' . $contact->last_name);
|
||||
}
|
||||
|
||||
// Build a per-coupon breakdown (code + discounted value) for the cart details view.
|
||||
// FluentCart stores the applied codes on $fctCart->coupons and the amount each code
|
||||
// saved (in cents) on checkout_data.__per_coupon_discounts, keyed by code.
|
||||
$perCouponDiscounts = Arr::get($checkoutData, '__per_coupon_discounts', []);
|
||||
$couponDetails = [];
|
||||
foreach (($fctCart->coupons ?: []) as $couponCode) {
|
||||
$couponAmount = (int)Arr::get($perCouponDiscounts, $couponCode, 0);
|
||||
$couponDetails[] = [
|
||||
'code' => $couponCode,
|
||||
'discount' => number_format($couponAmount / 100, 2, '.', ''),
|
||||
];
|
||||
}
|
||||
|
||||
// Snapshot the FluentCart data as-is for easy restore
|
||||
$data = [
|
||||
'cart_hash' => $fctCart->cart_hash,
|
||||
'full_name' => $fullName,
|
||||
'email' => $billingEmail,
|
||||
'provider' => 'fluent_cart',
|
||||
'user_id' => $fctCart->user_id,
|
||||
'contact_id' => $contact ? $contact->id : null,
|
||||
'order_id' => $fctCart->order_id,
|
||||
'subtotal' => number_format($subtotal / 100, 2, '.', ''),
|
||||
'shipping' => number_format($shippingTotal / 100, 2, '.', ''),
|
||||
'discounts' => number_format($discountTotal / 100, 2, '.', ''),
|
||||
'tax' => number_format($taxTotal / 100, 2, '.', ''),
|
||||
'fees' => 0,
|
||||
'total' => number_format($total / 100, 2, '.', ''),
|
||||
'currency' => $currency,
|
||||
'cart' => [
|
||||
'cart_data' => $fctCart->cart_data ?: [],
|
||||
'checkout_data' => $checkoutData,
|
||||
'coupons' => $fctCart->coupons ?: [],
|
||||
'coupons_detail' => $couponDetails,
|
||||
'utm_data' => $fctCart->utm_data ?: [],
|
||||
'cart_group' => $fctCart->cart_group,
|
||||
'customer_data' => $this->buildCustomerData($checkoutData),
|
||||
],
|
||||
];
|
||||
|
||||
$record = $this->getCurrentRecord($billingEmail, $fctCart->cart_hash);
|
||||
|
||||
if (!$record) {
|
||||
$data['status'] = 'draft';
|
||||
$record = AbandonCartModel::create($data);
|
||||
} else {
|
||||
$record->fill($data);
|
||||
$record->save();
|
||||
}
|
||||
|
||||
$cookieDays = (int)apply_filters('fluent_crm/ab_cart_cookie_validity', 30);
|
||||
setcookie('fc_ab_fct_cart_token', $record->checkout_key, time() + (86400 * $cookieDays), COOKIEPATH, COOKIE_DOMAIN);
|
||||
}
|
||||
|
||||
private function buildCustomerData($checkoutData)
|
||||
{
|
||||
$formData = Arr::get($checkoutData, 'form_data', []);
|
||||
|
||||
return [
|
||||
'billingAddress' => [
|
||||
'first_name' => Arr::get($formData, 'billing_first_name', ''),
|
||||
'last_name' => Arr::get($formData, 'billing_last_name', ''),
|
||||
'address_1' => Arr::get($formData, 'billing_address_1', ''),
|
||||
'address_2' => Arr::get($formData, 'billing_address_2', ''),
|
||||
'postcode' => Arr::get($formData, 'billing_postcode', ''),
|
||||
'city' => Arr::get($formData, 'billing_city', ''),
|
||||
'state' => Arr::get($formData, 'billing_state', ''),
|
||||
'country' => Arr::get($formData, 'billing_country', ''),
|
||||
'phone' => Arr::get($formData, 'billing_phone', ''),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getCurrentRecord($billingEmail = null, $cartHash = null)
|
||||
{
|
||||
if ($cartHash) {
|
||||
// Try to find by cart hash first if available
|
||||
$record = AbandonCartModel::where('cart_hash', $cartHash)
|
||||
->where('provider', 'fluent_cart')
|
||||
->whereIn('status', ['pending', 'opt_out', 'draft', 'processing'])
|
||||
->first();
|
||||
|
||||
if ($record) {
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
|
||||
// First try from the cookie
|
||||
$existingToken = Arr::get($_COOKIE, 'fc_ab_fct_cart_token');
|
||||
|
||||
if ($existingToken) {
|
||||
$record = AbandonCartModel::where('checkout_key', $existingToken)
|
||||
->where('provider', 'fluent_cart')
|
||||
->whereIn('status', ['pending', 'opt_out', 'draft', 'processing'])
|
||||
->first();
|
||||
|
||||
if ($record) {
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
|
||||
// Try with billing email
|
||||
if ($billingEmail) {
|
||||
$record = AbandonCartModel::where('email', $billingEmail)
|
||||
->where('provider', 'fluent_cart')
|
||||
->whereIn('status', ['pending', 'opt_out', 'draft', 'processing'])
|
||||
->first();
|
||||
|
||||
if ($record) {
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
|
||||
// If user logged in, try with user id
|
||||
$userId = get_current_user_id();
|
||||
if ($userId) {
|
||||
$record = AbandonCartModel::where('user_id', $userId)
|
||||
->where('provider', 'fluent_cart')
|
||||
->whereIn('status', ['pending', 'opt_out', 'draft', 'processing'])
|
||||
->first();
|
||||
|
||||
if ($record) {
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function handleOrderCreated($eventData)
|
||||
{
|
||||
$order = Arr::get($eventData, 'order');
|
||||
if (!$order) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = sanitize_text_field(Arr::get($_COOKIE, 'fc_ab_fct_cart_token', ''));
|
||||
if (!$token) {
|
||||
return;
|
||||
}
|
||||
|
||||
$abCart = AbCartHelper::getAbCartByDataProps([
|
||||
'checkout_key' => $token
|
||||
], ['processing', 'draft']);
|
||||
|
||||
if (!$abCart || $abCart->provider !== 'fluent_cart') {
|
||||
return;
|
||||
}
|
||||
|
||||
$abCart->order_id = $order->id;
|
||||
$abCart->save();
|
||||
}
|
||||
|
||||
public function handleOrderPaid($eventData)
|
||||
{
|
||||
$order = Arr::get($eventData, 'order');
|
||||
$customer = Arr::get($eventData, 'customer');
|
||||
if (!$order || !$customer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$abCart = AbandonCartModel::query()->where('order_id', $order->id)->where('provider', 'fluent_cart')->first();
|
||||
|
||||
if (!$abCart) {
|
||||
$this->cancelAutomationsByCustomer($customer);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->markCartAsRecovered($abCart, $order);
|
||||
}
|
||||
|
||||
public function handleOrderStatusChanged($eventData)
|
||||
{
|
||||
$order = Arr::get($eventData, 'order');
|
||||
$newStatus = Arr::get($eventData, 'new_status');
|
||||
|
||||
if (!$order || !$newStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
$abCartId = $order->getMeta('_fc_ab_cart_id');
|
||||
if (!$abCartId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$abCart = AbandonCartModel::find($abCartId);
|
||||
if (!$abCart || $abCart->provider !== 'fluent_cart') {
|
||||
return;
|
||||
}
|
||||
|
||||
$driver = new FluentCartDriver();
|
||||
|
||||
if ($driver->isWinOrderStatus($newStatus)) {
|
||||
if ($abCart->status !== 'recovered') {
|
||||
$this->markCartAsRecovered($abCart, $order);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$lostStatuses = ['failed', 'canceled'];
|
||||
if (in_array($newStatus, $lostStatuses, true)) {
|
||||
$this->handleCartLost($abCart, $order);
|
||||
}
|
||||
}
|
||||
|
||||
private function markCartAsRecovered($abCart, $order)
|
||||
{
|
||||
$deletableStatuses = ['draft', 'opt_out', 'pending'];
|
||||
if (in_array($abCart->status, $deletableStatuses, true)) {
|
||||
$abCart->deleteCart();
|
||||
$this->deleteOtherCarts($abCart, $order);
|
||||
return;
|
||||
}
|
||||
|
||||
$recoverableStatuses = ['processing', 'lost', 'cancelled'];
|
||||
if (!in_array($abCart->status, $recoverableStatuses, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = AbCartHelper::getSettings();
|
||||
$subscriber = $abCart->subscriber;
|
||||
if ($subscriber) {
|
||||
if ($attachLists = Arr::get($settings, 'lists_on_cart_abandoned', [])) {
|
||||
$subscriber->detachLists($attachLists);
|
||||
}
|
||||
|
||||
if ($attachTags = Arr::get($settings, 'tags_on_cart_abandoned', [])) {
|
||||
$subscriber->detachTags($attachTags);
|
||||
}
|
||||
}
|
||||
|
||||
$orderTotal = $order->total_amount ?? 0;
|
||||
|
||||
$oldStatus = $abCart->status;
|
||||
$abCart->status = 'recovered';
|
||||
$abCart->order_id = $order->id;
|
||||
$abCart->total = $orderTotal / 100;
|
||||
$abCart->recovered_at = current_time('mysql');
|
||||
$abCart->save();
|
||||
|
||||
do_action('fluent_crm/ab_cart_fluent_cart_recovered', $abCart, $order, $oldStatus);
|
||||
|
||||
$this->deleteOtherCarts($abCart, $order);
|
||||
|
||||
$this->handleCartRecoveredAutomations($abCart);
|
||||
}
|
||||
|
||||
private function handleCartLost($abCart, $order)
|
||||
{
|
||||
if ($abCart->status == 'lost') {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldStatus = $abCart->status;
|
||||
$abCart->status = 'lost';
|
||||
$abCart->save();
|
||||
|
||||
do_action('fluent_crm/ab_cart_fluent_cart_lost', $abCart, $order, $oldStatus);
|
||||
|
||||
if ($abCart->automation_id) {
|
||||
$subscriber = $abCart->subscriber;
|
||||
if ($subscriber) {
|
||||
$settings = AbCartHelper::getSettings();
|
||||
if ($attachLists = Arr::get($settings, 'lists_on_cart_lost', [])) {
|
||||
$subscriber->attachLists($attachLists);
|
||||
}
|
||||
|
||||
if ($attachTags = Arr::get($settings, 'tags_on_cart_lost', [])) {
|
||||
$subscriber->attachTags($attachTags);
|
||||
}
|
||||
|
||||
FunnelSubscriber::where('subscriber_id', $subscriber->id)
|
||||
->where('source_ref_id', $abCart->id)
|
||||
->whereHas('funnel', function ($q) {
|
||||
$q->where('trigger_name', 'fc_ab_cart_simulation_fluent_cart');
|
||||
})
|
||||
->where('funnel_id', $abCart->automation_id)
|
||||
->update([
|
||||
'status' => 'cancelled',
|
||||
'notes' => __('Automatically cancelled because the cart has been lost', 'fluent-crm')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleCartRecoveredAutomations($abCart)
|
||||
{
|
||||
if (!$abCart->automation_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = $abCart->subscriber;
|
||||
if (!$contact) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cancelAutomations($contact);
|
||||
}
|
||||
|
||||
private function cancelAutomations($subscriber)
|
||||
{
|
||||
FunnelSubscriber::where('subscriber_id', $subscriber->id)
|
||||
->whereHas('funnel', function ($q) {
|
||||
$q->where('trigger_name', 'fc_ab_cart_simulation_fluent_cart');
|
||||
})
|
||||
->whereIn('status', ['active', 'pending', 'paused'])
|
||||
->update([
|
||||
'status' => 'cancelled',
|
||||
'notes' => __('Automatically cancelled because a cart has been recovered', 'fluent-crm')
|
||||
]);
|
||||
}
|
||||
|
||||
private function cancelAutomationsByCustomer($customer)
|
||||
{
|
||||
$subscriberIds = Subscriber::select(['id'])
|
||||
->where('email', $customer->email)
|
||||
->when($customer->user_id, function ($q) use ($customer) {
|
||||
return $q->orWhere('user_id', $customer->user_id);
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
if (!$subscriberIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
FunnelSubscriber::whereIn('subscriber_id', $subscriberIds)
|
||||
->whereHas('funnel', function ($q) {
|
||||
$q->where('trigger_name', 'fc_ab_cart_simulation_fluent_cart');
|
||||
})
|
||||
->whereIn('status', ['active', 'pending', 'paused'])
|
||||
->update([
|
||||
'status' => 'cancelled',
|
||||
'notes' => __('Automatically cancelled because a cart has been recovered', 'fluent-crm')
|
||||
]);
|
||||
}
|
||||
|
||||
private function deleteOtherCarts($abCart, $order)
|
||||
{
|
||||
$customerEmail = $abCart->email;
|
||||
$customerId = 0;
|
||||
|
||||
if (method_exists($order, 'getAttribute')) {
|
||||
$customerId = $order->customer_id ?? 0;
|
||||
}
|
||||
|
||||
$query = AbandonCartModel::where('provider', 'fluent_cart')
|
||||
->where('id', '!=', $abCart->id)
|
||||
->whereIn('status', ['processing', 'draft']);
|
||||
|
||||
$query->where(function ($q) use ($customerEmail, $customerId) {
|
||||
$q->where('email', $customerEmail);
|
||||
if ($customerId) {
|
||||
// Look up user_id from the FluentCart customer
|
||||
$userId = fluentCrmDb()->table('fct_customers')
|
||||
->where('id', $customerId)
|
||||
->value('user_id');
|
||||
if ($userId) {
|
||||
$q->orWhere('user_id', $userId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$otherCarts = $query->get();
|
||||
|
||||
foreach ($otherCarts as $cart) {
|
||||
$cart->deleteCart();
|
||||
}
|
||||
}
|
||||
|
||||
public function maybeRestoreCart($data)
|
||||
{
|
||||
$cartHash = sanitize_text_field(Arr::get($data, 'fc_ab_hash', ''));
|
||||
|
||||
$abandonCart = null;
|
||||
if ($cartHash) {
|
||||
$abandonCart = AbandonCartModel::where('checkout_key', $cartHash)->first();
|
||||
}
|
||||
|
||||
if (!$abandonCart || $abandonCart->status != 'processing' || $abandonCart->provider != 'fluent_cart') {
|
||||
do_action('fluent_crm/ab_cart_restore_failed', $abandonCart);
|
||||
|
||||
$checkoutUrl = home_url();
|
||||
if (class_exists('\FluentCart\Api\StoreSettings')) {
|
||||
$checkoutUrl = (new \FluentCart\Api\StoreSettings())->getCheckoutPage() ?: $checkoutUrl;
|
||||
}
|
||||
|
||||
wp_redirect($checkoutUrl);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Set tracking cookie
|
||||
$cookieDays = (int)apply_filters('fluent_crm/ab_cart_cookie_validity', 30);
|
||||
setcookie('fc_ab_fct_cart_token', $abandonCart->checkout_key, time() + (86400 * $cookieDays), COOKIEPATH, COOKIE_DOMAIN);
|
||||
|
||||
$abandonCart->click_counts = $abandonCart->click_counts + 1;
|
||||
$abandonCart->save();
|
||||
|
||||
// Restore the FluentCart cart from our snapshot
|
||||
$snapshot = $abandonCart->cart;
|
||||
$fctCartHash = $abandonCart->cart_hash;
|
||||
|
||||
if ($fctCartHash) {
|
||||
$fctCart = \FluentCart\App\Models\Cart::where('cart_hash', $fctCartHash)->first();
|
||||
|
||||
if ($fctCart) {
|
||||
// just redirect to checkout if the cart still exists in FluentCart
|
||||
$checkoutUrl = add_query_arg([
|
||||
'fct_cart_hash' => $fctCart->cart_hash,
|
||||
], (new StoreSettings())->getCheckoutPage());
|
||||
|
||||
wp_redirect($checkoutUrl);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$newCart = new Cart();
|
||||
$newCart->cart_hash = $abandonCart->cart_hash ?: Cart::generateCartHash();
|
||||
// Write the snapshot back directly
|
||||
$newCart->cart_data = Arr::get($snapshot, 'cart_data', []);
|
||||
$newCart->checkout_data = Arr::get($snapshot, 'checkout_data', []);
|
||||
$newCart->coupons = Arr::get($snapshot, 'coupons', []);
|
||||
$newCart->email = $abandonCart->email;
|
||||
$newCart->utm_data = Arr::get($snapshot, 'utm_data', []);
|
||||
$newCart->cart_group = 'instant';
|
||||
$newCart->ip_address = AddressHelper::getIpAddress();
|
||||
$newCart->user_agent = AddressHelper::getUserAgent();
|
||||
|
||||
$fullName = $abandonCart->full_name;
|
||||
if ($fullName) {
|
||||
// Try to split full name into first and last name for better compatibility
|
||||
$nameParts = explode(' ', $fullName, 2);
|
||||
$newCart->first_name = $nameParts[0];
|
||||
$newCart->last_name = isset($nameParts[1]) ? $nameParts[1] : '';
|
||||
}
|
||||
|
||||
$newCart->save();
|
||||
|
||||
\FluentCart\Api\Cookie\Cookie::setCartHash($newCart->cart_hash);
|
||||
|
||||
$abandonCart->cart_hash = $newCart->cart_hash;
|
||||
$abandonCart->save();
|
||||
|
||||
wp_redirect(add_query_arg(['fct_cart_hash' => $newCart->cart_hash], (new \FluentCart\Api\StoreSettings())->getCheckoutPage()));
|
||||
exit();
|
||||
}
|
||||
|
||||
public function pushContextCodes($codes, $context)
|
||||
{
|
||||
if ($context != 'fc_ab_cart_simulation_fluent_cart') {
|
||||
return $codes;
|
||||
}
|
||||
|
||||
$smartCodes = [
|
||||
'key' => 'ab_cart_fluent_cart',
|
||||
'title' => 'Abandoned Cart - FluentCart',
|
||||
'shortcodes' => [
|
||||
'{{ab_cart_fluent_cart.billing_email}}' => __('Cart Billing Email', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.cart_items_table}}' => __('Cart Items', 'fluent-crm'),
|
||||
'##ab_cart_fluent_cart.recovery_url##' => __('Cart Recovery URL', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.cart_total}}' => __('Cart Total', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.subtotal}}' => __('Cart Subtotal (only products)', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.shipping_total}}' => __('Cart Shipping Total', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.discount_total}}' => __('Cart Discount Total', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.coupon_codes}}' => __('Applied Coupon Codes', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.tax_total}}' => __('Cart Tax Total', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_full_name}}' => __('Billing Full Name', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_address}}' => __('Billing Address', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.shipping_address}}' => __('Shipping Address', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_city}}' => __('Billing City', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_state}}' => __('Billing State', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_postcode}}' => __('Billing Postcode', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_country}}' => __('Billing Country', 'fluent-crm'),
|
||||
'{{ab_cart_fluent_cart.billing_phone}}' => __('Billing Phone', 'fluent-crm'),
|
||||
]
|
||||
];
|
||||
|
||||
$codes[] = $smartCodes;
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
public function parseSmartCodes($code, $valueKey, $defaultValue, $subscriber)
|
||||
{
|
||||
$abCart = null;
|
||||
|
||||
if ($subscriber->funnel_subscriber_id) {
|
||||
$funnelSub = FunnelSubscriber::find($subscriber->funnel_subscriber_id);
|
||||
if ($funnelSub) {
|
||||
$abCart = AbandonCartModel::find($funnelSub->source_ref_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$abCart) {
|
||||
$abCart = AbandonCartModel::where('email', $subscriber->email)
|
||||
->where('provider', 'fluent_cart')
|
||||
->whereIn('status', ['processing', 'opt_out', 'lost'])
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
}
|
||||
|
||||
if (!$abCart && defined('FLUENTCRM_PREVIEWING_EMAIL')) {
|
||||
$abCart = AbandonCartModel::where('provider', 'fluent_cart')
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
}
|
||||
|
||||
if (!$abCart) {
|
||||
if (defined('FLUENTCRM_PREVIEWING_EMAIL')) {
|
||||
return __('Dynamic Text will be available on real email', 'fluent-crm');
|
||||
}
|
||||
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
$formatPrice = function ($amount) use ($abCart) {
|
||||
$driver = new FluentCartDriver();
|
||||
return $driver->formatPrice($amount, $abCart->currency);
|
||||
};
|
||||
|
||||
switch ($valueKey) {
|
||||
case 'billing_email':
|
||||
return $abCart->email;
|
||||
case 'cart_total':
|
||||
return $formatPrice($abCart->total);
|
||||
case 'subtotal':
|
||||
return $formatPrice($abCart->subtotal);
|
||||
case 'shipping_total':
|
||||
return $abCart->shipping ? $formatPrice($abCart->shipping) : $defaultValue;
|
||||
case 'discount_total':
|
||||
return ($abCart->discounts > 0) ? $formatPrice($abCart->discounts) : $defaultValue;
|
||||
case 'coupon_codes':
|
||||
$couponCodes = Arr::get($abCart->cart, 'coupons', []);
|
||||
return $couponCodes ? implode(', ', $couponCodes) : $defaultValue;
|
||||
case 'tax_total':
|
||||
return $abCart->tax ? $formatPrice($abCart->tax) : $defaultValue;
|
||||
case 'billing_full_name':
|
||||
return $abCart->full_name ?: $defaultValue;
|
||||
case 'billing_address':
|
||||
case 'shipping_address':
|
||||
$prefix = ($valueKey === 'shipping_address') ? 'shipping_' : 'billing_';
|
||||
$formData = Arr::get($abCart->cart, 'checkout_data.form_data', []);
|
||||
return implode(', ', array_filter([
|
||||
Arr::get($formData, $prefix . 'address_1'),
|
||||
Arr::get($formData, $prefix . 'address_2'),
|
||||
Arr::get($formData, $prefix . 'city'),
|
||||
Arr::get($formData, $prefix . 'state'),
|
||||
Arr::get($formData, $prefix . 'postcode'),
|
||||
])) ?: $defaultValue;
|
||||
case 'billing_city':
|
||||
case 'billing_state':
|
||||
case 'billing_postcode':
|
||||
case 'billing_country':
|
||||
case 'billing_phone':
|
||||
return Arr::get($abCart->cart, 'checkout_data.form_data.' . $valueKey, $defaultValue);
|
||||
case 'recovery_url':
|
||||
return add_query_arg([
|
||||
'fluentcrm' => 1,
|
||||
'route' => 'general',
|
||||
'handler' => 'fc_cart_fluent_cart',
|
||||
'fc_ab_hash' => $abCart->checkout_key
|
||||
], home_url());
|
||||
case 'cart_items_table':
|
||||
return $abCart->getCartItemsHtml();
|
||||
default:
|
||||
return apply_filters('fluent_crm/ab_cart_smart_code_default_value', $defaultValue, $valueKey, $abCart);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user