← All posts
· 5 min read

Implementing Dual Currency Pricing in Laravel E-Commerce: A Developer's Guide

Accept local (PKR) and international (USD) payments without a complex gateway? Here is exactly how I implemented dual currency pricing using geolocation and manual payment workflows in Laravel.

Implementing Dual Currency Pricing in Laravel E-Commerce: A Developer's Guide

How to Build Dual Currency Pricing in Laravel (PKR + USD)

When I first started building custom e-commerce platforms for local Pakistani brands, I ran into a problem that almost no SaaS platform on the market supports out of the box: Dual Currency Pricing.

These businesses have local customers who want to pay in Pakistani Rupees (PKR) via local bank transfers, and international customers (often the diaspora) who expect to see prices in US Dollars (USD) and pay via international wire transfers.

Stripe and PayPal don't offer a simple "switch the price tag based on IP address" toggle. If you want to run a localized e-commerce experience without paying an arm and a leg for an enterprise Shopify plan, you have to build it yourself.

This is exactly the problem I solved for the AURETHE jewelry platform and the Almuneer LMS.

If you are building a Laravel app that needs to serve both local and international audiences, here is the exact dual-pricing architecture I use.

Step 1: The Geolocation Middleware

Before we can show a price, we need to know where the user is.

For this, I use a lightweight, IP-based geolocation package. In Laravel, the stevebauman/location package is incredibly easy to drop in.

I create a custom middleware named ResolveVisitorCountry that runs on every public route. The goal is to store the visitor's detected country in a Laravel session:

// app/Http/Middleware/ResolveVisitorCountry.php

public function handle($request, $next)
{
    if (!session()->has('visitor_country')) {
        $position = \Location::get($request->ip());
        session(['visitor_country' => $position->countryCode ?? 'PK']);
    }

    return $next($request);
}

Why I use a session: You do not want to perform an external API lookup for every single page refresh. By caching it in the session after the first visit, the app remains blazing fast, even on shared hosting.

Step 2: The Database Schema (Dual Price Fields)

You cannot just multiply a price by a conversion rate in real-time. Exchange rates fluctuate, and you need your stored prices to be exact.

In my courses or products tables, I store two dedicated columns:

Schema::table('courses', function (Blueprint $table) {
    $table->decimal('price_pkr', 10, 2)->nullable();
    $table->decimal('price_usd', 10, 2)->nullable();
});

This gives the admins complete control over the pricing strategy. They can set price_pkr to 5,000 and price_usd to $18.

Step 3: The Dynamic Display Method

To actually display the correct price on the frontend, I attach a helper method to my Product or Course model. It checks the session we set in Step 1 and returns the correct currency symbol and amount.

// app/Models/Course.php

public function displayPrice()
{
    $country = session('visitor_country', 'PK');

    if ($country === 'PK') {
        return 'PKR ' . number_format($this->price_pkr, 0);
    } else {
        return '$' . number_format($this->price_usd, 2);
    }
}

Now, in your Blade templates, you simply call:

{{ $course->displayPrice() }}

A visitor from Lahore sees PKR 5,000.

A visitor from London sees $18.

It feels completely native to the user, boosting conversion rates.

Step 4: The Payment Handling (The "Human" Part)

Now, the trickiest part: What happens when they press "Buy"?

Since most of my clients don't have a payment gateway for both currencies, I build a dual-flow system:

  • Local Currency (PKR): Show a Bank Transfer option with local Meezan Bank details, allowing the user to upload a payment screenshot.

  • International Currency (USD): Show an International Transfer option, which displays the SWIFT/BIC codes for wire transfers.

This manual workflow was a massive success for the Almuneer LMS platform. Because we didn't force a complex Stripe integration that only works for USD, the institution was able to accept payments from both their local Pakistani audience and international donors seamlessly.

Step 5: The Admin Reporting Fix (Don't Miss This!)

If you store prices in two separate columns, your reporting dashboard will break. If you sum price_pkr and price_usd together, you will end up with a mathematically meaningless number.

To fix this, I use an order_currency column in the orders table. The exact currency used at checkout is stored alongside the order. The admin dashboard then displays currency-aware reports:

// In your admin report query

$pkrRevenue = Order::where('currency', 'PKR')->sum('total_paid');
$usdRevenue = Order::where('currency', 'USD')->sum('total_paid');

This gives the admin a clear breakdown:

  • PKR 500,000 earned locally.
  • $4,200 earned internationally.

The Final Verdict on Dual Pricing

Implementing dual currency pricing is not just about aesthetics—it is a critical user experience feature that allows businesses to scale from local to global.

If you are building an e-commerce platform in a country with a local currency that fluctuates (like Pakistan, India, or Nigeria), do not force your users into a single USD gateway. Build this dual-currency workflow.

If you are currently planning a Laravel e-commerce platform and need a consultation on how to architect your payment flows for local and international currencies, feel free to check out my engineering portfolio or reach out to me directly. I love solving these specific, real-world local payment puzzles.