Skip to content

Commit 8690689

Browse files
committed
feat(contract): add loyalty module with points, tiers, streaks, redemption
- New loyalty.rs module: accumulate_points, redeem_points, streak tracking, tier calculation, referral bonuses, points expiry - Integrated accumulate_points into charge_subscription flow - 11 public contract functions for loyalty management - Points capped at 10M to prevent inflation - Streak bonus awarded every 10 consecutive charges - Auto-expiry check on balance reads - Redemption converts points to discount (100 pts = 1%, max 50%)
1 parent 4656e01 commit 8690689

2 files changed

Lines changed: 530 additions & 0 deletions

File tree

contracts/subscription/src/lib.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod gas_profiler;
33
mod gas_storage;
44
mod gas_optimization;
5+
mod loyalty;
56
use soroban_sdk::{token, Address, Env, IntoVal, String, TryFromVal, Val, Vec};
67
use subtrackr_types::{
78
Interval, Invoice, Plan, StorageKey, Subscription, SubscriptionStatus, TimeRange,
@@ -686,6 +687,15 @@ impl SubTrackrSubscription {
686687
(sub.subscriber.clone(), plan.price, 100_000u64, now),
687688
);
688689

690+
// Accumulate loyalty points after successful charge.
691+
loyalty::accumulate_points(
692+
&env,
693+
&storage,
694+
&sub.subscriber,
695+
plan.price,
696+
now,
697+
);
698+
689699
if let Some(invoice_addr) = invoice_contract(&env, &storage) {
690700
let period = TimeRange {
691701
start: sub.last_charged_at,
@@ -1045,6 +1055,140 @@ impl SubTrackrSubscription {
10451055
revenue::get_revenue_schedule(&env, &storage, subscription_id)
10461056
}
10471057

1058+
// ── Loyalty & Rewards API ──
1059+
1060+
pub fn initialize_loyalty(
1061+
env: Env,
1062+
proxy: Address,
1063+
storage: Address,
1064+
config: subtrackr_types::LoyaltyConfig,
1065+
) {
1066+
proxy.require_auth();
1067+
get_admin(&env, &storage).require_auth();
1068+
loyalty::set_loyalty_config(&env, &storage, &config);
1069+
}
1070+
1071+
pub fn update_loyalty_config(
1072+
env: Env,
1073+
proxy: Address,
1074+
storage: Address,
1075+
config: subtrackr_types::LoyaltyConfig,
1076+
) {
1077+
proxy.require_auth();
1078+
get_admin(&env, &storage).require_auth();
1079+
loyalty::set_loyalty_config(&env, &storage, &config);
1080+
}
1081+
1082+
pub fn get_loyalty_config(
1083+
env: Env,
1084+
proxy: Address,
1085+
storage: Address,
1086+
) -> Option<subtrackr_types::LoyaltyConfig> {
1087+
proxy.require_auth();
1088+
loyalty::get_loyalty_config(&env, &storage)
1089+
}
1090+
1091+
pub fn get_points(
1092+
env: Env,
1093+
proxy: Address,
1094+
storage: Address,
1095+
subscriber: Address,
1096+
) -> u64 {
1097+
proxy.require_auth();
1098+
loyalty::get_eligible_points(&env, &storage, &subscriber)
1099+
}
1100+
1101+
pub fn get_lifetime_points(
1102+
env: Env,
1103+
proxy: Address,
1104+
storage: Address,
1105+
subscriber: Address,
1106+
) -> u64 {
1107+
proxy.require_auth();
1108+
loyalty::get_lifetime_points(&env, &storage, &subscriber)
1109+
}
1110+
1111+
pub fn get_streak(
1112+
env: Env,
1113+
proxy: Address,
1114+
storage: Address,
1115+
subscriber: Address,
1116+
) -> u64 {
1117+
proxy.require_auth();
1118+
loyalty::get_streak(&env, &storage, &subscriber)
1119+
}
1120+
1121+
pub fn get_loyalty_status(
1122+
env: Env,
1123+
proxy: Address,
1124+
storage: Address,
1125+
subscriber: Address,
1126+
) -> (u64, u64, u64, i128, Option<subtrackr_types::LoyaltyTierConfig>) {
1127+
proxy.require_auth();
1128+
let points = loyalty::get_eligible_points(&env, &storage, &subscriber);
1129+
let lifetime = loyalty::get_lifetime_points(&env, &storage, &subscriber);
1130+
let streak = loyalty::get_streak(&env, &storage, &subscriber);
1131+
let spent = loyalty::get_total_spent(&env, &storage, &subscriber);
1132+
let tier = loyalty::get_current_tier(&env, &storage, &subscriber);
1133+
(points, lifetime, streak, spent, tier)
1134+
}
1135+
1136+
pub fn redeem_loyalty_points(
1137+
env: Env,
1138+
proxy: Address,
1139+
storage: Address,
1140+
subscriber: Address,
1141+
points: u64,
1142+
charge_amount: i128,
1143+
) -> i128 {
1144+
proxy.require_auth();
1145+
subscriber.require_auth();
1146+
let now = env.ledger().timestamp();
1147+
loyalty::redeem_points(&env, &storage, &subscriber, points, charge_amount, now)
1148+
}
1149+
1150+
pub fn earn_referral_bonus(
1151+
env: Env,
1152+
proxy: Address,
1153+
storage: Address,
1154+
referrer: Address,
1155+
) {
1156+
proxy.require_auth();
1157+
let now = env.ledger().timestamp();
1158+
loyalty::earn_referral_bonus(&env, &storage, &referrer, now);
1159+
}
1160+
1161+
pub fn expire_points(
1162+
env: Env,
1163+
proxy: Address,
1164+
storage: Address,
1165+
subscriber: Address,
1166+
) {
1167+
proxy.require_auth();
1168+
get_admin(&env, &storage).require_auth();
1169+
loyalty::expire_points(&env, &storage, &subscriber);
1170+
}
1171+
1172+
pub fn get_point_transactions(
1173+
env: Env,
1174+
proxy: Address,
1175+
storage: Address,
1176+
subscriber: Address,
1177+
) -> Vec<subtrackr_types::PointTransaction> {
1178+
proxy.require_auth();
1179+
loyalty::get_point_transactions(&env, &storage, &subscriber)
1180+
}
1181+
1182+
pub fn get_redemption(
1183+
env: Env,
1184+
proxy: Address,
1185+
storage: Address,
1186+
redemption_id: u64,
1187+
) -> Option<subtrackr_types::RewardsRedemption> {
1188+
proxy.require_auth();
1189+
loyalty::get_redemption(&env, &storage, redemption_id)
1190+
}
1191+
10481192
// ── Quota & Usage API ──
10491193

10501194
pub fn set_plan_quotas(

0 commit comments

Comments
 (0)