Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_add::subtract_money; ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_compare::compare_money; ← from money.compare ^1.0.0 · built alongside by fune
/// Redeem points against a purchase, then earn on the amount actually paid,
/// at the member's tier multiplier.
///
/// # Panics
/// Panics on mixed currencies, negative amounts, or a redemption that is not
/// whole blocks, exceeds the balance or exceeds the spend.
pub fn loyalty_points(
scheme: &LoyaltyScheme,
spend: &Money,
qualifying_spend: &Money,
balance: i64,
redeem_points: i64,
) -> LoyaltyOutcome {
assert_same_currency(spend, &scheme.earn_per);
assert_same_currency(spend, &scheme.block_value);
assert_same_currency(spend, qualifying_spend);
for t in &scheme.tiers {
assert_same_currency(spend, &t.threshold);
}
if spend.minor < 0 {
panic!("spend must not be negative, received {}", spend.minor);
}
if scheme.earn_per.minor <= 0 {
panic!("earnPer must be more than 0, received {}", scheme.earn_per.minor);
}
if scheme.earn_points < 0 {
panic!("earnPoints must not be negative, received {}", scheme.earn_points);
}
if scheme.redeem_block < 1 {
panic!("redeemBlock must be 1 or more, received {}", scheme.redeem_block);
}
if balance < 0 {
panic!("balance must not be negative, received {}", balance);
}
if redeem_points < 0 {
panic!("redeemPoints must not be negative, received {}", redeem_points);
}
if redeem_points > balance {
panic!("cannot redeem {} points from a balance of {}", redeem_points, balance);
}
if redeem_points % scheme.redeem_block != 0 {
panic!(
"points are redeemed in blocks of {}, received {}",
scheme.redeem_block, redeem_points
);
}
let discount = money(
redeem_points / scheme.redeem_block * scheme.block_value.minor,
&spend.currency,
);
if compare_money(&discount, spend) > 0 {
panic!(
"redeeming {} points is worth {}, more than the spend of {}",
redeem_points, discount.minor, spend.minor
);
}
let amount_payable = subtract_money(spend, &discount);
let mut tier: Option<String> = None;
let mut multiplier_basis_points = 10000;
let mut best: Option<Money> = None;
for t in &scheme.tiers {
if compare_money(&t.threshold, qualifying_spend) > 0 {
continue;
}
let better = match &best {
None => true,
Some(b) => compare_money(&t.threshold, b) > 0,
};
if better {
best = Some(t.threshold.clone());
tier = Some(t.name.clone());
multiplier_basis_points = t.multiplier_basis_points;
}
}
let base = round_div(
amount_payable.minor * scheme.earn_points,
scheme.earn_per.minor,
&scheme.earn_rounding,
);
let earned_points = round_div(base * multiplier_basis_points, 10000, &scheme.earn_rounding);
LoyaltyOutcome {
tier,
multiplier_basis_points,
redeemed_points: redeem_points,
discount,
amount_payable,
earned_points,
new_balance: balance - redeem_points + earned_points,
}
}
pub fn loyalty_scheme_from_value(v: &Value) -> LoyaltyScheme {
LoyaltyScheme {
earn_points: v.get("earnPoints").as_i64(),
earn_per: money_from_value(v.get("earnPer")),
earn_rounding: v.get("earnRounding").as_str().to_string(),
redeem_block: v.get("redeemBlock").as_i64(),
block_value: money_from_value(v.get("blockValue")),
tiers: v
.get("tiers")
.as_arr()
.iter()
.map(|t| LoyaltyTier {
name: t.get("name").as_str().to_string(),
threshold: money_from_value(t.get("threshold")),
multiplier_basis_points: t.get("multiplierBasisPoints").as_i64(),
})
.collect(),
}
}
pub fn loyalty_outcome_to_value(o: &LoyaltyOutcome) -> Value {
Value::obj(vec![
(
"tier",
match &o.tier {
Some(name) => Value::str(name),
None => Value::Null,
},
),
("multiplierBasisPoints", Value::Int(o.multiplier_basis_points)),
("redeemedPoints", Value::Int(o.redeemed_points)),
("discount", money_to_value(&o.discount)),
("amountPayable", money_to_value(&o.amount_payable)),
("earnedPoints", Value::Int(o.earned_points)),
("newBalance", Value::Int(o.new_balance)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
loyalty_outcome_to_value(&loyalty_points(
&loyalty_scheme_from_value(&args[0]),
&money_from_value(&args[1]),
&money_from_value(&args[2]),
args[3].as_i64(),
args[4].as_i64(),
))
}