| 7 |
7 |
|
//!
|
| 8 |
8 |
|
//! See also: `/docs/guide/pricing`
|
| 9 |
9 |
|
|
|
10 |
+ |
use crate::currency::SettlementCurrency;
|
| 10 |
11 |
|
use crate::db;
|
| 11 |
12 |
|
use crate::error::AppError;
|
| 12 |
13 |
|
use crate::helpers;
|
| 192 |
193 |
|
fn can_access(&self, ctx: &AccessContext) -> bool;
|
| 193 |
194 |
|
|
| 194 |
195 |
|
/// Human-readable price string for display (e.g. "$9.99", "Free", "PWYW").
|
| 195 |
|
- |
fn price_display(&self) -> String;
|
|
196 |
+ |
fn price_display(&self, currency: SettlementCurrency) -> String;
|
| 196 |
197 |
|
|
| 197 |
198 |
|
/// Raw price in cents (0 for free/subscription).
|
| 198 |
199 |
|
fn price_cents(&self) -> i32;
|
| 206 |
207 |
|
fn checkout_type(&self) -> CheckoutType;
|
| 207 |
208 |
|
|
| 208 |
209 |
|
/// Validate a buyer-submitted amount in cents. Returns `Ok(())` or an error message.
|
| 209 |
|
- |
fn validate_amount(&self, amount_cents: i32) -> Result<(), String>;
|
|
210 |
+ |
fn validate_amount(
|
|
211 |
+ |
&self,
|
|
212 |
+ |
amount_cents: i32,
|
|
213 |
+ |
currency: SettlementCurrency,
|
|
214 |
+ |
) -> Result<(), String>;
|
| 210 |
215 |
|
|
| 211 |
216 |
|
/// The DB discriminant for this pricing model.
|
| 212 |
217 |
|
fn kind(&self) -> db::PricingKind;
|
| 227 |
232 |
|
true
|
| 228 |
233 |
|
}
|
| 229 |
234 |
|
|
| 230 |
|
- |
fn price_display(&self) -> String {
|
|
235 |
+ |
fn price_display(&self, _currency: SettlementCurrency) -> String {
|
| 231 |
236 |
|
"Free".to_string()
|
| 232 |
237 |
|
}
|
| 233 |
238 |
|
|
| 239 |
244 |
|
CheckoutType::None
|
| 240 |
245 |
|
}
|
| 241 |
246 |
|
|
| 242 |
|
- |
fn validate_amount(&self, _amount_cents: i32) -> Result<(), String> {
|
|
247 |
+ |
fn validate_amount(
|
|
248 |
+ |
&self,
|
|
249 |
+ |
_amount_cents: i32,
|
|
250 |
+ |
_currency: SettlementCurrency,
|
|
251 |
+ |
) -> Result<(), String> {
|
| 243 |
252 |
|
Ok(())
|
| 244 |
253 |
|
}
|
| 245 |
254 |
|
|
| 266 |
275 |
|
ctx.is_creator || ctx.has_purchased || ctx.has_active_subscription()
|
| 267 |
276 |
|
}
|
| 268 |
277 |
|
|
| 269 |
|
- |
fn price_display(&self) -> String {
|
| 270 |
|
- |
helpers::format_price(self.price_cents)
|
|
278 |
+ |
fn price_display(&self, currency: SettlementCurrency) -> String {
|
|
279 |
+ |
helpers::format_price(self.price_cents, currency)
|
| 271 |
280 |
|
}
|
| 272 |
281 |
|
|
| 273 |
282 |
|
fn price_cents(&self) -> i32 {
|
| 278 |
287 |
|
CheckoutType::OneTime
|
| 279 |
288 |
|
}
|
| 280 |
289 |
|
|
| 281 |
|
- |
fn validate_amount(&self, amount_cents: i32) -> Result<(), String> {
|
|
290 |
+ |
fn validate_amount(
|
|
291 |
+ |
&self,
|
|
292 |
+ |
amount_cents: i32,
|
|
293 |
+ |
currency: SettlementCurrency,
|
|
294 |
+ |
) -> Result<(), String> {
|
| 282 |
295 |
|
if amount_cents < self.price_cents {
|
| 283 |
296 |
|
Err(format!(
|
| 284 |
297 |
|
"Amount must be at least {}",
|
| 285 |
|
- |
helpers::format_price(self.price_cents)
|
|
298 |
+ |
helpers::format_price(self.price_cents, currency)
|
| 286 |
299 |
|
))
|
| 287 |
300 |
|
} else {
|
| 288 |
301 |
|
Ok(())
|
| 311 |
324 |
|
ctx.is_creator || ctx.has_purchased || ctx.has_active_subscription()
|
| 312 |
325 |
|
}
|
| 313 |
326 |
|
|
| 314 |
|
- |
fn price_display(&self) -> String {
|
|
327 |
+ |
fn price_display(&self, currency: SettlementCurrency) -> String {
|
| 315 |
328 |
|
match self.min_cents {
|
| 316 |
|
- |
Some(min) if min > 0 => format!("From {}", helpers::format_price(min)),
|
|
329 |
+ |
Some(min) if min > 0 => format!("From {}", helpers::format_price(min, currency)),
|
| 317 |
330 |
|
_ => "Pay what you want".to_string(),
|
| 318 |
331 |
|
}
|
| 319 |
332 |
|
}
|
| 330 |
343 |
|
CheckoutType::PayWhatYouWant
|
| 331 |
344 |
|
}
|
| 332 |
345 |
|
|
| 333 |
|
- |
fn validate_amount(&self, amount_cents: i32) -> Result<(), String> {
|
|
346 |
+ |
fn validate_amount(
|
|
347 |
+ |
&self,
|
|
348 |
+ |
amount_cents: i32,
|
|
349 |
+ |
currency: SettlementCurrency,
|
|
350 |
+ |
) -> Result<(), String> {
|
| 334 |
351 |
|
let min = self.min_cents.unwrap_or(0);
|
| 335 |
352 |
|
if amount_cents < min {
|
| 336 |
353 |
|
return Err(format!(
|
| 337 |
354 |
|
"Amount must be at least {}",
|
| 338 |
|
- |
crate::formatting::format_revenue(min as i64)
|
|
355 |
+ |
crate::formatting::format_revenue(min as i64, currency)
|
| 339 |
356 |
|
));
|
| 340 |
357 |
|
}
|
| 341 |
358 |
|
// Cap at $10,000 (same ceiling as tips) to prevent accidental mega-charges
|
| 366 |
383 |
|
ctx.is_creator || ctx.has_active_subscription()
|
| 367 |
384 |
|
}
|
| 368 |
385 |
|
|
| 369 |
|
- |
fn price_display(&self) -> String {
|
|
386 |
+ |
fn price_display(&self, _currency: SettlementCurrency) -> String {
|
|
387 |
+ |
// The tiers carry the prices; this label names the model, not an amount.
|
| 370 |
388 |
|
"Subscription".to_string()
|
| 371 |
389 |
|
}
|
| 372 |
390 |
|
|
| 378 |
396 |
|
CheckoutType::Subscription
|
| 379 |
397 |
|
}
|
| 380 |
398 |
|
|
| 381 |
|
- |
fn validate_amount(&self, _amount_cents: i32) -> Result<(), String> {
|
|
399 |
+ |
fn validate_amount(
|
|
400 |
+ |
&self,
|
|
401 |
+ |
_amount_cents: i32,
|
|
402 |
+ |
_currency: SettlementCurrency,
|
|
403 |
+ |
) -> Result<(), String> {
|
| 382 |
404 |
|
Err("Subscription items cannot be purchased directly".to_string())
|
| 383 |
405 |
|
}
|
| 384 |
406 |
|
|
| 468 |
490 |
|
|
| 469 |
491 |
|
#[test]
|
| 470 |
492 |
|
fn free_price_display() {
|
| 471 |
|
- |
assert_eq!(FreePricing.price_display(), "Free");
|
|
493 |
+ |
assert_eq!(FreePricing.price_display(SettlementCurrency::Usd), "Free");
|
| 472 |
494 |
|
}
|
| 473 |
495 |
|
|
| 474 |
496 |
|
#[test]
|
| 483 |
505 |
|
|
| 484 |
506 |
|
#[test]
|
| 485 |
507 |
|
fn free_validate_amount() {
|
| 486 |
|
- |
assert!(FreePricing.validate_amount(0).is_ok());
|
| 487 |
|
- |
assert!(FreePricing.validate_amount(100).is_ok());
|
|
508 |
+ |
assert!(
|
|
509 |
+ |
FreePricing
|
|
510 |
+ |
.validate_amount(0, SettlementCurrency::Usd)
|
|
511 |
+ |
.is_ok()
|
|
512 |
+ |
);
|
|
513 |
+ |
assert!(
|
|
514 |
+ |
FreePricing
|
|
515 |
+ |
.validate_amount(100, SettlementCurrency::Usd)
|
|
516 |
+ |
.is_ok()
|
|
517 |
+ |
);
|
| 488 |
518 |
|
}
|
| 489 |
519 |
|
|
| 490 |
520 |
|
#[test]
|
| 536 |
566 |
|
#[test]
|
| 537 |
567 |
|
fn fixed_price_display_whole() {
|
| 538 |
568 |
|
let p = FixedPricing { price_cents: 1000 };
|
| 539 |
|
- |
assert_eq!(p.price_display(), "$10");
|
|
569 |
+ |
assert_eq!(p.price_display(SettlementCurrency::Usd), "$10");
|
| 540 |
570 |
|
}
|
| 541 |
571 |
|
|
| 542 |
572 |
|
#[test]
|
| 543 |
573 |
|
fn fixed_price_display_cents() {
|
| 544 |
574 |
|
let p = FixedPricing { price_cents: 999 };
|
| 545 |
|
- |
assert_eq!(p.price_display(), "$9.99");
|
|
575 |
+ |
assert_eq!(p.price_display(SettlementCurrency::Usd), "$9.99");
|
| 546 |
576 |
|
}
|
| 547 |
577 |
|
|
| 548 |
578 |
|
#[test]
|
| 549 |
579 |
|
fn fixed_validate_amount_ok() {
|
| 550 |
580 |
|
let p = FixedPricing { price_cents: 999 };
|
| 551 |
|
- |
assert!(p.validate_amount(999).is_ok());
|
| 552 |
|
- |
assert!(p.validate_amount(1500).is_ok());
|
|
581 |
+ |
assert!(p.validate_amount(999, SettlementCurrency::Usd).is_ok());
|
|
582 |
+ |
assert!(p.validate_amount(1500, SettlementCurrency::Usd).is_ok());
|
| 553 |
583 |
|
}
|
| 554 |
584 |
|
|
| 555 |
585 |
|
#[test]
|
| 556 |
586 |
|
fn fixed_validate_amount_too_low() {
|
| 557 |
587 |
|
let p = FixedPricing { price_cents: 999 };
|
| 558 |
|
- |
assert!(p.validate_amount(500).is_err());
|
|
588 |
+ |
assert!(p.validate_amount(500, SettlementCurrency::Usd).is_err());
|
| 559 |
589 |
|
}
|
| 560 |
590 |
|
|
| 561 |
591 |
|
#[test]
|
| 613 |
643 |
|
let p = PwywPricing {
|
| 614 |
644 |
|
min_cents: Some(500),
|
| 615 |
645 |
|
};
|
| 616 |
|
- |
assert_eq!(p.price_display(), "From $5");
|
|
646 |
+ |
assert_eq!(p.price_display(SettlementCurrency::Usd), "From $5");
|
| 617 |
647 |
|
}
|
| 618 |
648 |
|
|
| 619 |
649 |
|
#[test]
|
| 620 |
650 |
|
fn pwyw_price_display_no_min() {
|
| 621 |
651 |
|
let p = PwywPricing { min_cents: None };
|
| 622 |
|
- |
assert_eq!(p.price_display(), "Pay what you want");
|
|
652 |
+ |
assert_eq!(
|
|
653 |
+ |
p.price_display(SettlementCurrency::Usd),
|
|
654 |
+ |
"Pay what you want"
|
|
655 |
+ |
);
|
| 623 |
656 |
|
}
|
| 624 |
657 |
|
|
| 625 |
658 |
|
#[test]
|
| 626 |
659 |
|
fn pwyw_price_display_zero_min() {
|
| 627 |
660 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 628 |
|
- |
assert_eq!(p.price_display(), "Pay what you want");
|
|
661 |
+ |
assert_eq!(
|
|
662 |
+ |
p.price_display(SettlementCurrency::Usd),
|
|
663 |
+ |
"Pay what you want"
|
|
664 |
+ |
);
|
| 629 |
665 |
|
}
|
| 630 |
666 |
|
|
| 631 |
667 |
|
#[test]
|
| 633 |
669 |
|
let p = PwywPricing {
|
| 634 |
670 |
|
min_cents: Some(500),
|
| 635 |
671 |
|
};
|
| 636 |
|
- |
assert!(p.validate_amount(500).is_ok());
|
| 637 |
|
- |
assert!(p.validate_amount(1000).is_ok());
|
|
672 |
+ |
assert!(p.validate_amount(500, SettlementCurrency::Usd).is_ok());
|
|
673 |
+ |
assert!(p.validate_amount(1000, SettlementCurrency::Usd).is_ok());
|
| 638 |
674 |
|
}
|
| 639 |
675 |
|
|
| 640 |
676 |
|
#[test]
|
| 642 |
678 |
|
let p = PwywPricing {
|
| 643 |
679 |
|
min_cents: Some(500),
|
| 644 |
680 |
|
};
|
| 645 |
|
- |
assert!(p.validate_amount(400).is_err());
|
|
681 |
+ |
assert!(p.validate_amount(400, SettlementCurrency::Usd).is_err());
|
| 646 |
682 |
|
}
|
| 647 |
683 |
|
|
| 648 |
684 |
|
#[test]
|
| 649 |
685 |
|
fn pwyw_validate_amount_zero_min() {
|
| 650 |
686 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 651 |
|
- |
assert!(p.validate_amount(0).is_ok());
|
|
687 |
+ |
assert!(p.validate_amount(0, SettlementCurrency::Usd).is_ok());
|
| 652 |
688 |
|
}
|
| 653 |
689 |
|
|
| 654 |
690 |
|
#[test]
|
| 722 |
758 |
|
|
| 723 |
759 |
|
#[test]
|
| 724 |
760 |
|
fn subscription_price_display() {
|
| 725 |
|
- |
assert_eq!(SubscriptionPricing.price_display(), "Subscription");
|
|
761 |
+ |
assert_eq!(
|
|
762 |
+ |
SubscriptionPricing.price_display(SettlementCurrency::Usd),
|
|
763 |
+ |
"Subscription"
|
|
764 |
+ |
);
|
| 726 |
765 |
|
}
|
| 727 |
766 |
|
|
| 728 |
767 |
|
#[test]
|
| 735 |
774 |
|
|
| 736 |
775 |
|
#[test]
|
| 737 |
776 |
|
fn subscription_validate_amount() {
|
| 738 |
|
- |
assert!(SubscriptionPricing.validate_amount(100).is_err());
|
|
777 |
+ |
assert!(
|
|
778 |
+ |
SubscriptionPricing
|
|
779 |
+ |
.validate_amount(100, SettlementCurrency::Usd)
|
|
780 |
+ |
.is_err()
|
|
781 |
+ |
);
|
| 739 |
782 |
|
}
|
| 740 |
783 |
|
|
| 741 |
784 |
|
#[test]
|
| 819 |
862 |
|
// Negative price_cents is semantically wrong but FixedPricing doesn't validate construction
|
| 820 |
863 |
|
let p = FixedPricing { price_cents: -100 };
|
| 821 |
864 |
|
// amount >= price_cents (-100), so 0 and -50 pass, but -200 fails
|
| 822 |
|
- |
assert!(p.validate_amount(0).is_ok());
|
| 823 |
|
- |
assert!(p.validate_amount(-50).is_ok());
|
| 824 |
|
- |
assert!(p.validate_amount(-200).is_err()); // -200 < -100
|
|
865 |
+ |
assert!(p.validate_amount(0, SettlementCurrency::Usd).is_ok());
|
|
866 |
+ |
assert!(p.validate_amount(-50, SettlementCurrency::Usd).is_ok());
|
|
867 |
+ |
assert!(p.validate_amount(-200, SettlementCurrency::Usd).is_err()); // -200 < -100
|
| 825 |
868 |
|
}
|
| 826 |
869 |
|
|
| 827 |
870 |
|
#[test]
|
| 828 |
871 |
|
fn pwyw_validate_amount_at_cap() {
|
| 829 |
872 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 830 |
|
- |
assert!(p.validate_amount(1_000_000).is_ok()); // exactly $10,000
|
| 831 |
|
- |
assert!(p.validate_amount(1_000_001).is_err()); // $10,000.01
|
|
873 |
+ |
assert!(
|
|
874 |
+ |
p.validate_amount(1_000_000, SettlementCurrency::Usd)
|
|
875 |
+ |
.is_ok()
|
|
876 |
+ |
); // exactly $10,000
|
|
877 |
+ |
assert!(
|
|
878 |
+ |
p.validate_amount(1_000_001, SettlementCurrency::Usd)
|
|
879 |
+ |
.is_err()
|
|
880 |
+ |
); // $10,000.01
|
| 832 |
881 |
|
}
|
| 833 |
882 |
|
|
| 834 |
883 |
|
#[test]
|
| 835 |
884 |
|
fn pwyw_validate_amount_negative() {
|
| 836 |
885 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 837 |
886 |
|
// Negative amount is below min (0), should fail
|
| 838 |
|
- |
assert!(p.validate_amount(-1).is_err());
|
|
887 |
+ |
assert!(p.validate_amount(-1, SettlementCurrency::Usd).is_err());
|
| 839 |
888 |
|
}
|
| 840 |
889 |
|
|
| 841 |
890 |
|
#[test]
|
| 845 |
894 |
|
min_cents: Some(-100),
|
| 846 |
895 |
|
};
|
| 847 |
896 |
|
// Negative amount still above negative min
|
| 848 |
|
- |
assert!(p.validate_amount(-50).is_ok());
|
|
897 |
+ |
assert!(p.validate_amount(-50, SettlementCurrency::Usd).is_ok());
|
| 849 |
898 |
|
}
|
| 850 |
899 |
|
|
| 851 |
900 |
|
#[test]
|
| 852 |
901 |
|
fn fixed_validate_amount_no_upper_cap() {
|
| 853 |
902 |
|
// FixedPricing has no $10k cap like PWYW does
|
| 854 |
903 |
|
let p = FixedPricing { price_cents: 100 };
|
| 855 |
|
- |
assert!(p.validate_amount(99_999_999).is_ok());
|
|
904 |
+ |
assert!(
|
|
905 |
+ |
p.validate_amount(99_999_999, SettlementCurrency::Usd)
|
|
906 |
+ |
.is_ok()
|
|
907 |
+ |
);
|
| 856 |
908 |
|
}
|
| 857 |
909 |
|
|
| 858 |
910 |
|
#[test]
|
| 897 |
949 |
|
fn adversarial_pwyw_max_i32_amount() {
|
| 898 |
950 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 899 |
951 |
|
// i32::MAX = 2,147,483,647 cents = ~$21.4M, should be rejected by $10k cap
|
| 900 |
|
- |
assert!(p.validate_amount(i32::MAX).is_err());
|
|
952 |
+ |
assert!(
|
|
953 |
+ |
p.validate_amount(i32::MAX, SettlementCurrency::Usd)
|
|
954 |
+ |
.is_err()
|
|
955 |
+ |
);
|
| 901 |
956 |
|
}
|
| 902 |
957 |
|
|
| 903 |
958 |
|
#[test]
|
| 904 |
959 |
|
fn adversarial_pwyw_min_i32_amount() {
|
| 905 |
960 |
|
let p = PwywPricing { min_cents: Some(0) };
|
| 906 |
|
- |
assert!(p.validate_amount(i32::MIN).is_err());
|
|
961 |
+ |
assert!(
|
|
962 |
+ |
p.validate_amount(i32::MIN, SettlementCurrency::Usd)
|
|
963 |
+ |
.is_err()
|
|
964 |
+ |
);
|
| 907 |
965 |
|
}
|
| 908 |
966 |
|
|
| 909 |
967 |
|
#[test]
|
| 912 |
970 |
|
price_cents: i32::MAX,
|
| 913 |
971 |
|
};
|
| 914 |
972 |
|
// validate_amount with exactly i32::MAX should pass
|
| 915 |
|
- |
assert!(p.validate_amount(i32::MAX).is_ok());
|
|
973 |
+ |
assert!(p.validate_amount(i32::MAX, SettlementCurrency::Usd).is_ok());
|
| 916 |
974 |
|
// Any amount below should fail
|
| 917 |
|
- |
assert!(p.validate_amount(i32::MAX - 1).is_err());
|
|
975 |
+ |
assert!(
|
|
976 |
+ |
p.validate_amount(i32::MAX - 1, SettlementCurrency::Usd)
|
|
977 |
+ |
.is_err()
|
|
978 |
+ |
);
|
| 918 |
979 |
|
}
|
| 919 |
980 |
|
|
| 920 |
981 |
|
#[test]
|
| 1050 |
1111 |
|
min_cents: Some(1_000_001),
|
| 1051 |
1112 |
|
};
|
| 1052 |
1113 |
|
// Any amount below min fails the min check
|
| 1053 |
|
- |
assert!(p.validate_amount(1_000_000).is_err());
|
|
1114 |
+ |
assert!(
|
|
1115 |
+ |
p.validate_amount(1_000_000, SettlementCurrency::Usd)
|
|
1116 |
+ |
.is_err()
|
|
1117 |
+ |
);
|
| 1054 |
1118 |
|
// Any amount at/above min fails the cap check
|
| 1055 |
|
- |
assert!(p.validate_amount(1_000_001).is_err());
|
|
1119 |
+ |
assert!(
|
|
1120 |
+ |
p.validate_amount(1_000_001, SettlementCurrency::Usd)
|
|
1121 |
+ |
.is_err()
|
|
1122 |
+ |
);
|
| 1056 |
1123 |
|
// Even i32::MAX fails
|
| 1057 |
|
- |
assert!(p.validate_amount(i32::MAX).is_err());
|
|
1124 |
+ |
assert!(
|
|
1125 |
+ |
p.validate_amount(i32::MAX, SettlementCurrency::Usd)
|
|
1126 |
+ |
.is_err()
|
|
1127 |
+ |
);
|
| 1058 |
1128 |
|
}
|
| 1059 |
1129 |
|
|
| 1060 |
1130 |
|
#[test]
|
| 1063 |
1133 |
|
let p = PwywPricing {
|
| 1064 |
1134 |
|
min_cents: Some(1_000_000),
|
| 1065 |
1135 |
|
};
|
| 1066 |
|
- |
assert!(p.validate_amount(1_000_000).is_ok());
|
| 1067 |
|
- |
assert!(p.validate_amount(999_999).is_err());
|
| 1068 |
|
- |
assert!(p.validate_amount(1_000_001).is_err());
|
|
1136 |
+ |
assert!(
|
|
1137 |
+ |
p.validate_amount(1_000_000, SettlementCurrency::Usd)
|
|
1138 |
+ |
.is_ok()
|
|
1139 |
+ |
);
|
|
1140 |
+ |
assert!(p.validate_amount(999_999, SettlementCurrency::Usd).is_err());
|
|
1141 |
+ |
assert!(
|
|
1142 |
+ |
p.validate_amount(1_000_001, SettlementCurrency::Usd)
|
|
1143 |
+ |
.is_err()
|
|
1144 |
+ |
);
|
| 1069 |
1145 |
|
}
|
| 1070 |
1146 |
|
|
| 1071 |
1147 |
|
#[test]
|
| 1072 |
1148 |
|
fn pwyw_none_min_allows_zero() {
|
| 1073 |
1149 |
|
// min_cents = None → unwrap_or(0) → amount >= 0 required
|
| 1074 |
1150 |
|
let p = PwywPricing { min_cents: None };
|
| 1075 |
|
- |
assert!(p.validate_amount(0).is_ok());
|
| 1076 |
|
- |
assert!(p.validate_amount(-1).is_err());
|
| 1077 |
|
- |
assert!(p.validate_amount(1_000_000).is_ok());
|
| 1078 |
|
- |
assert!(p.validate_amount(1_000_001).is_err());
|
|
1151 |
+ |
assert!(p.validate_amount(0, SettlementCurrency::Usd).is_ok());
|
|
1152 |
+ |
assert!(p.validate_amount(-1, SettlementCurrency::Usd).is_err());
|
|
1153 |
+ |
assert!(
|
|
1154 |
+ |
p.validate_amount(1_000_000, SettlementCurrency::Usd)
|
|
1155 |
+ |
.is_ok()
|
|
1156 |
+ |
);
|
|
1157 |
+ |
assert!(
|
|
1158 |
+ |
p.validate_amount(1_000_001, SettlementCurrency::Usd)
|
|
1159 |
+ |
.is_err()
|
|
1160 |
+ |
);
|
| 1079 |
1161 |
|
}
|
| 1080 |
1162 |
|
|
| 1081 |
1163 |
|
#[test]
|
| 1082 |
1164 |
|
fn fixed_validate_amount_at_exact_boundary() {
|
| 1083 |
1165 |
|
// Amount exactly equal to price should pass (not off-by-one)
|
| 1084 |
1166 |
|
let p = FixedPricing { price_cents: 1 };
|
| 1085 |
|
- |
assert!(p.validate_amount(1).is_ok());
|
| 1086 |
|
- |
assert!(p.validate_amount(0).is_err());
|
|
1167 |
+ |
assert!(p.validate_amount(1, SettlementCurrency::Usd).is_ok());
|
|
1168 |
+ |
assert!(p.validate_amount(0, SettlementCurrency::Usd).is_err());
|
| 1087 |
1169 |
|
}
|
| 1088 |
1170 |
|
|
| 1089 |
1171 |
|
#[test]
|
| 1151 |
1233 |
|
#[test]
|
| 1152 |
1234 |
|
fn prop_fixed_validate_amount_consistent(price in 0..=1_000_000i32, amount in -100_000..=2_000_000i32) {
|
| 1153 |
1235 |
|
let p = FixedPricing { price_cents: price };
|
| 1154 |
|
- |
let result = p.validate_amount(amount);
|
|
1236 |
+ |
let result = p.validate_amount(amount, SettlementCurrency::Usd);
|
| 1155 |
1237 |
|
if amount >= price {
|
| 1156 |
1238 |
|
proptest::prop_assert!(result.is_ok());
|
| 1157 |
1239 |
|
} else {
|
| 1162 |
1244 |
|
#[test]
|
| 1163 |
1245 |
|
fn prop_pwyw_validate_enforces_min_and_cap(min in 0..=1_100_000i32, amount in -1_000..=1_100_000i32) {
|
| 1164 |
1246 |
|
let p = PwywPricing { min_cents: Some(min) };
|
| 1165 |
|
- |
let result = p.validate_amount(amount);
|
|
1247 |
+ |
let result = p.validate_amount(amount, SettlementCurrency::Usd);
|
| 1166 |
1248 |
|
if amount >= min && amount <= 1_000_000 {
|
| 1167 |
1249 |
|
proptest::prop_assert!(result.is_ok());
|
| 1168 |
1250 |
|
} else {
|
| 1172 |
1254 |
|
|
| 1173 |
1255 |
|
#[test]
|
| 1174 |
1256 |
|
fn prop_subscription_never_direct_purchase(amount in proptest::num::i32::ANY) {
|
| 1175 |
|
- |
proptest::prop_assert!(SubscriptionPricing.validate_amount(amount).is_err());
|
|
1257 |
+ |
proptest::prop_assert!(SubscriptionPricing.validate_amount(amount, SettlementCurrency::Usd).is_err());
|
| 1176 |
1258 |
|
}
|
| 1177 |
1259 |
|
}
|
| 1178 |
1260 |
|
}
|