Skip to content

Seat subscriptions, counter-offer reject, child upgrades - #862

Merged
fati-Onchain merged 2 commits into
Facil-Pay:mainfrom
Diyaq:handsoff/issues-753-813-819-829-188cd1
Oct 23, 2026
Merged

fati-Onchain merged 2 commits into
Facil-Pay:mainfrom
Diyaq:handsoff/issues-753-813-819-829-188cd1

Conversation

@Diyaq

@Diyaq Diyaq commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Closes #753, closes #813, closes #819, closes #829.

#753: Seat-based subscriptions with prorated quantity changes (payment)

  • New SubscriptionSeats { quantity: u32, unit_amount: i128 }, stored under SubscriptionKey::Seats(id). The billed Subscription::amount is always unit_amount * quantity.
  • Migration: the seat record lives under its own key and is not a new field on Subscription, so stored subscriptions still decode. A subscription with no seat record is read as quantity = 1, unit_amount = amount, so no migration call is needed.
  • New update_subscription_quantity(customer, subscription_id, new_quantity):
    • new_quantity == 0 is rejected with the new SubscriptionError::InvalidQuantity (328). The TryFrom<soroban_sdk::Error> range now includes 328.
    • Proration follows the set_subscription_proration flag and uses the same formula as resume_subscription: (new_amount - old_amount) * (next_payment_at - now) / interval. Nothing is prorated while proration is off or during a trial.
    • An increase is pulled from the customer immediately, with the merchant pause and spend-limit checks. A decrease is stored as a seat credit (SubscriptionKey::SeatCredit), and execute_recurring_payment deducts it from the next charges. This applies on both the normal path and the dunning path.
    • Emits SubscriptionQuantityChanged.
  • New getters get_subscription_seats and get_subscription_seat_credit.
  • When a price proposal is applied to a subscription that has a seat record, the new price is treated as the per-seat price.
  • The README documents the functions, parameters, proration rules, errors and the event.

#813: Reject a counter-offer and escalate (refund)

  • reject_counter_offer(customer, refund_id) discards the pending offer. The refund stays Requested at its original amount, which puts it back in review.
  • reject_counter_offer_escalate(customer, refund_id, fee_token, fee_amount) -> u64 covers the optional escalation in a single call. It rejects the offer, moves the refund to PendingAppeal (rejected_by = the offering merchant), then opens an arbitration case. The name is shortened because Soroban caps function names at 32 characters. escalate_to_arbitration's body moved into a private escalate_to_arbitration_internal so it can be reused without a second require_auth. Any failure reverts the whole call.
  • Expired offers count as rejected. Either function can be called on them, and the event reports expired: true.
  • Emits CounterOfferRejected { refund_id, customer, requested_amount, offered_amount, expired, escalated }.
  • The Counter-Offers section and the events table in the README are updated.

#819: Coordinated upgrade of child contracts (admin)

  • upgrade_children(admin, payment_hash, escrow_hash, refund_hash) works in three steps: it pauses each child, calls upgrade on each, then unpauses them. The coordinator calls the children as itself (current_contract_address()), so it must be an admin on each child. The README describes the setup.
  • A failing child call panics, which reverts the whole invocation. No child is left upgraded or paused.
  • A child that was already globally paused (for example by an emergency pause) is left paused.
  • Emits one ChildContractUpgraded per child.
  • The new AdminAction::UpgradeChildren(..) lets multisig deployments (threshold > 1) do the same through propose/approve/execute. The direct call follows the same single-admin rule as upgrade.
  • Restored the ChildContractUpdated event struct. A merge had dropped it while the set_*_contract setters still emitted it, so the admin crate did not compile.
  • The README has a new "Coordinated Child Upgrades" section.

#829: Document scheduled policy changes, auto-approve ceiling and cooldown config (refund, docs only)

The refund README now documents:

  • Scheduled policy changes: schedule_refund_policy_change, cancel_scheduled_refund_policy, get_scheduled_refund_policy and activate_scheduled_refund_policy. The section covers parameters, auth, the lazy-activation rules, a timeline example, errors (69/70/71/12) and events.
  • The auto-approve ceiling: set_auto_approve_below_ceiling and get_auto_approve_below_ceiling, and how the ceiling limits a merchant's set_auto_approve_below (min(threshold, ceiling) at request time, error 32).
  • Cooldown config: set_refund_cooldown_config and get_refund_cooldown_config, the RefundCooldownConfig fields, RefundCooldownActive (33) and RefundCooldownEnforced.

The examples use the real signatures from lib.rs. The policy events were added to the events table.

Notes

Per the task rules, nothing was built, formatted or tested locally. cargo fmt, cargo clippy and cargo test should be run in CI.

Closes #753
Closes #813
Closes #819
Closes #829

@fati-Onchain
fati-Onchain merged commit c563c76 into Facil-Pay:main Oct 23, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants