Tracking purchases from any payment provider
Count sales from WooCommerce, Paddle, PayPal, Thinkific and more with one line on your confirmation page
Written By David
Last updated 7 days ago
Cromojo can count sales from any payment provider, not only Stripe and Shopify. You add one short line of code to your order confirmation page, the page buyers see after paying. Each sale is credited to the visit it happened in, so you see which pages, sources and keywords earn money.
Use Stripe or Shopify? Connect them in Settings > Analytics instead. Their sales arrive straight from the provider, so ad blockers can't stop them (see Setting Up Conversions: Revenue or Goals).
Find your platform
Click your platform to jump to its guide.
Online stores:
Payment providers:
Course, creator and funnel platforms:
Something else: Your own checkout · Not supported yet
Before you start
The Cromojo tracking script must be on your order confirmation page. If it's on every page of your site, you're set (see Installing Your Analytics Tracking Script).
Selling on a separate platform, such as Podia, Kajabi or Thinkific? Install the tracking script on that platform too, and check its address is allowed (see "If you sell on another platform" below). Each guide shows where the platform lets you paste it.
In Settings > Analytics > Conversion metrics, choose Revenue, then Custom (any payment provider) as the revenue provider.
Pick your Currency and click Save. Cromojo only counts purchases in this currency.
The line to add
Put this on your order confirmation page, and replace the values with the real order's:
<script>
// Cromojo reads this when the page loads.
window.cromojoPurchase = {
// The order number. Each order is counted once.
order_id: '1042',
// The total paid, tax and shipping included, with a dot for decimals.
amount: 69.00,
// Your site's currency, as a three-letter code.
currency: 'USD',
};
</script>order_id is the order number. Each order counts once, so a buyer reloading the page doesn't count twice. Cromojo never stores it readable: it's scrambled before it's saved.
amount is the total the buyer paid, including tax and shipping, after discounts. Write it in normal units with a dot:
69.00, not6900and not69,00.currency is the three-letter code, such as USD or EUR. It must be your site's revenue currency.
It doesn't matter whether this line runs before or after the tracking script, so script order and speed plugins that delay scripts are fine.
Pages that don't reload
If your checkout finishes without loading a new page (a pop-up checkout, or a single-page app), call this once the payment has succeeded:
// Only runs if the Cromojo tracking script has loaded.
window.CRMJ && CRMJ.trackPurchase({
// The order number. Each order is counted once.
order_id: '1042',
// The total paid, tax and shipping included.
amount: 69.00,
// Your site's currency, as a three-letter code.
currency: 'USD',
});Check that it works
Place a test order on your site.
Go to Settings > Analytics and click Verify Installation under the purchase line.
You should see Purchase received. If a purchase arrived but wasn't counted, the card tells you why (see Troubleshooting below).
The sale reaches your dashboard within about 7 minutes.
If you sell on another platform
Many shops sell through a separate platform, such as a Podia store or a Kajabi course site, while the rest of the site lives elsewhere. Cromojo only sees pages where its tracking script is installed, so the platform needs it too:
Install the tracking script on the platform. Paste the same script you use on your main site into the platform's site-wide code setting. Each guide below says where. Without it, the purchase line on the thank-you page has nothing to send it.
Check the platform's address is allowed. Cromojo only accepts visits from your site's domain and the domains you list. A subdomain of your site, such as
shop.yoursite.com, is included already. The platform's own address, such asyourname.podia.comoryourschool.thinkific.com, must be added in Settings > Analytics > Domains (see Tracking more than one domain). Extra domains are included from the Starter plan.
A buyer who moves from your main site to the platform stays in the same visit, so the sale is still credited to the source that brought them.
Setup guides by platform
Find your platform below. Each guide shows where to paste the code and how to fill in the real order values.
Amounts in cents. Some platforms give the total in cents (6900 for $69.00). The guides divide those by 100 for you. If you sell in a currency without decimals, such as the Japanese yen, remove the / 100.
Online stores
WooCommerce
Add this with a code snippets plugin (such as Code Snippets or WPCode), or in your child theme's functions.php. It runs only on WooCommerce's order received page:
// Runs at the bottom of every page, but does nothing outside the order received page.
add_action( 'wp_footer', function () {
// Makes sure the line is printed only once, even if the theme calls this twice.
static $done = false;
if ( $done || ! function_exists( 'is_order_received_page' ) || ! is_order_received_page() ) {
return;
}
// Load the order from the page address.
$order_id = absint( get_query_var( 'order-received' ) );
$order = $order_id ? wc_get_order( $order_id ) : false;
// The order key proves this buyer placed the order. Failed payments are skipped.
$key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '';
if ( ! $order || ! $order->key_is_valid( $key ) || $order->has_status( 'failed' ) ) {
return;
}
$done = true;
// The order number, the total paid (tax, shipping and coupons included) and the currency.
$purchase = array(
'order_id' => (string) $order->get_order_number(),
'amount' => wc_format_decimal( $order->get_total(), wc_get_price_decimals() ),
'currency' => $order->get_currency(),
);
// Print the purchase line for Cromojo.
echo '<script>window.cromojoPurchase = ' . wp_json_encode( $purchase ) . ';</script>';
} );The total includes tax, shipping, fees and coupons. Failed payments aren't counted, and the order key check stops anyone from loading someone else's order.
Easy Digital Downloads
Add this with a code snippets plugin or in functions.php. It runs on the purchase confirmation page:
// Runs under the receipt table that Easy Digital Downloads shows.
add_action( 'edd_order_receipt_after_table', function ( $order ) {
// Only on the purchase confirmation page, and only once.
static $done = false;
if ( $done || ! function_exists( 'edd_is_success_page' ) || ! edd_is_success_page() ) {
return;
}
// Skip orders that didn't go through.
if ( ! $order instanceof \EDD\Orders\Order || in_array( $order->status, array( 'failed', 'abandoned', 'revoked' ), true ) ) {
return;
}
$done = true;
// The order number, the total and the currency.
$purchase = array(
'order_id' => (string) $order->get_number(),
'amount' => (float) $order->total,
'currency' => $order->currency,
);
// Print the purchase line for Cromojo.
echo '<script>window.cromojoPurchase = ' . wp_json_encode( $purchase ) . ';</script>';
}, 10, 1 );This needs EDD 3 or later. Check the total on one test order that includes tax.
BigCommerce
Go to Settings > Data solutions > Affiliate Conversion Tracking, and paste this in Conversion Tracking Code. BigCommerce runs this box only on the order confirmation page:
<script>
window.cromojoPurchase = {
// BigCommerce fills in the order number here.
order_id: '%%ORDER_ID%%',
// BigCommerce gives the total in cents, so divide by 100.
amount: Number('%%ORDER_AMOUNT_IN_CENTS%%') / 100,
// Type your store currency. BigCommerce has no variable for it here.
currency: 'USD',
};
</script>BigCommerce has no currency variable here, so type your store currency. Then make sure your Cromojo tracking script is in Storefront > Script Manager with the location set to All pages. "Store pages" alone skips the confirmation page.
Squarespace
Go to Website > Website Tools > Code Injection.
Paste this in both Order Status Page (physical, digital and service products) and Order Confirmation Page (memberships, subscriptions and gift cards):
{.if firstPageView}
<script>
window.cromojoPurchase = {
// Squarespace fills in the order number here.
order_id: '{orderId}',
// Squarespace gives the total in cents, so divide by 100.
amount: Number('{orderGrandTotalCents}') / 100,
// Type your store currency. Squarespace has no variable for it.
currency: 'USD',
};
</script>
{.end}Code injection needs a Core, Plus or Advanced plan. After a test order, view the page's source and check your Cromojo tracking script is there. If it isn't, paste it into the same field above this code.
Wix
Go to Settings > Custom Code, click Add Custom Code, choose All pages and Body - end, and paste:
<script>
(function () {
// Listens for the Purchase event Wix sends from its Thank You page.
function register() {
window.wixDevelopersAnalytics.register('cromojoPurchase', function (eventName, data) {
// Ignore every other Wix event.
if (eventName !== 'Purchase' || !data || !data.orderId) return;
// The order number, the total paid and the currency, as Wix sends them.
var purchase = { order_id: String(data.orderId), amount: String(data.revenue), currency: data.currency };
// Send it now if Cromojo is loaded, or leave it for Cromojo to pick up.
if (window.CRMJ && window.CRMJ.trackPurchase) {
window.CRMJ.trackPurchase(purchase);
} else {
window.cromojoPurchase = purchase;
}
});
}
// Wix may not be ready yet, so wait for it if needed.
if (window.wixDevelopersAnalytics) {
register();
} else {
window.addEventListener('wixDevelopersAnalyticsReady', register);
}
})();
</script>Wix sends a Purchase event when its Thank You page loads, and this code listens for it. No Velo code is needed. Custom code needs a paid plan and a connected domain. Wix doesn't say whether its revenue includes shipping and tax, so check one test order.
Webflow (best effort)
Webflow doesn't give custom code the order's details, so this reads them from the Order Confirmation page itself.
In the Designer, open Pages > Ecommerce pages > Order Confirmation.
Select the element that shows the order total, and give it the ID
cromojo-totalin its element settings.In the page's settings, under Custom code > Before </body> tag, paste (replace USD with your currency):
<script>
(function () {
// Webflow puts the order number in the page address.
var orderId = new URLSearchParams(location.search).get('orderId');
if (!orderId) return;
// Turns a shown price like "$1,069.00" or "1.069,00 EUR" into "1069.00".
function toAmount(text) {
var t = text.replace(/[^0-9.,]/g, '');
var lastComma = t.lastIndexOf(','), lastDot = t.lastIndexOf('.');
if (lastComma > lastDot && t.length - lastComma <= 3) {
t = t.replace(/\./g, '').replace(',', '.');
} else {
t = t.replace(/,/g, '');
}
return t;
}
// The total appears a moment after the page loads, so check a few times.
var tries = 0;
(function read() {
// The element you gave the ID cromojo-total.
var el = document.getElementById('cromojo-total');
var text = el ? el.textContent.trim() : '';
if (!/\d/.test(text)) {
if (++tries < 40) setTimeout(read, 250);
return;
}
// Replace USD with your store currency.
window.cromojoPurchase = { order_id: orderId, amount: toAmount(text), currency: 'USD' };
})();
})();
</script>Custom code needs a paid Webflow plan. Place a test order and check that the page address contains orderId, and that Verify Installation shows the purchase.
Ecwid
On an Ecwid Instant Site, go to Website > Advanced website settings > Custom JavaScript code. If your store is embedded in your own site, add it to the page that shows the store instead:
<script>
// Waits until Ecwid has loaded on the page.
(function waitForEcwid() {
if (!window.Ecwid || !Ecwid.OnAPILoaded) { return setTimeout(waitForEcwid, 300); }
Ecwid.OnAPILoaded.add(function () {
// Runs once, when the buyer places an order.
Ecwid.OnOrderPlaced.add(function (order) {
// The order number customers see, and the total paid.
var purchase = { order_id: String(order.vendorNumber || order.orderNumber), amount: String(order.total) };
// Send it now if Cromojo is loaded, or leave it for Cromojo to pick up.
if (window.CRMJ && CRMJ.trackPurchase) {
CRMJ.trackPurchase(purchase);
} else {
window.cromojoPurchase = purchase;
}
});
});
})();
</script>Ecwid's total includes discounts, taxes, fees and shipping. Ecwid doesn't send the currency, so Cromojo uses your site's revenue currency. On an Instant Site, also add your Cromojo tracking script in the same field, and add its domain in Cromojo if it isn't your main one.
Magento 2 and Adobe Commerce
Magento needs a small module (ask your developer). Create these files in app/code/Cromojo/Purchase/:
<?php
// registration.php: tells Magento this module exists.
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE, 'Cromojo_Purchase', __DIR__
);<?xml version="1.0"?>
<!-- etc/module.xml: names the module, and loads it after Magento's checkout. -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Cromojo_Purchase"><sequence><module name="Magento_Checkout"/></sequence></module>
</config><?xml version="1.0"?>
<!-- view/frontend/layout/checkout_onepage_success.xml: applies only to the order success page. -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<!-- Adds our template at the end of the page. cacheable="false" keeps each buyer's own order. -->
<referenceContainer name="before.body.end">
<block name="cromojo.purchase" template="Cromojo_Purchase::purchase.phtml" cacheable="false">
<arguments>
<!-- The class that reads the order (the next file). -->
<argument name="view_model" xsi:type="object">Cromojo\Purchase\ViewModel\Purchase</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page><?php
// ViewModel/Purchase.php: reads the order the buyer just placed.
namespace Cromojo\Purchase\ViewModel;
use Magento\Checkout\Model\Session;
use Magento\Framework\View\Element\Block\ArgumentInterface;
class Purchase implements ArgumentInterface
{
public function __construct(private Session $checkoutSession) {}
public function getJson(): ?string
{
// The order from this buyer's checkout. Nothing is printed if there isn't one.
$order = $this->checkoutSession->getLastRealOrder();
if (!$order || !$order->getId()) {
return null;
}
// The order number, the grand total and the currency the buyer paid in.
return json_encode([
'order_id' => (string) $order->getIncrementId(),
'amount' => round((float) $order->getGrandTotal(), 2),
'currency' => $order->getOrderCurrencyCode(),
], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
}<?php
// view/frontend/templates/purchase.phtml: prints the purchase line.
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
$json = $block->getData('view_model')->getJson();
if ($json) {
// secureRenderer adds the security code Magento's checkout requires on scripts.
echo $secureRenderer->renderTag('script', [], 'window.cromojoPurchase = ' . $json . ';', false);
}Then run bin/magento module:enable Cromojo_Purchase, bin/magento setup:upgrade and bin/magento cache:flush. If your store uses a strict Content Security Policy, allow the Cromojo tracking script's address there too.
PrestaShop
PrestaShop needs a tiny module. Save this as cromojopurchase/cromojopurchase.php, zip the cromojopurchase folder, and upload it in Modules > Module Manager > Upload a module:
<?php
// Stops the file from being opened directly.
if (!defined('_PS_VERSION_')) { exit; }
class CromojoPurchase extends Module
{
// The module's name and description, as shown in the Module Manager.
public function __construct()
{
$this->name = 'cromojopurchase';
$this->tab = 'analytics_stats';
$this->version = '1.0.0';
$this->author = 'Cromojo';
$this->need_instance = 0;
parent::__construct();
$this->displayName = 'Cromojo purchase tracking';
$this->description = 'Reports each order to Cromojo from the order confirmation page.';
$this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_];
}
// On install, ask PrestaShop to run this module on the order confirmation page.
public function install()
{
return parent::install() && $this->registerHook('displayOrderConfirmation');
}
public function hookDisplayOrderConfirmation($params)
{
// The order PrestaShop just confirmed.
$order = isset($params['order']) ? $params['order'] : null;
if (!Validate::isLoadedObject($order)) {
return '';
}
// One checkout can be split into several orders sharing one reference, so add them all up.
$total = 0.0;
foreach (Order::getByReference($order->reference) as $sibling) {
$total += (float) $sibling->total_paid_tax_incl;
}
// The order reference, the total paid with tax and shipping, and the currency.
$currency = new Currency((int) $order->id_currency);
$purchase = [
'order_id' => (string) $order->reference,
'amount' => round($total, 2),
'currency' => $currency->iso_code,
];
// Print the purchase line for Cromojo.
return '<script>window.cromojoPurchase = ' . json_encode($purchase, JSON_HEX_TAG | JSON_HEX_AMP) . ';</script>';
}
}This works on PrestaShop 1.7, 8 and 9. The total includes tax and shipping.
OpenCart 3
Save this as cromojo.ocmod.xml, upload it in Extensions > Installer, then click Refresh in Extensions > Modifications:
<?xml version="1.0" encoding="utf-8"?>
<!-- Adds Cromojo purchase tracking to OpenCart 3's checkout success page. -->
<modification>
<name>Cromojo purchase tracking</name>
<code>cromojo_purchase</code>
<version>1.0</version>
<author>Cromojo</author>
<!-- Step 1: read the order before OpenCart forgets it. -->
<file path="catalog/controller/checkout/success.php">
<operation>
<search><![CDATA[unset($this->session->data['order_id']);]]></search>
<add position="before"><![CDATA[
// Load the order the buyer just placed.
$this->load->model('checkout/order');
$cromojo_order = $this->model_checkout_order->getOrder($this->session->data['order_id']);
if ($cromojo_order && $cromojo_order['order_status_id']) {
// The order number, the total converted to the currency the buyer paid in, and that currency.
$data['cromojo_purchase'] = '<script>window.cromojoPurchase = ' . json_encode(array(
'order_id' => (string)$cromojo_order['order_id'],
'amount' => $this->currency->format($cromojo_order['total'], $cromojo_order['currency_code'], $cromojo_order['currency_value'], false),
'currency' => $cromojo_order['currency_code'],
), JSON_HEX_TAG | JSON_HEX_AMP) . ';</script>';
}
]]></add>
</operation>
</file>
<!-- Step 2: print the purchase line just before the page footer. -->
<file path="catalog/view/theme/*/template/common/success.twig">
<operation>
<search><![CDATA[{{ footer }}]]></search>
<add position="before"><![CDATA[{{ cromojo_purchase }}]]></add>
</operation>
</file>
</modification>This converts the total into the currency the buyer paid. For OpenCart 4, ask your developer to add the same code to the checkout success page with an extension event.
Shopware 6
Shopware needs a template change in your theme or a plugin (ask your developer). Create src/Resources/views/storefront/page/checkout/finish/index.html.twig in your theme:
{% sw_extends '@Storefront/storefront/page/checkout/finish/index.html.twig' %}
{# Adds to the end of the order finish page, keeping what's already there. #}
{% block base_body_script %}
{{ parent() }}
{% if page.order %}
<script>
window.cromojoPurchase = {
// The order number.
order_id: {{ page.order.orderNumber|json_encode|raw }},
// The order total, with shipping and tax.
amount: {{ page.order.amountTotal|json_encode|raw }},
// The currency the buyer paid in.
currency: {{ page.order.currency.isoCode|json_encode|raw }}
};
</script>
{% endif %}
{% endblock %}Then run bin/console cache:clear. If you sell to business customers at net prices, check one of their test orders shows the total with tax.
Payment providers
Stripe (without the Stripe connection)
If you can, connect Stripe in Settings > Analytics instead: it's more reliable. If you can't, send buyers back to your site after paying.
When you create the Checkout Session, set the success page to
https://yoursite.com/thanks?session_id={CHECKOUT_SESSION_ID}. Keep{CHECKOUT_SESSION_ID}exactly as written: Stripe fills it in. For a Payment Link, set the same address under After the payment > Don't show confirmation page.On your thanks page, look the session up on your server and print the line. In PHP:
<?php
// Ask Stripe for the checkout session named in the page address.
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));
$session = $stripe->checkout->sessions->retrieve($_GET['session_id']);
// Only print the line once Stripe confirms the payment.
if ($session->payment_status === 'paid'): ?>
<script>
window.cromojoPurchase = <?= json_encode([
// Stripe's checkout session ID works as the order number.
'order_id' => $session->id,
// Stripe gives the total in cents, so divide by 100.
'amount' => number_format($session->amount_total / 100, 2, '.', ''),
// Stripe writes currencies in lowercase, so make it uppercase.
'currency' => strtoupper($session->currency),
]) ?>;
</script>
<?php endif; ?>Payments that settle later, like bank debits, aren't marked paid yet when the buyer arrives, so they aren't counted this way.
Stripe Payment Element (your own checkout form)
If you confirm the payment on your page with redirect: 'if_required':
// Confirms the payment without leaving the page when possible.
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
redirect: 'if_required',
confirmParams: { return_url: 'https://yoursite.com/thanks' },
});
// Only record the sale once Stripe says the payment succeeded.
if (!error && paymentIntent && paymentIntent.status === 'succeeded') {
window.CRMJ && CRMJ.trackPurchase({
// Stripe's payment ID works as the order number.
order_id: paymentIntent.id,
// Stripe gives the amount in cents, so divide by 100.
amount: paymentIntent.amount / 100,
// Stripe writes currencies in lowercase, so make it uppercase.
currency: paymentIntent.currency.toUpperCase(),
});
}Some payment methods always redirect to your return_url. To catch those too, add this to the return page:
// Stripe adds this to your return page's address.
const secret = new URLSearchParams(location.search).get('payment_intent_client_secret');
if (secret) {
// Ask Stripe for the payment, instead of trusting the address.
stripe.retrievePaymentIntent(secret).then(function (result) {
var pi = result.paymentIntent;
// Only record the sale once the payment succeeded.
if (pi && pi.status === 'succeeded') {
window.CRMJ && CRMJ.trackPurchase({
order_id: pi.id,
// Cents to normal units.
amount: pi.amount / 100,
currency: pi.currency.toUpperCase(),
});
}
});
}The amount is what you charged, so include tax and shipping in the PaymentIntent.
Paddle Billing
Add eventCallback where you set up Paddle.js. It runs when the checkout completes, for both inline and overlay checkouts:
Paddle.Initialize({
// Your existing Paddle client token.
token: 'live_xxx',
// Paddle calls this for every checkout event.
eventCallback: function (event) {
// Only act when the checkout is completed.
if (event.name === 'checkout.completed') {
window.CRMJ && CRMJ.trackPurchase({
// Paddle's transaction ID works as the order number.
order_id: event.data.transaction_id,
// The total with tax, already in normal units.
amount: event.data.totals.total,
currency: event.data.currency_code,
});
}
},
});Paddle already gives this total in normal units, tax included, so there's nothing to divide.
Paddle Classic
Paddle Classic sends a Checkout.Complete event. Paddle doesn't document its fields, so log it once with a test purchase and check the names before relying on it:
Paddle.Setup({
// Your existing Paddle vendor ID.
vendor: 12345,
// Paddle calls this for every checkout event.
eventCallback: function (data) {
// Only act when the checkout is complete.
if (data.event === 'Checkout.Complete') {
// Shows the event in the browser console, so you can check the names below. Remove it once checked.
console.log(data);
var checkout = data.eventData.checkout;
window.CRMJ && CRMJ.trackPurchase({
order_id: checkout.id,
amount: checkout.prices.customer.total,
currency: checkout.prices.customer.currency,
});
}
},
});Lemon Squeezy (overlay checkout)
Add an event handler where you set up Lemon.js:
LemonSqueezy.Setup({
// Lemon.js calls this for every checkout event.
eventHandler: function (event) {
// Only act on a successful checkout.
if (event.event !== 'Checkout.Success') return;
// The order's details. This works with both shapes Lemon Squeezy uses.
var order = event.data || {};
var a = order.attributes || order;
window.CRMJ && CRMJ.trackPurchase({
// The order's unique ID.
order_id: String(a.identifier || order.id),
// Lemon Squeezy gives the total in cents, so divide by 100.
amount: a.total / 100,
currency: a.currency,
});
},
});Lemon Squeezy gives the total in cents, tax included.
Lemon Squeezy (hosted checkout)
The hosted checkout lives on Lemon Squeezy's site, so the buyer has to come back to yours.
In your product's settings, set the confirmation Button link to
https://yoursite.com/thanks?order_id=[order_id]. Lemon Squeezy fills in[order_id].On your thanks page, look the order up on your server and print the line:
<?php
// The order number Lemon Squeezy put in the page address (digits only).
$id = preg_replace('/\D/', '', $_GET['order_id'] ?? '');
// Ask Lemon Squeezy for that order, with your API key.
$ctx = stream_context_create(['http' => ['header' =>
"Authorization: Bearer " . getenv('LEMONSQUEEZY_API_KEY') . "\r\n" .
"Accept: application/vnd.api+json\r\n"]]);
$res = $id ? json_decode(@file_get_contents("https://api.lemonsqueezy.com/v1/orders/$id", false, $ctx), true) : null;
$a = $res['data']['attributes'] ?? null;
// Only print the line if the order is paid.
if ($a && $a['status'] === 'paid'): ?>
<script>
window.cromojoPurchase = <?= json_encode([
'order_id' => $a['identifier'],
// Lemon Squeezy gives the total in cents, so divide by 100.
'amount' => number_format($a['total'] / 100, 2, '.', ''),
'currency' => $a['currency'],
]) ?>;
</script>
<?php endif; ?>Only buyers who click the button come back, so some sales may be missed.
PayPal (JavaScript buttons)
In onApprove, record the sale once your server has captured the order. This follows PayPal's standard setup, where your server returns PayPal's capture response:
paypal.Buttons({
createOrder: function () { /* your existing code */ },
// Runs when the buyer approves the payment in PayPal.
onApprove: function (data) {
// Your server captures the payment and returns PayPal's answer.
return fetch('/api/orders/' + data.orderID + '/capture', { method: 'POST' })
.then(function (response) { return response.json(); })
.then(function (orderData) {
// The captured payment inside PayPal's answer.
var unit = orderData.purchase_units && orderData.purchase_units[0];
var capture = unit && unit.payments && unit.payments.captures && unit.payments.captures[0];
// Only record the sale once the money is captured.
if (capture && capture.status === 'COMPLETED') {
window.CRMJ && CRMJ.trackPurchase({
order_id: orderData.id,
// PayPal gives the amount in normal units.
amount: capture.amount.value,
currency: capture.amount.currency_code,
});
}
// ...your existing success message
});
},
}).render('#paypal-button-container');PayPal gives the amount in normal units. A capture with status PENDING isn't paid yet, so it isn't counted.
Polar
Set your checkout's success URL to
https://yoursite.com/thanks?checkout_id={CHECKOUT_ID}. Polar fills in{CHECKOUT_ID}.On your thanks page, look the checkout up on your server. In Node:
// Your thanks page, on a Node (Express) server.
app.get('/thanks', async (req, res) => {
// Ask Polar for the checkout named in the page address.
const response = await fetch(
'https://api.polar.sh/v1/checkouts/' + encodeURIComponent(req.query.checkout_id),
{ headers: { Authorization: 'Bearer ' + process.env.POLAR_ACCESS_TOKEN } },
);
const checkout = response.ok ? await response.json() : null;
// Only build the purchase once the checkout succeeded. Polar gives cents, so divide by 100.
const purchase = checkout && checkout.status === 'succeeded'
? { order_id: checkout.id, amount: (checkout.total_amount / 100).toFixed(2), currency: checkout.currency.toUpperCase() }
: null;
// Then print it in your page's HTML:
// <script>window.cromojoPurchase = ${JSON.stringify(purchase)};</script>
});Polar gives the total in cents, after discounts and taxes. The embedded checkout sends buyers to the same success URL, so this covers it too.
Chargebee
Chargebee.js gives you a hosted page ID when the checkout succeeds. Look it up on your server to get the invoice.
// Server (Node): GET /chargebee-purchase?id=...
// Looks up a finished Chargebee checkout and returns the purchase.
app.get('/chargebee-purchase', async (req, res) => {
// Your Chargebee API key, sent the way Chargebee expects.
const auth = 'Basic ' + Buffer.from(process.env.CHARGEBEE_API_KEY + ':').toString('base64');
// Ask Chargebee for the hosted page the browser sent.
const response = await fetch(
`https://${process.env.CHARGEBEE_SITE}.chargebee.com/api/v2/hosted_pages/${encodeURIComponent(req.query.id)}`,
{ headers: { Authorization: auth } },
);
const page = response.ok ? (await response.json()).hosted_page : null;
// The invoice only exists once the checkout succeeded.
const invoice = page && page.state === 'succeeded' && page.content && page.content.invoice;
// Chargebee gives the total in cents, so divide by 100.
res.json(invoice ? { order_id: invoice.id, amount: (invoice.total / 100).toFixed(2), currency: invoice.currency_code } : null);
});// Browser
cbInstance.openCheckout({
hostedPage: function () { /* your existing code */ },
// Chargebee calls this when the checkout succeeds.
success: function (hostedPageId) {
// Ask your server for the purchase details.
fetch('/chargebee-purchase?id=' + encodeURIComponent(hostedPageId))
.then(function (response) { return response.json(); })
.then(function (purchase) {
// Send it to Cromojo, unless there was nothing to charge.
if (purchase) {
window.CRMJ && CRMJ.trackPurchase(purchase);
}
});
},
});A free trial has no invoice, so it isn't counted.
Mollie
Mollie sends the buyer to the redirectUrl you set when you create the payment. Put your own order number in it, such as https://yoursite.com/thanks?order=1042, and check the payment on your server:
<?php
// Your own order, from the order number in the page address.
$order = findOrder($_GET['order']);
// Ask Mollie for the payment you saved with that order.
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setApiKey(getenv('MOLLIE_API_KEY'));
$payment = $mollie->payments->get($order->mollie_payment_id);
// Only print the line once Mollie says it's paid.
if ($payment->isPaid()): ?>
<script>
window.cromojoPurchase = <?= json_encode([
'order_id' => $order->number,
// Mollie gives the amount in normal units, like "69.00".
'amount' => $payment->amount->value,
'currency' => $payment->amount->currency,
]) ?>;
</script>
<?php endif; ?>Bank transfers are often still pending when the buyer returns, so they aren't counted.
Braintree, Recurly, FastSpring and other server-side payments
With these, your own server completes the payment and decides which page the buyer sees next. Print the line on that confirmation page from your order record, as shown in "Your own checkout" below.
Course, creator and funnel platforms
Thinkific
Go to Settings > Code & analytics > Order Tracking Code. Thinkific runs this code only on the thank-you page after a purchase.
Paste your Cromojo tracking script there too, then add:
<script>
window.cromojoPurchase = {
// Thinkific fills in the order number here.
order_id: '{{ order_number }}',
// Thinkific gives the total in cents, so divide by 100.
amount: Number('{{ total_price_in_cents }}') / 100,
// Thinkific fills in your site currency here.
currency: '{{ currency }}',
};
</script>Free courses don't run this code. If the thank-you page is on your thinkific.com address, add it to your domains (see "If you sell on another platform").
Podia
Go to Settings > Analytics. Paste your tracking script in Website tracking code.
Paste this in Conversion tracking code, which Podia runs only after a purchase:
<script>
(function () {
// Podia provides the purchase details on this page.
var c = window.Podia && window.Podia.Conversion;
// Skip free products: there's no money to count.
if (!c || !c.revenue_cents) return;
window.cromojoPurchase = {
// The order number (not the product number).
order_id: String(c.object.order_id),
// Podia gives the amount in cents, so divide by 100.
amount: c.revenue_cents / 100,
currency: c.currency,
};
})();
</script>Custom code needs Podia's Mover or Shaker plan. If your Podia site isn't on a subdomain of your own site, add its address in Cromojo (see "If you sell on another platform").
ThriveCart
Open your product, then Checkout > Tracking, and turn on tracking code for checkout pages.
Paste your Cromojo tracking script in the field for all pages.
Paste this in the field that runs if the main product is purchased:
<script>
// ThriveCart provides the order details on its success page.
if (window._thrive_order && _thrive_order.order) {
window.cromojoPurchase = {
order_id: String(_thrive_order.order.id),
// ThriveCart gives the total in cents, so divide by 100.
amount: _thrive_order.order.total / 100,
currency: _thrive_order.order.currency,
};
}
</script>ThriveCart gives the total in cents. If its success page is on ThriveCart's address rather than your own domain, add that address in Cromojo too (see "If you sell on another platform").
SamCart
Open your product, then Advanced > Fire Pixels/Scripts After An Order is Completed.
Paste your Cromojo tracking script there, then add:
<script>
window.cromojoPurchase = {
// SamCart fills in the order number here.
order_id: '##order_id##',
// SamCart fills in the total. This keeps only digits and the dot.
amount: String('##total_amount##').replace(/[^0-9.]/g, ''),
// SamCart fills in the currency here.
currency: '##currency##',
};
</script>The replace removes any currency symbol or thousands separator SamCart adds. If the order page is on SamCart's address rather than your own domain, add that address in Cromojo too. Place one test order and check the result with Verify Installation.
Outseta
Outseta can add the plan's details to the address it sends new members to.
In Auth > Sign up settings, set the Post sign up URL to:
https://yoursite.com/welcome?sub={{ params.Subscription.Uid }}&term={{ params.Subscription.BillingRenewalTerm }}&monthly={{ params.Subscription.Plan.MonthlyRate }}&annual={{ params.Subscription.Plan.AnnualRate }}On that page, add (replace USD with your currency):
<script>
(function () {
// Outseta added the plan's details to this page's address.
var p = new URLSearchParams(location.search);
// Use the annual price for annual plans, otherwise the monthly price.
var annual = (p.get('term') || '').toLowerCase().indexOf('annual') > -1;
var amount = Number(annual ? p.get('annual') : p.get('monthly'));
// Stop if something is missing.
if (!p.get('sub') || !amount) return;
// The subscription ID works as the order number. Replace USD with your currency.
window.cromojoPurchase = { order_id: p.get('sub'), amount: amount, currency: 'USD' };
})();
</script>These are the plan's list prices: discounts aren't included, and a free trial counts as a sale. Check the address once after a test sign-up, to confirm the term reads as you expect.
Kajabi, ClickFunnels, Kartra, Systeme.io, Payhip, Memberstack and Ghost
These platforms let you add code to a thank-you page, but they don't give it the order's number or total. Use the fixed-price thank-you page below, with one thank-you page per product.
On each one, first install your Cromojo tracking script, then add the purchase code to the thank-you page:
Kajabi: paste the tracking script in Settings > Site details > Page scripts > Header page scripts. Then set each offer's post-purchase page to a landing page (Details > Post-purchase > Existing Landing Page), and add the code in that page's Settings > Custom Code.
ClickFunnels 2.0: paste the tracking script in your funnel's settings, under Tracking Code > Header, so it runs on every step. Then add the purchase code in the thank-you page's Settings > Tracking Code.
Kartra: in the thank-you page's Settings > Tracking Codes, paste the tracking script and then the purchase code in the BODY field (the HEAD field only takes plain JavaScript). Add the tracking script to your other Kartra pages' BODY field the same way.
Systeme.io: paste the tracking script in Profile > Settings > Sales funnels > Tracking code, which covers all your funnels. Then add the purchase code in the thank-you page's Settings > Tracking.
Payhip: under Settings > Advanced Settings > Checkout Settings, redirect buyers to a page on your own site after checkout, and add the code there. That page already has your tracking script if your site does.
Memberstack: set each plan's On Signup redirect to its own thank-you page, and add the code there. Memberstack pages are on your own site, so the tracking script is usually already there.
Ghost: if the tracking script isn't on your site yet, paste it in Settings > Code Injection > Site Header. Add the purchase code in Settings > Code Injection > Site Footer, inside
if (new URLSearchParams(location.search).get('stripe') === 'success') { ... }. After a test purchase, check that the page address ends in?stripe=success: Ghost doesn't document it. This only works if you sell one tier at one price.
The fixed-price thank-you page
Replace 49.00 and USD with the product's price and your currency:
<script>
(function () {
// These platforms give no order number, so make one up and remember it in this
// browser tab. A reload then reuses it and isn't counted twice.
var key = 'cromojo-order:' + location.pathname;
var id = null;
try { id = sessionStorage.getItem(key); } catch (e) {}
if (!id) {
id = 'order-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
try { sessionStorage.setItem(key, id); } catch (e) {}
}
// Replace 49.00 with this product's price, and USD with your currency.
window.cromojoPurchase = { order_id: id, amount: 49.00, currency: 'USD' };
})();
</script>This is an estimate. It counts the list price, so coupons, tax, order bumps and upsells aren't included, and anyone who opens the page directly counts as a sale.
Your own checkout
If you built your checkout yourself, print the line on the order confirmation page from your server, and only once the order is paid. Take the values from your order record, never from the page address.
Laravel (Blade):
{{-- Print this only on the confirmation page of a paid order. --}}
<script>
window.cromojoPurchase = @json([
// Your order number.
'order_id' => (string) $order->id,
// Convert cents to normal units.
'amount' => round($order->total_cents / 100, 2),
'currency' => $order->currency,
]);
</script>Plain PHP:
<script>
// Print this only on the confirmation page of a paid order.
window.cromojoPurchase = <?= json_encode([
// Your order number.
'order_id' => (string) $order['id'],
// The total, written with two decimals and a dot.
'amount' => number_format($order['total'], 2, '.', ''),
'currency' => $order['currency'],
]) ?>;
</script>Django:
{# Prints the purchase safely, then hands it to Cromojo. #}
{{ purchase|json_script:"cromojo-purchase" }}
<script>
window.cromojoPurchase = JSON.parse(document.getElementById('cromojo-purchase').textContent);
</script>Your view passes purchase = {"order_id": str(order.id), "amount": order.total_cents / 100, "currency": order.currency}.
Ruby on Rails (ERB):
<%# Print this only on the confirmation page of a paid order. Cents become normal units. %>
<script>
window.cromojoPurchase = <%= raw json_escape({ order_id: @order.id.to_s, amount: (@order.total_cents / 100.0).round(2), currency: @order.currency }.to_json) %>;
</script>Next.js and React:
'use client';
import { useEffect } from 'react';
// Render this on your order confirmation page, with the paid order's values.
export function CromojoPurchase({ orderId, amount, currency }) {
// Runs in the browser once the page shows, and hands the purchase to Cromojo.
useEffect(() => {
window.cromojoPurchase = { order_id: orderId, amount, currency };
}, [orderId, amount, currency]);
// Shows nothing on the page.
return null;
}Render it on your confirmation page with the paid order's values. Running twice in development is fine: the order still counts once.
Not supported yet
Teachable: custom code doesn't run on Teachable's thank-you page.
Gumroad: buyers stay on Gumroad after paying, so no code on your site can see the sale.
Sellfy: there's no way to add the order's details to the confirmation page.
Using one of these? Email support@cromojo.com and tell us. It helps us decide what to support next.
Troubleshooting
Verify Installation says "Not detected".
Place a test order first, then click Verify again.
If you just added the line, visitors' browsers can keep the previous tracking script for up to 4 hours. Try a private window.
Check the line is on the page buyers really see after paying.
Open your browser's console on that page and type
window.CRMJ. If it says undefined, the tracking script isn't on the page.
A purchase arrived but wasn't counted. The card shows the reason:
The dashboard hasn't moved yet. Give it about 7 minutes after the purchase.
My numbers are lower than my payment provider's. That's expected. Buyers who leave before the confirmation page, or who block trackers, aren't counted. Refunds and subscription renewals aren't included either.
FAQ
Is the order number stored? No. It's scrambled before it's saved, and only used to make sure each order counts once.
Can someone send a fake purchase? Yes, anyone can, just like with other analytics tools. Cromojo limits how many purchases a visit and a site can send. Use these numbers to see what earns money, and keep your payment provider as the record for accounting.
Do I still need my provider's webhooks? Yes. Keep using them to fulfil orders. This line is for analytics only.
What about refunds and renewals? They aren't included. Only the purchase on the confirmation page is counted.
Related articles
Was this helpful?
More in Web Analytics
Web Analytics OverviewUnderstanding Your Analytics DashboardSetting Up Conversions: Revenue or GoalsCreating & Tracking Custom EventsStill need help? Ask the team