E-commerceInternationalThe Good Guys

Scaling an Aussie e-commerce brand into Asia

January 15, 2024 • 11 min read

What made it hard

  • Currency – live rates, hedging, and tax that differs per market
  • Localization – which is mostly not translation
  • Logistics – shipping, duties, and where you put fulfillment
  • Payments – every market has a method people actually expect

How it went

  • Singapore – 15% conversion rate within six months
  • Malaysia – 200% YoY growth in local sales
  • Mobile – about 85% of traffic across the Asian markets
  • Satisfaction – 4.6/5 averaged across those markets

Taking an Australian store into Asia turns up problems you don't see coming until you're in the middle of them — currency, localization, logistics, and a pile of cultural stuff you only really learn by getting it wrong first. I worked on The Good Guys' expansion and the e-commerce systems behind it, and the short version is that translating the site is the easy 10%. The rest is understanding how people in each market actually shop, and reshaping the infrastructure to match.

Multi-currency, properly

Showing a different currency symbol is the part everyone thinks of. The work is underneath it — live exchange rates, tax that varies by country, and reporting that still reconciles when money moves through several currencies.

Single Currency Challenges
• AUD-only pricing
• Limited payment options
• Complex currency conversion
• Poor user experience
Multi-Currency Solution
• Native currency pricing
• Local payment methods
• Real-time rate updates
• Checkout that feels local

Getting this right lifted conversion by about 40% in the first Asian market we launched. People trust a price in their own currency far more than a converted one.

The pricing service

The core of it is a service that turns a base price into a local one. It has to deal with exchange rates, the pricing conventions of each market, and tax — without the numbers drifting out of sync. Here's the gist:

// Multi-currency pricing system
public class CurrencyService
{
    private readonly ICurrencyExchangeProvider _exchangeProvider;
    private readonly IPricingRepository _pricingRepo;
    
    public async Task<ProductPrice> GetLocalizedPrice(
        string productId, 
        string currencyCode, 
        string countryCode)
    {
        // 1. Check for manually set local prices first
        var localPrice = await _pricingRepo.GetLocalPrice(
            productId, 
            currencyCode, 
            countryCode
        );
        
        if (localPrice != null)
        {
            return new ProductPrice
            {
                Amount = localPrice.Amount,
                Currency = currencyCode,
                PricingStrategy = "Local",
                IncludesTax = localPrice.IncludesTax,
                TaxAmount = CalculateLocalTax(localPrice.Amount, countryCode)
            };
        }
        
        // 2. Convert from base currency (AUD) with current rates
        var basePrice = await _pricingRepo.GetBasePriceAUD(productId);
        var exchangeRate = await _exchangeProvider.GetExchangeRate("AUD", currencyCode);
        
        var convertedAmount = basePrice.Amount * exchangeRate.Rate;
        
        // 3. Apply local market pricing strategy
        var finalAmount = ApplyLocalMarketStrategy(
            convertedAmount, 
            currencyCode, 
            countryCode
        );
        
        return new ProductPrice
        {
            Amount = Math.Round(finalAmount, 2),
            Currency = currencyCode,
            PricingStrategy = "Converted",
            ExchangeRate = exchangeRate.Rate,
            BaseAmount = basePrice.Amount,
            BaseCurrency = "AUD",
            IncludesTax = CalculateLocalTax(finalAmount, countryCode) > 0,
            TaxAmount = CalculateLocalTax(finalAmount, countryCode)
        };
    }
    
    private decimal ApplyLocalMarketStrategy(
        decimal convertedAmount, 
        string currency, 
        string country)
    {
        // Round to local pricing conventions
        switch (currency)
        {
            case "SGD":
                // Singapore prefers .95 endings
                return Math.Round(convertedAmount - 0.05m, 2);
            
            case "MYR":
                // Malaysia rounds to nearest .50
                return Math.Round(convertedAmount * 2, MidpointRounding.AwayFromZero) / 2;
            
            case "JPY":
                // Japan uses whole numbers only
                return Math.Round(convertedAmount, 0);
            
            default:
                return Math.Round(convertedAmount, 2);
        }
    }
}

Localization that isn't translation

Each market shops differently — different payment habits, different colour associations, different expectations about delivery and reviews. Those differences move conversion more than the wording does. We ended up driving a lot of it from per-market config:

// Market-specific localization configurations
const marketConfigurations = {
  singapore: {
    paymentMethods: ['credit_card', 'paypal', 'grab_pay', 'bank_transfer'],
    preferredColors: ['red', 'gold'], // Auspicious colors
    shippingOptions: ['same_day', 'next_day', 'standard'],
    taxDisplay: 'inclusive', // GST included in price
    mobileFirst: true,
    reviewsImportance: 'high',
    socialProof: ['testimonials', 'ratings', 'social_shares'],
    checkoutPreferences: {
      guestCheckout: true,
      savedPaymentMethods: true,
      expressCheckout: ['grab_pay', 'paypal']
    }
  },
  
  malaysia: {
    paymentMethods: ['credit_card', 'maybank', 'cimb', 'public_bank', 'grab_pay'],
    preferredColors: ['blue', 'green', 'red'],
    shippingOptions: ['standard', 'express', 'pickup_points'],
    taxDisplay: 'exclusive', // SST shown separately
    mobileFirst: true,
    reviewsImportance: 'critical',
    socialProof: ['local_testimonials', 'influencer_endorsements'],
    languageSupport: ['english', 'bahasa_malaysia', 'chinese'],
    checkoutPreferences: {
      installmentPayments: true, // Very popular in Malaysia
      bankTransferOptions: true,
      cashOnDelivery: true
    }
  },
  
  japan: {
    paymentMethods: ['credit_card', 'konbini', 'bank_transfer', 'cod'],
    preferredColors: ['white', 'blue', 'minimal_palette'],
    shippingOptions: ['same_day_tokyo', 'next_day', 'convenience_store_pickup'],
    taxDisplay: 'inclusive',
    mobileFirst: true,
    reviewsImportance: 'very_high',
    detailLevel: 'extensive', // Japanese consumers want detailed product info
    qualityCertifications: true,
    checkoutPreferences: {
      detailedReceipts: true,
      preciseDeliveryTimes: true,
      multiplePaymentOptions: true
    }
  }
};

// Dynamic UI adaptation based on market
export const getLocalizedProductCard = (product, market) => {
  const config = marketConfigurations[market];
  
  return {
    priceDisplay: config.taxDisplay === 'inclusive' 
      ? `${product.price} (incl. tax)` 
      : `${product.price} + tax`,
    
    paymentBadges: config.paymentMethods
      .slice(0, 3)
      .map(method => getPaymentMethodIcon(method)),
    
    shippingMessage: getLocalizedShippingMessage(config.shippingOptions[0], market),
    
    socialProof: config.socialProof.includes('ratings') 
      ? product.averageRating 
      : null,
    
    colorScheme: config.preferredColors[0],
    
    callToAction: getLocalizedCTA(market)
  };
};

Shipping, duties and fulfillment

Cross-border shipping is where the customer experience can quietly fall apart — a duty bill at the door, or a two-week delivery, undoes a good checkout. The calculator has to weigh duties, local tax thresholds, and whether there's a fulfillment centre in-country that avoids the border entirely:

// International shipping calculation system
public class InternationalShippingCalculator
{
    public async Task<ShippingQuote> CalculateShipping(
        ShippingRequest request)
    {
        var originCountry = "AU"; // Australia
        var destinationCountry = request.DeliveryAddress.CountryCode;
        
        // 1. Calculate base shipping cost
        var baseShipping = await CalculateBaseShippingCost(
            request.Items, 
            originCountry, 
            destinationCountry
        );
        
        // 2. Calculate duties and taxes
        var dutiesAndTaxes = await CalculateImportDuties(
            request.Items, 
            destinationCountry
        );
        
        // 3. Check for local fulfillment options
        var localFulfillment = await CheckLocalFulfillmentCenters(
            request.Items, 
            destinationCountry
        );
        
        if (localFulfillment.Available)
        {
            return new ShippingQuote
            {
                ShippingCost = localFulfillment.Cost,
                EstimatedDelivery = localFulfillment.DeliveryTime,
                DutiesAndTaxes = 0, // No cross-border charges
                FulfillmentMethod = "Local",
                TrackingAvailable = true,
                InsuranceIncluded = true
            };
        }
        
        // 4. International shipping with duties
        return new ShippingQuote
        {
            ShippingCost = baseShipping.Cost,
            EstimatedDelivery = baseShipping.DeliveryTime,
            DutiesAndTaxes = dutiesAndTaxes.TotalAmount,
            FulfillmentMethod = "International",
            CarrierOptions = new[] { "DHL", "FedEx", "Australia Post" },
            CustomsHandling = true,
            TrackingAvailable = true,
            InsuranceIncluded = baseShipping.Cost > 100
        };
    }
    
    private async Task<DutiesCalculation> CalculateImportDuties(
        List<CartItem> items, 
        string destinationCountry)
    {
        var totalValue = items.Sum(i => i.Price * i.Quantity);
        var dutyRate = await GetDutyRate(items.First().Category, destinationCountry);
        var taxRate = await GetLocalTaxRate(destinationCountry);
        
        // Most countries have duty-free thresholds
        var dutyFreeThreshold = GetDutyFreeThreshold(destinationCountry);
        
        if (totalValue <= dutyFreeThreshold)
        {
            return new DutiesCalculation
            {
                DutyAmount = 0,
                TaxAmount = 0,
                TotalAmount = 0,
                Reason = "Below duty-free threshold"
            };
        }
        
        var dutyAmount = (totalValue - dutyFreeThreshold) * dutyRate;
        var taxAmount = (totalValue + dutyAmount) * taxRate;
        
        return new DutiesCalculation
        {
            DutyAmount = Math.Round(dutyAmount, 2),
            TaxAmount = Math.Round(taxAmount, 2),
            TotalAmount = Math.Round(dutyAmount + taxAmount, 2),
            DutyRate = dutyRate,
            TaxRate = taxRate,
            DutyFreeThreshold = dutyFreeThreshold
        };
    }
}

What I'd do differently with hindsight

  • Learn the market before you build for it – how people pay, what they expect, what the local norms are. Cheaper to find out first.
  • Have a currency strategy, not just support – local pricing is one thing; hedging against rate swings is another, and finance will care about it.
  • Treat mobile as the default – most of the traffic in these markets is on a phone, so that's the experience to get right first.
  • Sort out logistics partners early – local fulfillment and carriers are what make delivery times competitive.
  • Map the compliance early too – tax, import/export and data privacy rules differ per market and aren't something to discover at launch.

The numbers, and what they cost

# The Good Guys Asian Expansion Results (2020-2022)

Investment Breakdown:
- Platform localization: $450K
- Local fulfillment centers: $1.2M  
- Marketing & partnerships: $800K
- Legal & compliance: $200K
- Total investment: $2.65M

Market Performance:
┌─────────────────────┬─────────────┬──────────────┬─────────────┐
│ Market              │ Launch Date │ Break-even   │ 2022 Revenue│
├─────────────────────┼─────────────┼──────────────┼─────────────┤
│ Singapore           │ March 2020  │ 8 months     │ $4.2M       │
│ Malaysia            │ August 2020 │ 12 months    │ $2.8M       │
│ Hong Kong           │ Feb 2021    │ 10 months    │ $1.9M       │
│ Thailand (pilot)    │ Nov 2021    │ Ongoing      │ $0.8M       │
└─────────────────────┴─────────────┴──────────────┴─────────────┘

Total ROI: 3.6x return on investment by end of 2022

Key Success Factors:
✅ Local payment method integration (40% conversion lift)
✅ Mobile-optimized checkout flows (60% mobile conversion)
✅ In-country fulfillment centers (70% faster delivery)
✅ Cultural localization beyond translation (25% engagement increase)
✅ Local customer service teams (4.6/5 satisfaction rating)

Biggest Challenges Overcome:
• Currency hedging strategy to manage exchange rate risk
• Complex tax compliance across multiple jurisdictions
• Integration with local logistics providers and customs
• Cultural adaptation of product descriptions and marketing
• Building trust with customers in new markets

The lasting lesson for me was that the technology was rarely the bottleneck. The hard part was trust — getting customers in a new market to believe an Australian brand would deliver, charge them fairly, and handle their data properly. You earn that by adapting to how they already shop, not by asking them to adapt to you. It's expensive and slow, and it was worth it.

Share: