Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contract/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub fn publish_fee_cleared(env: &Env) {
env.events()
.publish((Symbol::new(env, "fee_cleared"),), ());
}

pub fn publish_subscription_amount_updated(
env: &Env,
user: &Address,
Expand Down
86 changes: 21 additions & 65 deletions contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl FlowPay {
trial_period,
referrer,
);
let _ = subscription_metadata::set_metadata(&env, &user, label);
subscription_metadata::set_metadata(&env, &user, label);
}

/// Charges the next due recurring payment for `user`.
Expand Down Expand Up @@ -1365,67 +1365,16 @@ impl FlowPay {
}

fn extend_subscription_ttl(env: &Env, user: &Address) {
storage::extend_subscription_ttl(env, user);
env.storage().persistent().extend_ttl(
&DataKey::Subscription(user.clone()),
SUBSCRIPTION_TTL_LEDGERS,
SUBSCRIPTION_TTL_LEDGERS,
);
env.storage()
.instance()
.extend_ttl(SUBSCRIPTION_TTL_LEDGERS, SUBSCRIPTION_TTL_LEDGERS);
}

/// Shared implementation for `pay_per_use` and `pay_per_use_to`. `recipient`
/// is `None` for `pay_per_use` (defaults to `sub.merchant`, matching its
/// existing behavior exactly) and `Some(addr)` for `pay_per_use_to`.
fn pay_per_use_inner(env: &Env, user: Address, amount: i128, recipient: Option<Address>) {
ensure_contract_not_paused(env);
user.require_auth();

if amount <= 0 {
env.panic_with_error(ContractError::AmountMustBePositive);
}
if amount > MAX_AMOUNT {
env.panic_with_error(ContractError::AmountExceedsMaximum);
}

let key = DataKey::Subscription(user.clone());

let sub: Subscription = env
.storage()
.persistent()
.get(&key)
.unwrap_or_else(|| env.panic_with_error(ContractError::NoSubscriptionFound));

if !sub.active {
env.panic_with_error(ContractError::SubscriptionInactive);
}
if sub.paused {
env.panic_with_error(ContractError::SubscriptionPaused);
}

// Only the explicit `pay_per_use_to` path re-validates the whitelist;
// `pay_per_use` (recipient == None) keeps its existing behavior of not
// re-checking a merchant that was already whitelisted at subscribe time.
let is_pay_per_use_to = recipient.is_some();
let recipient = recipient.unwrap_or_else(|| sub.merchant.clone());

if is_pay_per_use_to
&& whitelist::is_whitelist_enabled(env)
&& !whitelist::is_whitelisted(env, &recipient)
{
env.panic_with_error(ContractError::MerchantNotWhitelisted);
}

spending_limit::enforce_limit(env, &user, amount);

let fee_amount = fee::transfer_pay_per_use(env, &user, &sub.token, amount, &recipient);
let net_amount = amount - fee_amount;

check_and_update_global_volume(env, amount);
merchant_stats::increment_revenue_with_daily(env, &recipient, net_amount);
spending_limit::record_spend(env, &user, amount);
extend_subscription_ttl(env, &user);

events::publish_pay_per_use(env, &user, &recipient, amount);
}

fn subscribe_inner(
env: &Env,
user: Address,
Expand All @@ -1446,7 +1395,6 @@ fn subscribe_inner(
env.panic_with_error(ContractError::MerchantFrozen);
}

// Prevent new subscriptions when contract is paused
let paused = env
.storage()
.instance()
Expand All @@ -1457,7 +1405,16 @@ fn subscribe_inner(
}

validation::require_valid_amount(env, amount);
if interval == 0 {
env.panic_with_error(ContractError::IntervalMustBePositive);
}

use soroban_sdk::xdr::ToXdr;
if token.clone().to_xdr(env).get(7) == Some(0) {
env.panic_with_error(ContractError::InvalidTokenAddress);
}

validation::check_allowance(env, &user, &token, amount);
if interval < 60 {
env.panic_with_error(ContractError::IntervalTooShort);
}
Expand All @@ -1466,19 +1423,18 @@ fn subscribe_inner(
env.panic_with_error(ContractError::IntervalTooShort);
}

use soroban_sdk::xdr::ToXdr;
if token.clone().to_xdr(env).get(7) == Some(0) {
env.panic_with_error(ContractError::InvalidTokenAddress);
let token_client = token::Client::new(env, &token);
let allowance = token_client.allowance(&user, &env.current_contract_address());
if allowance < amount {
env.panic_with_error(ContractError::InsufficientAllowance);
}

validation::check_allowance(env, &user, &token, amount);

let now = env.ledger().timestamp();
let trial_duration = trial_period.unwrap_or(0);
let last_charged = now + trial_duration;

let existing = storage::get_subscription(env, &user);
let should_increment = existing.as_ref().is_none_or(|s| !s.active);
let should_increment = existing.as_ref().map_or(true, |s| !s.active);

if let Some(ref existing_sub) = existing {
if existing_sub.active && existing_sub.merchant != merchant {
Expand Down Expand Up @@ -1514,7 +1470,7 @@ fn subscribe_inner(
events::publish_subscribed(env, &user, &sub);
}

pub(crate) fn check_and_update_global_volume(env: &Env, amount: i128) {
fn check_and_update_global_volume(env: &Env, amount: i128) {
let now = env.ledger().timestamp();
let mut window: GlobalVolumeWindow = env
.storage()
Expand Down
Loading
Loading