Skip to content

Conversation

@aecsocket
Copy link
Contributor

Supercedes #4549

@aecsocket aecsocket marked this pull request as draft October 30, 2025 10:25
@aecsocket aecsocket marked this pull request as ready for review October 31, 2025 11:23
Copy link
Contributor

@fetchfern fetchfern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good direction, would just address the comments and, overall, to strengthen the approach:

  • 1<->1<->1 relationship between charges, users_subscriptions_affiliations_payouts, payouts_values (one charge/payout value per row in usap, maybe UNIQUE (payout_value_id)?)
  • Handle refunded charges:
    • Separate step which goes through refund charges which have a parent charge which have a usap row which have a payout_value that's not yet available and delete the usap row/payout_value
    • In your current query, JOIN the charge with its refund charges and handle the refund amount (don't forget refund charges have a many-to-1 relationship with their parent charge, since multiple refunds can be issued for 1 charge)

continue;
}

let affiliate_cut = net * revenue_split;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

charges.net includes tax amount, you'll probably want to take taxes off the cut (so (charges.net - charges.tax_amount) * revenue_split)

Comment on lines +74 to +81
let now = Utc::now();
let start: DateTime<Utc> = DateTime::from_naive_utc_and_offset(
(now - Duration::days(1))
.date_naive()
.and_hms_nano_opt(0, 0, 0, 0)
.unwrap_or_default(),
Utc,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The start time (created value of the payouts_values row) should be based on the charge's last attempt time rather than the current time

payout_info.amount += affiliate_cut;
payout_info
.charge_subscription_ids
.push((row.charge_id, row.subscription_id));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make more sense to have a single payouts_values row for each unique charge? Then it's easier to handle refunds (which aren't handled here—what happens if the charge gets processed here, is added to payouts_values but is later refunded before it is available to withdraw for the affiliate?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth between batching the payout value rows and inserting them individually. The payouts queue code batches project views/downloads, but I suppose since each payout value is linked to a single concrete thing here (a payout) it's better to do separately here

INNER JOIN users_subscriptions_affiliations usa
ON c.subscription_id = usa.subscription_id
AND c.subscription_id IS NOT NULL
AND usa.deactivated_at IS NULL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be checking if c.last_attempt < usa.deactivated_at

Copy link
Contributor Author

@aecsocket aecsocket Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with

INNER JOIN users_subscriptions_affiliations usa
  ON c.subscription_id = usa.subscription_id
  AND c.subscription_id IS NOT NULL
  AND (usa.deactivated_at IS NULL OR c.last_attempt < usa.deactivated_at)

pub struct DBUsersSubscriptionsAffiliations {
pub subscription_id: DBUserSubscriptionId,
pub affiliate_code: DBAffiliateCodeId,
pub deactivated_at: Option<DateTime<Utc>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be setting deactivated_at when a subscription is cancelled in the subscription cancelled route handler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done in routes/internal/billing.rs - see calls to DBUsersSubscriptionsAffiliations::deactivate. But I'm not sure if I've covered all of the places where this is set. I'd like a review to see if there are any paths I've missed for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh i think i was looking at an outdated diff rip

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should deactivate it here as well

async fn unprovision_subscriptions(
(unprovisioning subscriptions that were cancelled/has failed payments)

@fetchfern fetchfern added the backend Involves work from the backend team label Nov 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Involves work from the backend team

Development

Successfully merging this pull request may close these issues.

3 participants