Skip to content

Commit 8a283f7

Browse files
authored
feat(loyalty): implement loyalty and rewards program for subscribers (#387) (#464)
* feat(types): add loyalty types and StorageKey variants - New types: LoyaltyConfig, LoyaltyTierConfig, PointTransaction, PointTxType, RewardsRedemption - 11 new StorageKey variants for on-chain loyalty state: LoyaltyConfig, LoyaltyPoints, LifetimePoints, TotalSpent, MemberSince, Streak, LastChargeAt, PointsExpiration, PointTxCount, PointTx, RedemptionCount, Redemption * 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%) * feat(frontend): integrate loyalty store with contract, add streaks/badges/refs to screen - New types: PointTxType, LoyaltyConfig, StreakInfo, ReferralInfo - loyaltyStore: contract integration stubs, streak tracking, referral bonuses, gamification triggers - LoyaltyDashboardScreen: streak card, referral share, badges modal, points expiry UI, tier comparison table - gamificationService: 8 new loyalty achievements and badges (point milestones, streak milestones, referral milestones) - Gamification triggers wired into accumulatePoints and earnReferralBonus flows
1 parent ec44ae8 commit 8a283f7

8 files changed

Lines changed: 1161 additions & 11 deletions

File tree

contracts/subscription/src/lib.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,15 @@ impl SubTrackrSubscription {
10321032
(sub.subscriber.clone(), charge_price, 100_000u64, now),
10331033
);
10341034

1035+
// Accumulate loyalty points after successful charge.
1036+
loyalty::accumulate_points(
1037+
&env,
1038+
&storage,
1039+
&sub.subscriber,
1040+
plan.price,
1041+
now,
1042+
);
1043+
10351044
if let Some(invoice_addr) = invoice_contract(&env, &storage) {
10361045
let period = TimeRange {
10371046
start: sub.last_charged_at,
@@ -1394,6 +1403,140 @@ impl SubTrackrSubscription {
13941403
revenue::get_revenue_schedule(&env, &storage, subscription_id)
13951404
}
13961405

1406+
// ── Loyalty & Rewards API ──
1407+
1408+
pub fn initialize_loyalty(
1409+
env: Env,
1410+
proxy: Address,
1411+
storage: Address,
1412+
config: subtrackr_types::LoyaltyConfig,
1413+
) {
1414+
proxy.require_auth();
1415+
get_admin(&env, &storage).require_auth();
1416+
loyalty::set_loyalty_config(&env, &storage, &config);
1417+
}
1418+
1419+
pub fn update_loyalty_config(
1420+
env: Env,
1421+
proxy: Address,
1422+
storage: Address,
1423+
config: subtrackr_types::LoyaltyConfig,
1424+
) {
1425+
proxy.require_auth();
1426+
get_admin(&env, &storage).require_auth();
1427+
loyalty::set_loyalty_config(&env, &storage, &config);
1428+
}
1429+
1430+
pub fn get_loyalty_config(
1431+
env: Env,
1432+
proxy: Address,
1433+
storage: Address,
1434+
) -> Option<subtrackr_types::LoyaltyConfig> {
1435+
proxy.require_auth();
1436+
loyalty::get_loyalty_config(&env, &storage)
1437+
}
1438+
1439+
pub fn get_points(
1440+
env: Env,
1441+
proxy: Address,
1442+
storage: Address,
1443+
subscriber: Address,
1444+
) -> u64 {
1445+
proxy.require_auth();
1446+
loyalty::get_eligible_points(&env, &storage, &subscriber)
1447+
}
1448+
1449+
pub fn get_lifetime_points(
1450+
env: Env,
1451+
proxy: Address,
1452+
storage: Address,
1453+
subscriber: Address,
1454+
) -> u64 {
1455+
proxy.require_auth();
1456+
loyalty::get_lifetime_points(&env, &storage, &subscriber)
1457+
}
1458+
1459+
pub fn get_streak(
1460+
env: Env,
1461+
proxy: Address,
1462+
storage: Address,
1463+
subscriber: Address,
1464+
) -> u64 {
1465+
proxy.require_auth();
1466+
loyalty::get_streak(&env, &storage, &subscriber)
1467+
}
1468+
1469+
pub fn get_loyalty_status(
1470+
env: Env,
1471+
proxy: Address,
1472+
storage: Address,
1473+
subscriber: Address,
1474+
) -> (u64, u64, u64, i128, Option<subtrackr_types::LoyaltyTierConfig>) {
1475+
proxy.require_auth();
1476+
let points = loyalty::get_eligible_points(&env, &storage, &subscriber);
1477+
let lifetime = loyalty::get_lifetime_points(&env, &storage, &subscriber);
1478+
let streak = loyalty::get_streak(&env, &storage, &subscriber);
1479+
let spent = loyalty::get_total_spent(&env, &storage, &subscriber);
1480+
let tier = loyalty::get_current_tier(&env, &storage, &subscriber);
1481+
(points, lifetime, streak, spent, tier)
1482+
}
1483+
1484+
pub fn redeem_loyalty_points(
1485+
env: Env,
1486+
proxy: Address,
1487+
storage: Address,
1488+
subscriber: Address,
1489+
points: u64,
1490+
charge_amount: i128,
1491+
) -> i128 {
1492+
proxy.require_auth();
1493+
subscriber.require_auth();
1494+
let now = env.ledger().timestamp();
1495+
loyalty::redeem_points(&env, &storage, &subscriber, points, charge_amount, now)
1496+
}
1497+
1498+
pub fn earn_referral_bonus(
1499+
env: Env,
1500+
proxy: Address,
1501+
storage: Address,
1502+
referrer: Address,
1503+
) {
1504+
proxy.require_auth();
1505+
let now = env.ledger().timestamp();
1506+
loyalty::earn_referral_bonus(&env, &storage, &referrer, now);
1507+
}
1508+
1509+
pub fn expire_points(
1510+
env: Env,
1511+
proxy: Address,
1512+
storage: Address,
1513+
subscriber: Address,
1514+
) {
1515+
proxy.require_auth();
1516+
get_admin(&env, &storage).require_auth();
1517+
loyalty::expire_points(&env, &storage, &subscriber);
1518+
}
1519+
1520+
pub fn get_point_transactions(
1521+
env: Env,
1522+
proxy: Address,
1523+
storage: Address,
1524+
subscriber: Address,
1525+
) -> Vec<subtrackr_types::PointTransaction> {
1526+
proxy.require_auth();
1527+
loyalty::get_point_transactions(&env, &storage, &subscriber)
1528+
}
1529+
1530+
pub fn get_redemption(
1531+
env: Env,
1532+
proxy: Address,
1533+
storage: Address,
1534+
redemption_id: u64,
1535+
) -> Option<subtrackr_types::RewardsRedemption> {
1536+
proxy.require_auth();
1537+
loyalty::get_redemption(&env, &storage, redemption_id)
1538+
}
1539+
13971540
// ── Quota & Usage API ──
13981541

13991542
pub fn set_plan_quotas(

0 commit comments

Comments
 (0)