Functional Weave
Code in Python

subscriptions.proration@1.0.0

impl/rust.rs

2,470 bytes · the Rust implementation · view raw

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::dates_days_between::days_between;  ← from dates.days-between ^1.0.0 · built alongside by fune
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_add::add_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

/// Stripe-style proration for a mid-cycle plan change, by whole days: a credit
/// for the unused remainder of the old plan and a charge for the same
/// remainder on the new plan, each rounded once from the exact fraction.
///
/// # Panics
/// Panics if the period is empty or reversed, the change date is outside it,
/// the prices are in different currencies, or the mode is unknown.
pub fn prorate_change(
    old_price: &Money,
    new_price: &Money,
    period_start: &str,
    period_end: &str,
    change_date: &str,
    mode: &str,
) -> PlanChangeProration {
    let total_days = days_between(period_start, period_end);
    if total_days <= 0 {
        panic!(
            "period end must be after period start, received {} to {}",
            period_start, period_end
        );
    }
    let elapsed = days_between(period_start, change_date);
    if elapsed < 0 || elapsed > total_days {
        panic!(
            "change date must be within the billing period, received {} for {} to {}",
            change_date, period_start, period_end
        );
    }
    assert_same_currency(old_price, new_price);
    let remaining_days = total_days - elapsed;

    let credit = money(
        -round_div(old_price.minor * remaining_days, total_days, mode),
        &old_price.currency,
    );
    let charge = money(
        round_div(new_price.minor * remaining_days, total_days, mode),
        &new_price.currency,
    );
    let net = add_money(&charge, &credit);
    PlanChangeProration {
        total_days,
        remaining_days,
        credit,
        charge,
        net,
    }
}

pub fn plan_change_proration_to_value(p: &PlanChangeProration) -> Value {
    Value::obj(vec![
        ("totalDays", Value::Int(p.total_days)),
        ("remainingDays", Value::Int(p.remaining_days)),
        ("credit", money_to_value(&p.credit)),
        ("charge", money_to_value(&p.charge)),
        ("net", money_to_value(&p.net)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    plan_change_proration_to_value(&prorate_change(
        &money_from_value(&args[0]),
        &money_from_value(&args[1]),
        args[2].as_str(),
        args[3].as_str(),
        args[4].as_str(),
        args[5].as_str(),
    ))
}