Skip to main content

Multi-Currency Billing

Subscribd supports per-customer billing currencies. A customer can be billed in EUR while another is billed in USD — on the same account and gateway.

Gateway support

Not all gateways support multiple billing currencies on a single account:
GatewayMulti-Currency
Stripe✅ Yes
PayPal✅ Yes
Paddle✅ Yes (via storefront currency)
FastSpring✅ Yes (MoR handles it)
Braintree⚠️ Limited — depends on merchant account config
Square❌ No — single currency per location
For gateways without multi-currency support, supportsMultiCurrency() returns false. Attempting to create a subscription in a currency that differs from the gateway’s default will throw SubscriptionException.

Setting the billing currency

Override billingCurrency() on your Billable model:
use Pixelworxio\Subscribd\Concerns\ManagesBilling;
use Pixelworxio\Subscribd\Contracts\Billable;

class User extends Authenticatable implements Billable
{
    use ManagesBilling;

    public function billingCurrency(): string
    {
        // Return from a database column
        return strtoupper($this->preferred_currency ?? 'USD');
    }
}
The default implementation in ManagesBilling reads the currency from the user’s most recent active subscription, falling back to config('subscribd.currency', 'USD').

Currency storage

Each Subscription record stores its own currency column. Once a customer subscribes in a currency, all subsequent operations on that subscription (charges, refunds, credits) use that same currency. Invoice records store both total (minor units) and currency.

The Money value object

Subscribd uses Pixelworxio\Subscribd\Support\Money internally to carry amount + currency together:
use Pixelworxio\Subscribd\Support\Money;

$price = Money::of(4900, 'USD');   // $49.00
$price->amount;    // 4900 (int)
$price->currency;  // 'USD' (string)

// Comparison
$price->equals(Money::of(4900, 'USD'));   // true
$price->equals(Money::of(4900, 'EUR'));   // false
You will encounter Money in:
  • BillingGateway::charge() — accepts a Money argument
  • ProcessRefund::execute() — accepts an optional Money argument for partial refunds
  • BillingGateway::refundInvoice() — receives ?Money $amount

Plan currencies

Plans define a base currency column. When creating a subscription, the resolved billingCurrency() must match the plan currency unless the gateway supports on-the-fly currency conversion.
// Plans are defined with a fixed currency
Plan::create([
    'key'      => 'pro-eur',
    'price'    => 4900,
    'currency' => 'EUR',
    // ...
]);
If you want to offer the same plan in multiple currencies, create one plan per currency and resolve the right plan for the customer at checkout.

Credits and currency

Credits are per-currency. A EUR credit cannot be applied to a USD invoice:
// Add EUR credit
app(AddCredit::class)->execute($user, amount: 500, currency: 'EUR', description: 'Promo credit');

// Apply to EUR invoice
app(ApplyCreditToInvoice::class)->execute($user, $eurInvoice);

// This does nothing — no EUR credit exists for USD invoice
app(ApplyCreditToInvoice::class)->execute($user, $usdInvoice);
creditBalance(string $currency): int returns the balance for a specific currency.

Zero-decimal currencies

Currencies like JPY, KRW, and VND have no minor units. Subscribd handles zero-decimal currencies automatically:
  • Money::of(500, 'JPY') is ¥500 (not ¥5.00)
  • Gateway drivers format amounts accordingly when making API calls
Always store and pass amounts in minor units. The Money class handles zero-decimal formatting at the gateway boundary.

Next steps

  • Plans — Defining plan currencies
  • Gateways — Per-gateway currency support details
  • REST API — Currency field in API responses