///|
fn has_duplicate(ids : Array[String], id : String) -> Bool {
count_string(ids, id) > 1
}
///|
fn valid_currency(policy : Policy, currency : String) -> Bool {
contains_string(policy.allowed_currencies, currency)
}
///|
fn validate_orders(
input : LedgerInput,
policy : Policy,
findings : Array[Finding],
) -> Unit {
let ids : Array[String] = []
for order in input.orders {
ids.push(order.id)
}
for order in input.orders {
if is_blank(order.id) {
findings.push(
make_finding(
MissingReference,
Error,
Order,
order.id,
order.id,
"order id is required",
),
)
}
if has_duplicate(ids, order.id) {
findings.push(
make_finding(
DuplicateOrderId,
Error,
Order,
order.id,
order.id,
"order id appears more than once",
),
)
}
if order.amount_minor <= 0 {
findings.push(
make_finding(
InvalidAmount,
Error,
Order,
order.id,
order.id,
"order amount must be positive",
),
)
}
if !valid_currency(policy, order.currency) {
findings.push(
make_finding(
InvalidCurrency,
Error,
Order,
order.id,
order.id,
"order currency is not allowed by policy",
),
)
}
}
}
///|
fn validate_payments(
input : LedgerInput,
policy : Policy,
findings : Array[Finding],
) -> Unit {
let ids : Array[String] = []
for payment in input.payments {
ids.push(payment.id)
}
for payment in input.payments {
if has_duplicate(ids, payment.id) {
findings.push(
make_finding(
DuplicatePaymentId,
Error,
Payment,
payment.id,
payment.order_id,
"payment id appears more than once",
),
)
}
if payment.amount_minor <= 0 {
findings.push(
make_finding(
InvalidAmount,
Error,
Payment,
payment.id,
payment.order_id,
"payment amount must be positive",
),
)
}
match find_order(input, payment.order_id) {
None =>
findings.push(
make_finding(
UnknownOrder,
Error,
Payment,
payment.id,
payment.order_id,
"payment references an unknown order",
),
)
Some(order) => {
if order.currency != payment.currency {
findings.push(
make_finding(
CurrencyMismatch,
Error,
Payment,
payment.id,
payment.order_id,
"payment currency differs from order currency",
),
)
}
if payment.received_day - order.created_day >
policy.max_payment_lag_days {
findings.push(
make_finding(
PaymentAfterDeadline,
Warning,
Payment,
payment.id,
payment.order_id,
"payment arrived after the configured payment window",
),
)
}
if order.status == Cancelled && !policy.permit_cancelled_payment {
findings.push(
make_finding(
PaymentForCancelledOrder,
Error,
Payment,
payment.id,
payment.order_id,
"captured payment belongs to a cancelled order",
),
)
}
}
}
if payment.status == Voided {
findings.push(
make_finding(
PaymentVoided,
Info,
Payment,
payment.id,
payment.order_id,
"voided payment is excluded from captured total",
),
)
}
}
}
///|
fn validate_refunds(
input : LedgerInput,
policy : Policy,
findings : Array[Finding],
) -> Unit {
let ids : Array[String] = []
for refund in input.refunds {
ids.push(refund.id)
}
for refund in input.refunds {
if has_duplicate(ids, refund.id) {
findings.push(
make_finding(
DuplicateRefundId,
Error,
Refund,
refund.id,
refund.order_id,
"refund id appears more than once",
),
)
}
if refund.amount_minor <= 0 {
findings.push(
make_finding(
InvalidAmount,
Error,
Refund,
refund.id,
refund.order_id,
"refund amount must be positive",
),
)
}
match find_order(input, refund.order_id) {
None =>
findings.push(
make_finding(
UnknownOrder,
Error,
Refund,
refund.id,
refund.order_id,
"refund references an unknown order",
),
)
Some(order) =>
if order.currency != refund.currency {
findings.push(
make_finding(
CurrencyMismatch,
Error,
Refund,
refund.id,
refund.order_id,
"refund currency differs from order currency",
),
)
}
}
}
ignore(policy)
}
///|
fn validate_adjustments(
input : LedgerInput,
policy : Policy,
findings : Array[Finding],
) -> Unit {
let ids : Array[String] = []
for adjustment in input.adjustments {
ids.push(adjustment.id)
}
for adjustment in input.adjustments {
if has_duplicate(ids, adjustment.id) {
findings.push(
make_finding(
DuplicateAdjustmentId,
Error,
Adjustment,
adjustment.id,
adjustment.order_id,
"adjustment id appears more than once",
),
)
}
if adjustment.amount_minor <= 0 {
findings.push(
make_finding(
InvalidAmount,
Error,
Adjustment,
adjustment.id,
adjustment.order_id,
"adjustment amount must be positive",
),
)
}
if is_blank(adjustment.reason) {
findings.push(
make_finding(
MissingReference,
Warning,
Adjustment,
adjustment.id,
adjustment.order_id,
"adjustment should carry a reason",
),
)
}
if find_order(input, adjustment.order_id) is None {
findings.push(
make_finding(
UnknownOrder,
Error,
Adjustment,
adjustment.id,
adjustment.order_id,
"adjustment references an unknown order",
),
)
}
}
ignore(policy)
}
///|
pub fn validate(input : LedgerInput, policy : Policy) -> Array[Finding] {
let findings : Array[Finding] = []
validate_orders(input, policy, findings)
validate_payments(input, policy, findings)
validate_refunds(input, policy, findings)
validate_adjustments(input, policy, findings)
findings
}