Out of the box, our Opayo plugin is built to talk to Opayo. Some merchants required a Sage 50-friendly basket format, but didn’t have a developer to work on a custom function using our basket filter code. We don’t have a Sage 50 account to test against, so we worked with a merchant to produce this drop-in function that formats the basket the way Sage 50 expects.
This filter replaces Opayo’s standard basket string with a Sage 50-compatible format.
Data flow: WooCommerce Order → This Function → Opayo → Sage 50
Format produced:
Drop-in Code (copy/paste)
/**
* Opayo Basket Payload Modifier for Sage 50 Integration
*
* This function modifies WooCommerce order data into Opayo's required basket format,
* which is then passed to Sage 50 accounting software for proper transaction recording.
*
* Data Flow: WooCommerce Order → This Function → Opayo → Sage 50
*
* Current Format:
* - Line count prefix: <number_of_lines>:
* - Product lines: [SKU]Name:<qty>:<unit_ex_vat>:<unit_vat>:<unit_inc_vat>:<line_inc_vat>
* - Delivery: Delivery:000:000:000:000:<delivery_total_with_vat>
* - Discount: Discount:000:000:000:000:-<discount_total_with_vat>
* - All amounts use "." decimal separator with exactly 2 decimals
* - Tax/VAT is embedded per product line via <unit_vat>
*
*/
add_filter('ag_opayo_modify_basket_full_string', 'modify_basket_payload', 10, 2);
function modify_basket_payload($basket, $order)
{
$basket = ''; // Reset basket
$item_loop = 0; // Initialize item loop count
$basket_lines = array(); // Array to store basket lines
// Process each product/item in the order
foreach ($order->get_items() as $item) {
if ($item['qty']) {
$item_loop++; // Increment item loop for each item
$product = $item->get_product();
// Get item name and add SKU if available (matching original working format)
$item_name = esc_attr($item->get_name());
if ($product && $product->get_sku()) {
$item_name = '[' . preg_replace("/[^a-zA-Z0-9-.]+/", "", $product->get_sku()) . ']' . $item_name;
}
// Add product meta data (matching original working format)
$meta_display = '';
foreach ($item->get_formatted_meta_data() as $meta_key => $meta) {
$meta_display .= ' ( ' . $meta->display_key . ':' . $meta->value . ' )';
}
if ($meta_display) {
$item_name .= $meta_display;
}
// Sanitize item name (matching original working format)
$item_name = str_replace(':', ' ', $item_name);
// Build the basket line using Opayo format: [SKU]Name:<qty>:<unit_ex_vat>:<unit_vat>:<unit_inc_vat>:<line_inc_vat>
$unit_ex_vat = $order->get_item_total($item, false);
$unit_vat = $order->get_item_tax($item) / $item['qty']; // Unit VAT amount
$unit_inc_vat = $unit_ex_vat + $unit_vat;
$line_inc_vat = $order->get_line_total($item, true);
$basket_lines[] = $item_name // [SKU]Name
. ':' . $item['qty'] // Quantity
. ':' . number_format($unit_ex_vat, 2, '.', '') // Unit ex VAT
. ':' . number_format($unit_vat, 2, '.', '') // Unit VAT
. ':' . number_format($unit_inc_vat, 2, '.', '') // Unit inc VAT
. ':' . number_format($line_inc_vat, 2, '.', ''); // Line inc VAT
}
}
// Process any additional fees (e.g., service charges, handling fees)
if (sizeof($order->get_fees()) > 0) {
foreach ($order->get_fees() as $order_item) {
$item_loop++;
// Format fees as product lines with quantity 1 and no VAT
$basket_lines[] = str_replace(':', ' ', $order_item['name'])
. ':1:' . number_format($order_item['line_total'], 2, '.', '') // Unit price
. ':0.00:' . number_format($order_item['line_total'], 2, '.', '') // Unit VAT (0 for fees)
. ':' . number_format($order_item['line_total'], 2, '.', ''); // Line total
}
}
// Add shipping details - Format: Delivery:000:000:000:000:<delivery_total_with_vat>
$total_shipping = $order->get_shipping_total();
$shipping_tax = $order->get_shipping_tax();
if ($total_shipping > 0) {
$delivery_total = $total_shipping + $shipping_tax;
$basket_lines[] = 'Delivery:000:000:000:000:' . number_format($delivery_total, 2, '.', '');
$item_loop++;
}
// Add discount - Format: Discount:000:000:000:000:-<discount_total_with_vat>
if ($order->get_total_discount() > 0) {
$basket_lines[] = 'Discount:000:000:000:000:-' . number_format($order->get_total_discount(), 2, '.', '');
$item_loop++;
}
// Build final basket string with line count prefix
$basket = $item_loop . ':' . implode(':', $basket_lines);
// Clean up any unwanted characters
$basket = str_replace("\n", "", $basket);
$basket = str_replace("\r", "", $basket);
// Check basket length limit (Opayo has a 7500 character limit)
if (mb_strlen($basket) > 7500) {
AG_SS_Helpers::ag_log('[Basket Modifier] Basket too long, returning NULL', 'debug', 'yes');
$basket = NULL;
}
// Return the formatted basket payload for Opayo → Sage 50 integration
return $basket;
}3:[ABC123]Blue T-Shirt ( Size:L ) :2:12.50:2.50:15.00:30.00:Delivery:000:000:000:000:4.79:Discount:000:000:000:000:-5.00
Can I change the wording of “Delivery” or “Discount”? Yes, edit the two label strings in the snippet, but keep the :000:000:000:000: structure intact.
We need meta in a different format. Adjust the section that builds $meta_display. Keep colons out of labels/values.
Does this affect non-Sage 50 sites? It only changes the basket string at the Opayo hand-off. If you remove the snippet, behaviour reverts to default.
Was this helpful?

Our team are on hand to provide fast, helpful and professional support.
Start exploring our fully functional demo site today.
Gain exclusive admin access to see what's possible.