billing anchor

This commit is contained in:
Brandon Thomas committed 2025-09-12 01:00:04 -04:00
1 parent 437317438e
commit b9de0bf456
9 files changed
+34 -5

No files matched your search

@@ -59,6 +59,10 @@ CREATE TABLE user_subscriptions (
-- Which day of the month, or month of the year, etc. to anchor the subscription to.
-- See docs on the subscription object.
-- Stripe:
-- "The reference point that aligns future billing cycle dates. It sets the day
-- of week for week intervals, the day of month for month and year intervals, and
-- the month of year for year intervals. The timestamp is in UTC format."
maybe_stripe_billing_cycle_anchor TIMESTAMP DEFAULT NULL,
maybe_stripe_is_production BOOLEAN DEFAULT NULL,
@@ -39,6 +39,10 @@ pub struct UpsertUserSubscription<'a> {
/// This may predate Stripe's subscription object `created` field due to backdating.
pub subscription_start_at: NaiveDateTime,
// Which day of the month, month of the year, etc. to anchor the subscription against.
// See the Stripe docs for more.
pub maybe_stripe_billing_cycle_anchor: Option<NaiveDateTime>,
// Billing periods for the subscription...
pub current_billing_period_start_at: NaiveDateTime,
@@ -116,6 +120,8 @@ SET
maybe_stripe_recurring_interval = ?,
maybe_stripe_subscription_status = ?,
maybe_stripe_is_production = ?,
maybe_stripe_billing_cycle_anchor = ?,
subscription_start_at = ?,
current_billing_period_start_at = ?,
@@ -135,6 +141,8 @@ ON DUPLICATE KEY UPDATE
maybe_stripe_recurring_interval = ?,
maybe_stripe_subscription_status = ?,
maybe_stripe_billing_cycle_anchor = ?,
current_billing_period_start_at = ?,
current_billing_period_end_at = ?,
@@ -159,6 +167,8 @@ ON DUPLICATE KEY UPDATE
self.maybe_stripe_recurring_interval.as_deref(),
self.maybe_stripe_subscription_status.as_deref(),
self.maybe_stripe_is_production,
self.maybe_stripe_billing_cycle_anchor,
self.subscription_start_at,
self.current_billing_period_start_at,
@@ -176,6 +186,8 @@ ON DUPLICATE KEY UPDATE
self.maybe_stripe_recurring_interval.as_deref(),
self.maybe_stripe_subscription_status.as_deref(),
self.maybe_stripe_billing_cycle_anchor,
self.current_billing_period_start_at,
self.current_billing_period_end_at,
@@ -84,6 +84,7 @@ pub async fn customer_subscription_created_handler(
maybe_stripe_subscription_status: Some(summary.stripe_subscription_status),
maybe_stripe_is_production: Some(summary.stripe_is_production),
subscription_start_at: summary.subscription_start_date,
maybe_stripe_billing_cycle_anchor: None,
current_billing_period_start_at: summary.current_billing_period_start,
current_billing_period_end_at: summary.current_billing_period_end,
subscription_expires_at: calculate_subscription_end_date(&summary),
@@ -85,6 +85,7 @@ pub async fn customer_subscription_deleted_handler(
maybe_stripe_subscription_status: Some(summary.stripe_subscription_status),
maybe_stripe_is_production: Some(summary.stripe_is_production),
subscription_start_at: summary.subscription_start_date,
maybe_stripe_billing_cycle_anchor: None,
current_billing_period_start_at: summary.current_billing_period_start,
current_billing_period_end_at: summary.current_billing_period_end,
subscription_expires_at: calculate_subscription_end_date(&summary),
@@ -85,6 +85,7 @@ pub async fn customer_subscription_updated_handler(
maybe_stripe_subscription_status: Some(summary.stripe_subscription_status),
maybe_stripe_is_production: Some(summary.stripe_is_production),
subscription_start_at: summary.subscription_start_date,
maybe_stripe_billing_cycle_anchor: None,
current_billing_period_start_at: summary.current_billing_period_start,
current_billing_period_end_at: summary.current_billing_period_end,
subscription_expires_at: calculate_subscription_end_date(&summary),
@@ -59,6 +59,10 @@ pub struct UpsertableSubscriptionDetails {
pub stripe_recurring_interval : StripeRecurringInterval,
// Which day of the month / month of the year to anchor the subscription against.
// See the Stripe docs.
pub stripe_billing_cycle_anchor: NaiveDateTime,
pub stripe_is_production: bool,
/// When the subscription was "created" in Stripe (including any backdating)
@@ -66,6 +66,7 @@ pub async fn upsert_subscription_details(
maybe_stripe_is_production: Some(details.stripe_is_production),
// Timing data
maybe_stripe_billing_cycle_anchor: Some(details.stripe_billing_cycle_anchor),
subscription_start_at: details.subscription_start_at,
current_billing_period_start_at: details.current_billing_period_start_at,
current_billing_period_end_at: details.current_billing_period_end_at,
@@ -60,6 +60,7 @@ pub fn extract_subscription_details(
let calculated_end_date = calculate_subscription_end_date(&summary);
let subscription_details = UpsertableSubscriptionDetails {
// Unique Stripe foreign key
stripe_customer_id: summary.stripe_customer_id,
@@ -77,6 +78,7 @@ pub fn extract_subscription_details(
stripe_subscription_status: summary.stripe_subscription_status,
// Timing data
stripe_billing_cycle_anchor: summary.stripe_billing_cycle_anchor,
stripe_recurring_interval: summary.subscription_interval,
calculated_subscription_expires_at: calculated_end_date,
subscription_start_at: summary.subscription_start_date,
@@ -1,4 +1,3 @@
use crate::configs::stripe_artcraft_metadata_keys::STRIPE_ARTCRAFT_METADATA_USER_TOKEN;
use crate::utils::enum_conversion::recurring_interval_to_reusable_type::recurring_interval_to_reusable_type;
use crate::utils::enum_conversion::subscription_status_to_reusable_type::subscription_status_to_reusable_type;
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
@@ -7,11 +6,9 @@ use crate::utils::metadata::get_metadata_user_token::get_metadata_user_token;
use anyhow::anyhow;
use chrono::NaiveDateTime;
use errors::AnyhowResult;
use log::error;
use reusable_types::stripe::stripe_recurring_interval::StripeRecurringInterval;
use reusable_types::stripe::stripe_subscription_status::StripeSubscriptionStatus;
use stripe_shared::{Subscription, SubscriptionStatus};
use stripe_types::Expandable;
use tokens::tokens::users::UserToken;
#[derive(Clone, Debug)]
@@ -39,6 +36,10 @@ pub struct SubscriptionSummary {
pub subscription_is_active: bool,
pub subscription_interval: StripeRecurringInterval,
// Which day of the month / month of the year to anchor the subscription against.
// See the Stripe docs.
pub stripe_billing_cycle_anchor: NaiveDateTime,
/// When the subscription was "created" in Stripe (including any backdating)
pub subscription_start_date: NaiveDateTime,
@@ -55,11 +56,10 @@ pub struct SubscriptionSummary {
/// This should be unit testable against raw webhook JSON.
pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResult<SubscriptionSummary> {
let subscription_id = subscription.id.to_string();
// NB: Our internal user token.
let maybe_user_token = get_metadata_user_token(&subscription.metadata);
error!("SUBSCRIPTION EVENT USER TOKEN: {:?}", maybe_user_token);
if subscription.items.data.len() != 1 {
return Err(anyhow!("Too many items in subscription {} : {}",
subscription_id, subscription.items.data.len()));
@@ -83,6 +83,8 @@ pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResu
let period_start = NaiveDateTime::from_timestamp(item.current_period_start, 0);
let period_end = NaiveDateTime::from_timestamp(item.current_period_end, 0);
let stripe_billing_cycle_anchor = NaiveDateTime::from_timestamp(subscription.billing_cycle_anchor, 0);
let maybe_cancel_at = subscription.cancel_at.map(|t| NaiveDateTime::from_timestamp(t, 0));
let maybe_canceled_at = subscription.canceled_at.map(|t| NaiveDateTime::from_timestamp(t, 0));
@@ -100,6 +102,7 @@ pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResu
subscription_start_date: start_date,
current_billing_period_start: period_start,
current_billing_period_end: period_end,
stripe_billing_cycle_anchor,
maybe_cancel_at,
maybe_canceled_at,
})