Documentation

Add_filter | Form, Server, Direct & Pi | Sage 50 Basket Format

We’ve put this guide together as a little helping hand for developers and designers. You’ll need a basic understanding of PHP to make the most of it.

Just a quick heads-up, we can’t provide support for custom code or offer any bespoke tweaks. Think of the snippets below as examples to guide you in the right direction.

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.

Overview

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:

  • Line count prefix: <number_of_lines>:
  • Product lines: [SKU]Name:<qty>:<unit_ex_vat>:<unit_vat>:<unit_inc_vat>:<line_inc_vat>
  • Delivery line: Delivery:000:000:000:000:<delivery_total_inc_vat>
  • Discount line: Discount:000:000:000:000:-<discount_total_inc_vat>
  • Amounts: dot decimal (.) with exactly 2 decimals
  • Tax/VAT: embedded per product line via <unit_vat>

How to Use

  1. You can drop the snippet into your child theme’s functions.php file, a site-specific plugin, or a safe “Code Snippets” plugin, whichever suits your setup best.
  2. Make a test order and verify the basket line string in your gateway request (enable our plugin’s debug logs to see it).
  3. Confirm with your Sage 50 import that lines are arriving as expected.

The Filter & Callback

  • Filter: ag_opayo_modify_basket_full_string
  • Signature: function ( string $basket, WC_Order $order ): string|null
  • When it runs: Right before we hand the basket to Opayo.
  • Return: The full basket string (or null to fall back to default behaviour).

Drop-in Code (copy/paste)

PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
/**
 * 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; 
}

Example Output

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

  • 3 lines total (1 product + Delivery + Discount)
  • Product line is [SKU]Name:qty:unit_ex:unit_vat:unit_inc:line_inc
  • Delivery is inc VAT total
  • Discount is a negative inc VAT total

Compatibility Notes

  • Works with standard/simple/variable products.
  • Fees are included as individual lines (qty 1).
  • Removes standalone “Tax” and “Order Total” lines to suit Sage 50’s importer.
  • Respects Opayo’s basket length limit (~7,500 chars).

Troubleshooting

  • Wrong amounts showing in Sage 50: Check that VAT settings in WooCommerce match Sage 50 (rates, inclusive/exclusive display).
  • Basket too long: Very large orders with many variations/options may exceed Opayo’s limit. Consider shortening product names or trimming verbose meta.
  • Colons in names break the format: The snippet strips : from names. Avoid pasting colons into product/fee titles.

FAQ

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?

Opayo

Don't already have the plugin? Get access now.

Still need help?

Our team are on hand to provide fast, helpful and professional support.

Support request
All systems operational

Back End Demo

Start exploring our fully functional demo site today.
Gain exclusive admin access to see what's possible.

Create your demo now

Front End Demo

Test drive our plugin on the demo site.

View Demo