end and bill dates

This commit is contained in:
Brandon Thomas committed 2025-09-16 09:11:13 -04:00
1 parent 0f5ec3c83b
commit 92a0674776
6 files changed
+79 -12

No files matched your search

@@ -36,7 +36,12 @@ pub struct ActiveSubscriptionInfo {
pub subscription_token: UserSubscriptionToken,
pub product_slug: ArtcraftSubscriptionSlug,
pub namespace: PaymentsNamespace,
/// If the subscription is active, this is the next bill date
pub next_bill_at: Option<DateTime<Utc>>,
/// If the subscription is expired or is set to expire, this is the end date of the subscription.
pub subscription_end_at: Option<DateTime<Utc>>,
}
impl SerializeMarker for GetSubscriptionResponse {}
@@ -89,6 +94,7 @@ async fn get(
product_slug,
namespace: sub.namespace,
next_bill_at: sub.next_bill_at,
subscription_end_at: sub.subscription_end_at,
})
})
.flatten(),
@@ -34,6 +34,16 @@ pub struct UserSubscription {
/// When the current billing period ends (either auto-renew/rebill date or auto-cancel/cancellation date).
pub current_billing_period_end_at: DateTime<Utc>,
/// If the subscription is set to expire (perhaps in the future), this is the date it will expire.
/// TODO: Determine if this is set on expired, payment invalid, or canceled subscriptions where the date
/// is backdated ahead of `maybe_canceled_at`.
pub maybe_cancel_at: Option<DateTime<Utc>>,
/// This might be *before* the subscription is canceled, depending on stripe setup and
/// the cancellation algorithm used. Eg. a year-long subscription that ends next year
/// might be maybe_canceled_at a few days ago, but set to `maybe_cancel_at` next year.
pub maybe_canceled_at: Option<DateTime<Utc>>,
}
/// Technically, there may be more than one subscription record.
@@ -89,6 +99,8 @@ fn map_result(result: Result<Option<RawUserSubscription>, sqlx::Error>) -> Resul
subscription_start_at: record.subscription_start_at,
subscription_expires_at: record.subscription_expires_at,
current_billing_period_end_at: record.current_billing_period_end_at,
maybe_cancel_at: record.maybe_cancel_at,
maybe_canceled_at: record.maybe_canceled_at,
})),
Ok(None) => Ok(None),
Err(e) => Err(e.into()),
@@ -117,7 +129,9 @@ SELECT
maybe_stripe_invoice_is_paid,
subscription_start_at,
subscription_expires_at,
current_billing_period_end_at
current_billing_period_end_at,
maybe_cancel_at,
maybe_canceled_at
FROM user_subscriptions
@@ -157,4 +171,7 @@ struct RawUserSubscription {
subscription_expires_at: DateTime<Utc>,
current_billing_period_end_at: DateTime<Utc>,
maybe_cancel_at: Option<DateTime<Utc>>,
maybe_canceled_at: Option<DateTime<Utc>>,
}
@@ -81,16 +81,41 @@ pub async fn get_session_subscription_handler(
success: true,
active_subscription: maybe_subscription.map(|sub| {
let mut next_bill_date = None;
// TODO: Suppress if canceled.
next_bill_date = Some(sub.current_billing_period_end_at);
let mut subscription_end_date = None;
// cancel_at > canceled_at - [normal] subscription was terminated and expires (after the canceled_at date, perhaps in the future)
// cancel_at < canceled_at - [???] - ???
match (sub.maybe_cancel_at, sub.maybe_canceled_at) {
(Some(cancel_at), Some(canceled_at)) => {
// NB: `canceled_at` is when the user canceled the subscription, not necessarily when it expires.
// TODO: Not sure this is the correct logic.
subscription_end_date = Some(cancel_at);
},
(Some(cancel_at), None) => {
// Canceling at end of period.
subscription_end_date = Some(cancel_at);
},
(None, Some(canceled_at)) => {
// Canceled already.
subscription_end_date = Some(canceled_at);
},
(None, None) => {
// Active subscription.
next_bill_date = Some(sub.current_billing_period_end_at);
},
}
//if sub.maybe_cancel_at.is_none() && sub.maybe_canceled_at.is_none() {
// next_bill_date = Some(sub.current_billing_period_end_at);
//}
SubscriptionInfo {
namespace: sub.subscription_namespace,
product_slug: sub.subscription_product_slug,
subscription_token: sub.token,
next_bill_at: next_bill_date,
subscription_end_at: None,
subscription_end_at: subscription_end_date,
}
}),
}))
@@ -31,6 +31,9 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
const hasPaidPlan = subscriptionStore.hasPaidPlan();
const nextBillAt = subscriptionStore.subscriptionInfo?.nextBillAt?.toLocaleDateString();
const subscriptionEndAt = subscriptionStore.subscriptionInfo?.subscriptionEndAt?.toLocaleDateString();
useEffect(() => {
creditsStore.fetchFromServer();
subscriptionStore.fetchFromServer();
@@ -70,11 +73,22 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
{billingInfo.nextPayment.date}
</div>
*/}
<div className="flex items-center gap-2 text-white/50">
<FontAwesomeIcon icon={faInfoCircle} />
Next payment on{" "}
{subscriptionStore.subscriptionInfo?.nextBillAt?.toLocaleDateString()}
</div>
{subscriptionEndAt && (
<div className="flex items-center gap-2 text-white/50">
<FontAwesomeIcon icon={faInfoCircle} />
Subscription ends on{" "}
{subscriptionEndAt}
</div>
)}
{nextBillAt && (
<div className="flex items-center gap-2 text-white/50">
<FontAwesomeIcon icon={faInfoCircle} />
Next payment on{" "}
{nextBillAt}
</div>
)}
<hr className="border-white/10" />
@@ -24,8 +24,11 @@ interface SubscriptionInfo {
/// The namespace for the subscription ("artcraft" or "fakeyou")
namespace: string,
/// The next billing date for the subscription
/// The next billing date for the subscription (if it's active and not set to expire/cancel)
nextBillAt?: Date,
/// The end date for the subscription (if it's expired or set to expire/cancel)
subscriptionEndAt?: Date,
}
export const useSubscriptionState = create<SubscriptionState>()((set, get) => ({
@@ -47,6 +50,7 @@ export const useSubscriptionState = create<SubscriptionState>()((set, get) => ({
productSlug: data.payload.active_subscription.product_slug,
namespace: data.payload.active_subscription.namespace,
nextBillAt: data.payload.active_subscription.next_bill_at ? new Date(data.payload.active_subscription.next_bill_at) : undefined,
subscriptionEndAt: data.payload.active_subscription.subscription_end_at ? new Date(data.payload.active_subscription.subscription_end_at) : undefined,
};
}
console.log(">>> activeSubscription", activeSubscription);
@@ -14,6 +14,7 @@ export interface ActiveSubscriptionInfo {
product_slug: string,
namespace: string,
next_bill_at?: string,
subscription_end_at?: string,
}
export const ArtcraftGetSubscription = async () : Promise<ArtcraftGetSubscriptionSuccess> => {