Skip to content

Commit 7a0c95a

Browse files
Provide a more realworld example of a calculator
1 parent 85fabad commit 7a0c95a

File tree

2 files changed

+124
-16
lines changed

2 files changed

+124
-16
lines changed

16/umbraco-commerce/key-concepts/calculators.md

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,74 @@ All Calculator services can be replaced with alternative implementations should
1919
The individual Calculator interfaces may differ but the process for defining a custom Calculator implementation is the same for all of them. It is possible to create a new class that implements the default system Calculator that you wish to replace. You can then override the relevant calculation methods.
2020

2121
```csharp
22-
public class MyProductCalculator : ProductCalculator
22+
public class VolumeDiscountProductCalculator : ProductCalculatorBase
2323
{
24-
public MyProductCalculator(ITaxService taxService, IStoreService storeService)
25-
: base(taxService, storeService)
26-
{ }
24+
private readonly ITaxService _taxService;
25+
private readonly IStoreService _storeService;
2726

28-
public override async Task<TaxRate> CalculateProductTaxRateAsync(IProductSnapshot productSnapshot, TaxSource taxSource, TaxRate fallbackTaxRate)
27+
public VolumeDiscountProductCalculator(ITaxService taxService, IStoreService storeService)
2928
{
30-
// Do custom tax rate calculation here
29+
_taxService = taxService;
30+
_storeService = storeService;
3131
}
3232

33-
public override async Task<Price> CalculateProductPriceAsync(IProductSnapshot productSnapshot, Guid currencyId, TaxRate taxRate)
33+
public override async Task<Attempt<TaxRate>> TryCalculateProductTaxRateAsync(
34+
IProductSnapshot productSnapshot,
35+
TaxSource taxSource,
36+
TaxRate fallbackTaxRate,
37+
ProductCalculatorContext context = null,
38+
CancellationToken cancellationToken = default)
3439
{
35-
// Do custom price calculation here
40+
productSnapshot.MustNotBeNull(nameof(productSnapshot));
41+
fallbackTaxRate.MustNotBeNull(nameof(fallbackTaxRate));
42+
43+
TaxRate taxRate = fallbackTaxRate;
44+
45+
// Use the product's tax class if one is assigned
46+
if (productSnapshot.TaxClassId != null)
47+
{
48+
taxRate = (await _taxService.GetTaxClassAsync(productSnapshot.TaxClassId.Value))
49+
.GetTaxRate(taxSource);
50+
}
51+
52+
return Attempt.Succeed(taxRate);
53+
}
54+
55+
public override async Task<Attempt<Price>> TryCalculateProductPriceAsync(
56+
IProductSnapshot productSnapshot,
57+
Guid currencyId,
58+
TaxRate taxRate,
59+
ProductCalculatorContext context = null,
60+
CancellationToken cancellationToken = default)
61+
{
62+
taxRate.MustNotBeNull(nameof(taxRate));
63+
64+
StoreReadOnly store = await _storeService.GetStoreAsync(productSnapshot.StoreId);
65+
66+
// Get the base unit price for the currency
67+
var unitPrice = productSnapshot.Prices?.FirstOrDefault(x => x.CurrencyId == currencyId)?.Value ?? 0m;
68+
69+
// Apply volume discount based on quantity
70+
var quantity = context?.OrderLine?.Quantity ?? 1m;
71+
var discountedPrice = unitPrice;
72+
73+
if (quantity >= 100)
74+
{
75+
discountedPrice = unitPrice * 0.85m; // 15% discount for 100+ items
76+
}
77+
else if (quantity >= 50)
78+
{
79+
discountedPrice = unitPrice * 0.90m; // 10% discount for 50-99 items
80+
}
81+
else if (quantity >= 10)
82+
{
83+
discountedPrice = unitPrice * 0.95m; // 5% discount for 10-49 items
84+
}
85+
86+
// Calculate final price with tax using the store's tax configuration
87+
var price = Price.Calculate(discountedPrice, taxRate, currencyId, store.PricesIncludeTax);
88+
89+
return Attempt.Succeed(price);
3690
}
3791
}
3892

17/umbraco-commerce/key-concepts/calculators.md

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,74 @@ All Calculator services can be replaced with alternative implementations should
1919
The individual Calculator interfaces may differ but the process for defining a custom Calculator implementation is the same for all of them. It is possible to create a new class that implements the default system Calculator that you wish to replace. You can then override the relevant calculation methods.
2020

2121
```csharp
22-
public class MyProductCalculator : ProductCalculator
22+
public class VolumeDiscountProductCalculator : ProductCalculatorBase
2323
{
24-
public MyProductCalculator(ITaxService taxService, IStoreService storeService)
25-
: base(taxService, storeService)
26-
{ }
24+
private readonly ITaxService _taxService;
25+
private readonly IStoreService _storeService;
2726

28-
public override async Task<TaxRate> CalculateProductTaxRateAsync(IProductSnapshot productSnapshot, TaxSource taxSource, TaxRate fallbackTaxRate)
27+
public VolumeDiscountProductCalculator(ITaxService taxService, IStoreService storeService)
2928
{
30-
// Do custom tax rate calculation here
29+
_taxService = taxService;
30+
_storeService = storeService;
3131
}
3232

33-
public override async Task<Price> CalculateProductPriceAsync(IProductSnapshot productSnapshot, Guid currencyId, TaxRate taxRate)
33+
public override async Task<Attempt<TaxRate>> TryCalculateProductTaxRateAsync(
34+
IProductSnapshot productSnapshot,
35+
TaxSource taxSource,
36+
TaxRate fallbackTaxRate,
37+
ProductCalculatorContext context = null,
38+
CancellationToken cancellationToken = default)
3439
{
35-
// Do custom price calculation here
40+
productSnapshot.MustNotBeNull(nameof(productSnapshot));
41+
fallbackTaxRate.MustNotBeNull(nameof(fallbackTaxRate));
42+
43+
TaxRate taxRate = fallbackTaxRate;
44+
45+
// Use the product's tax class if one is assigned
46+
if (productSnapshot.TaxClassId != null)
47+
{
48+
taxRate = (await _taxService.GetTaxClassAsync(productSnapshot.TaxClassId.Value))
49+
.GetTaxRate(taxSource);
50+
}
51+
52+
return Attempt.Succeed(taxRate);
53+
}
54+
55+
public override async Task<Attempt<Price>> TryCalculateProductPriceAsync(
56+
IProductSnapshot productSnapshot,
57+
Guid currencyId,
58+
TaxRate taxRate,
59+
ProductCalculatorContext context = null,
60+
CancellationToken cancellationToken = default)
61+
{
62+
taxRate.MustNotBeNull(nameof(taxRate));
63+
64+
StoreReadOnly store = await _storeService.GetStoreAsync(productSnapshot.StoreId);
65+
66+
// Get the base unit price for the currency
67+
var unitPrice = productSnapshot.Prices?.FirstOrDefault(x => x.CurrencyId == currencyId)?.Value ?? 0m;
68+
69+
// Apply volume discount based on quantity
70+
var quantity = context?.OrderLine?.Quantity ?? 1m;
71+
var discountedPrice = unitPrice;
72+
73+
if (quantity >= 100)
74+
{
75+
discountedPrice = unitPrice * 0.85m; // 15% discount for 100+ items
76+
}
77+
else if (quantity >= 50)
78+
{
79+
discountedPrice = unitPrice * 0.90m; // 10% discount for 50-99 items
80+
}
81+
else if (quantity >= 10)
82+
{
83+
discountedPrice = unitPrice * 0.95m; // 5% discount for 10-49 items
84+
}
85+
86+
// Calculate final price with tax using the store's tax configuration
87+
var price = Price.Calculate(discountedPrice, taxRate, currencyId, store.PricesIncludeTax);
88+
89+
return Attempt.Succeed(price);
3690
}
3791
}
3892

0 commit comments

Comments
 (0)