///|
/// Validate a WeChat order amount. Amounts travel as integer fen and must be
/// positive — zero and negatives are rejected before they reach the gateway.
pub fn validate_amount_fen(fen : Int) -> Unit raise SdkError {
if fen <= 0 {
raise Config("amount must be a positive number of fen, got \{fen}")
}
}
///|
/// Format an amount in fen as the two-decimal yuan string Alipay expects in
/// `total_amount`: 8800 -> "88.00", 5 -> "0.05". The amount must be positive.
pub fn format_yuan(fen : Int) -> String raise SdkError {
validate_amount_fen(fen)
let yuan = fen / 100
let cents = fen % 100
"\{yuan}.\{if cents < 10 { "0\{cents}" } else { "\{cents}" }}"
}
///|
/// Parse the two-decimal yuan string Alipay returns in notifications and
/// answers into integer fen: "88.00" -> 8800. Anything but exactly two
/// decimals — one decimal, thousands separators, signs, blanks — is rejected,
/// so a mismatch always means a malformed amount, never a silent rounding.
pub fn parse_yuan_fen(text : StringView) -> Int raise SdkError {
let text = text.to_owned()
let parts = text.split(".").collect()
if parts.length() != 2 {
raise Config(
"amount \"\{text}\" must have exactly two decimals, like 88.00",
)
}
let yuan_part = parts[0]
let cents_part = parts[1]
if yuan_part.is_empty() || cents_part.length() != 2 {
raise Config(
"amount \"\{text}\" must have exactly two decimals, like 88.00",
)
}
let yuan = digits_of(yuan_part, text)
let cents = digits_of(cents_part, text)
yuan * 100 + cents
}
///|
fn digits_of(text : StringView, original : String) -> Int raise SdkError {
let mut value = 0
for ch in text {
let code = ch.to_int()
if code < 0x30 || code > 0x39 {
raise Config(
"amount \"\{original}\" must contain digits only between separators",
)
}
value = value * 10 + (code - 0x30)
}
value
}
///|
///|
fn contains_char(text : StringView, ch : Char) -> Bool {
for c in text {
if c == ch {
return true
}
}
false
}
///|
fn check_out_trade_no(
out_trade_no : StringView,
original : String,
min_len~ : Int,
max_len~ : Int,
extra_chars~ : String,
) -> Unit raise SdkError {
let len = out_trade_no.length()
if len < min_len || len > max_len {
raise Config(
"out_trade_no must be \{min_len}-\{max_len} characters, got \{len}",
)
}
for ch in out_trade_no {
let ok = (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
contains_char(extra_chars, ch)
if !ok {
raise Config(
"out_trade_no \"\{original}\" must use only letters, digits and \{extra_chars}",
)
}
}
}
///|
/// Validate an out-trade number for WeChat Pay: 6-32 characters, letters,
/// digits and `-_|*`.
pub fn validate_wechat_out_trade_no(
out_trade_no : StringView,
) -> Unit raise SdkError {
check_out_trade_no(
out_trade_no,
out_trade_no.to_owned(),
min_len=6,
max_len=32,
extra_chars="-_|*",
)
}
///|
/// Validate an out-trade number for Alipay: at most 64 characters, letters,
/// digits, underscore and hyphen.
pub fn validate_alipay_out_trade_no(
out_trade_no : StringView,
) -> Unit raise SdkError {
check_out_trade_no(
out_trade_no,
out_trade_no.to_owned(),
min_len=1,
max_len=64,
extra_chars="-_",
)
}