Refactoring for Multi-Variant Pricing Support in Laravel

📖 2 minutes read

When evolving a Laravel application to support multiple pricing variants (like product tiers, regions, or customer types), here's a clean refactoring pattern that maintains backward compatibility while enabling complex pricing structures.

## The Challenge

You start with single-variant pricing:

“`php
public function fetchPricing($inventory)
{
$config = $inventory->api_config_standard;
$response = $this->client->getPricing($config[“product_id”]);
return $response->prices;
}
“`

Now you need to support premium, standard, and budget variants – each with different pricing.

## The Solution: Reference Extraction Pattern

Create a helper method that extracts all configured variants:

“`php
private function getVariants($inventory): Collection
{
return collect($inventory->getAllApiConfigs())
->mapWithKeys(fn($config) => [$this->getVariantKey($config) => $config]);
}

private function getVariantKey($config): string
{
return $config[“tier”] . “_” . $config[“region”];
}
“`

Then refactor your pricing method to loop through all variants:

“`php
public function fetchAdvancedPricing($inventory): Collection
{
$allPricing = collect();
$variants = $this->getVariants($inventory);

foreach ($variants as $variantKey => $config) {
$response = $this->client->getPricing($config[“product_id”]);

foreach ($response->dates as $date) {
$allPricing->push(new DatePrice(
prices: collect([new VariantPrice($config, $date->price)]),
date: $date->value
));
}
}

// Group dates with same pricing across all variants
return $allPricing->groupBy(fn($dp) => $dp->date)
->map(fn($group) => new DatePrice(
prices: $group->flatMap(fn($dp) => $dp->prices),
date: $group->first()->date
));
}
“`

## Default Pricing Fallback

Some dates might not have pricing for certain variants. Create a fallback:

“`php
private function getDefaultPrices($variants, $allPricing): Collection
{
$prices = $allPricing->flatMap(fn($dp) => $dp->prices);

return $variants->map(function($config) use ($prices) {
return $prices
->where(“variant_key”, $this->getVariantKey($config))
->sortBy(“price”)
->first() ?? new VariantPrice($config, null);
});
}
“`

## Benefits

– **Backward compatible:** Single variant still works (collection of 1)
– **Flexible:** Add new variants without changing core logic
– **Clean:** Separation of concerns (extraction, fetching, grouping)

This pattern works for any multi-dimensional pricing: age groups, membership tiers, regional pricing, seasonal rates, etc.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *