Twin: synonymdev/bitkit-android#1413
What happened?
Right after Swipe To Cancel, a subscription reads as expired while the period already paid for is still running. STATUS reads "Expired", the timing cell reads "EXPIRED " with the end of that paid period, and the list row's subtitle reads "Expired". Nothing says it was canceled.
Bitkit Android showed exactly this on 25 Sep: "Expired" and "EXPIRED November 25" on both sides, while the paid period ran until 25 Nov. The iOS code maps it the same way: on master, a canceled subscription with a paid period that ends on Oct 1 renders STATUS "Expired", the timing cell "EXPIRED October 1" and the row subtitle "Expired" (checked on a simulator with a canceled fixture subscription).
Where, on master (c5a2a05):
Bitkit/Services/PaykitSubscription.swift:489 isActive(at:) requires .activeRecurring, and :509-510 isExpired(at:) counts .canceled as expired.
Bitkit/Views/Subscriptions/SubscriptionsView.swift maps every inactive subscription to "Expired":
:1052-1056 statusLabel(at:): return isActive(at: now) ? t("subscriptions__active") : t("subscriptions__expired")
:517-519 timingTitle(_:): guard subscription.isActive(at: now) else { return t("subscriptions__expired") }
:509-512 renewalText(_:) with :263-264 subscriptionEndDate: the timing value is the end of the last paid period.
:1076-1077 rowSubtitle(at:): a canceled subscription with no end date reads "Expired".
Expected behavior
Per the Figma v3 Subscription Details Renew frame (48063:282720: "a subscription has been canceled but has not expired yet") and the Overview list (48063:282432):
- A canceled subscription whose paid period has not ended looks like an active one with an end date: STATUS "Active", row subtitle "Expires ", listed under ACTIVE, timing cell "EXPIRES ".
- When the paid period ends it is listed under EXPIRED with the timing cell "EXPIRED ".
- A canceled subscription with no end date has no timing cell.
- The footer offers More Info and no Cancel.
- Active subscriptions keep "Renews" or "Expires".
Steps to Reproduce
- Subscribe to a monthly subscription and pay the first period.
- Open it and Swipe To Cancel.
- See STATUS "Expired" and "EXPIRED " while that date is still ahead; the list row reads "Expired".
Logs / Screenshots / Recordings
The code quotes above. Android on 25 Sep, right after Swipe To Cancel: "Expired" and "EXPIRED November 25".
iOS on master, canceled fixture subscription:
Bitkit Version
master c5a2a05
Device / OS
iPhone 17 simulator, iOS 26.5, with a canceled fixture subscription; not checked with a real Swipe To Cancel on a device.
Reproducibility
Always
Additional context
Android has the same defect and gets the same fix in its own PR.
Proposal:
- Treat a canceled subscription with a paid period still ahead like an active one with an end date, with one shared predicate for status, row subtitle, section, timing cell and the refresh timers.
- Test the status, subtitle, section grouping, timing title and timer date for a canceled subscription before and after the end of its paid period.
Twin: synonymdev/bitkit-android#1413
What happened?
Right after Swipe To Cancel, a subscription reads as expired while the period already paid for is still running. STATUS reads "Expired", the timing cell reads "EXPIRED " with the end of that paid period, and the list row's subtitle reads "Expired". Nothing says it was canceled.
Bitkit Android showed exactly this on 25 Sep: "Expired" and "EXPIRED November 25" on both sides, while the paid period ran until 25 Nov. The iOS code maps it the same way: on
master, a canceled subscription with a paid period that ends on Oct 1 renders STATUS "Expired", the timing cell "EXPIRED October 1" and the row subtitle "Expired" (checked on a simulator with a canceled fixture subscription).Where, on
master(c5a2a05):Bitkit/Services/PaykitSubscription.swift:489isActive(at:)requires.activeRecurring, and:509-510isExpired(at:)counts.canceledas expired.Bitkit/Views/Subscriptions/SubscriptionsView.swiftmaps every inactive subscription to "Expired"::1052-1056statusLabel(at:):return isActive(at: now) ? t("subscriptions__active") : t("subscriptions__expired"):517-519timingTitle(_:):guard subscription.isActive(at: now) else { return t("subscriptions__expired") }:509-512renewalText(_:)with:263-264subscriptionEndDate: the timing value is the end of the last paid period.:1076-1077rowSubtitle(at:): a canceled subscription with no end date reads "Expired".Expected behavior
Per the Figma v3 Subscription Details Renew frame (
48063:282720: "a subscription has been canceled but has not expired yet") and the Overview list (48063:282432):Steps to Reproduce
Logs / Screenshots / Recordings
The code quotes above. Android on 25 Sep, right after Swipe To Cancel: "Expired" and "EXPIRED November 25".
iOS on
master, canceled fixture subscription:Bitkit Version
masterc5a2a05Device / OS
iPhone 17 simulator, iOS 26.5, with a canceled fixture subscription; not checked with a real Swipe To Cancel on a device.
Reproducibility
Always
Additional context
Android has the same defect and gets the same fix in its own PR.
Proposal: