Skip to content

fix: keep canceled subscriptions active until they expire - #1391

Open
ovitrif wants to merge 4 commits into
masterfrom
fix/subscription-canceled-status
Open

ovitrif wants to merge 4 commits into
masterfrom
fix/subscription-canceled-status

Conversation

@ovitrif

@ovitrif ovitrif commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1413
Twin: synonymdev/bitkit-ios#846

Description

  • Fixes a canceled subscription reading "Expired" while its paid period is still running: it now reads like an active one with an end date, STATUS "Active", list subtitle "Expires " and timing cell "EXPIRES ".
  • Lists a canceled subscription under ACTIVE until its paid period ends, so it counts toward the ACTIVE count and the monthly cost like an active one; it then moves to EXPIRED, dimmed, with timing "EXPIRED ".
  • Drives status, row subtitle, section, timing and the refresh timers from one predicate (active, or canceled with its last paid period still ahead), so they cannot disagree. A canceled subscription ends at its last paid period even when a fixed end date is later.
  • Refreshes the list and an open detail screen at the moment the paid period ends, so they flip without being reopened.
  • Hides the timing cell for a canceled subscription that has no end date.
  • Applies this only to subscriptions you pay: a canceled subscription you created stays in CREATED and reads as expired, as before.
  • Leaves the detail footer as is: More Info only, no Cancel for a canceled subscription.

Out of Scope

  • SubscriptionsScreen.kt: the Renew button on the detail footer, tracked in Subscriptions: unbuilt controls and the swipe colour rule #1276.
  • PaykitSubscription.kt: lifecycle rules (isActive, isExpired, canCancel) are unchanged.
  • SubscriptionsScreen.kt: an expired subscription with a fixed end date still reads "Expires " in the list, as before.

Design

Subscription Details Renew is the state for a canceled subscription that has not expired yet, and the Subscriptions overview lists it under ACTIVE as "Expires ". The frames show STATUS "Active" and no "Canceled" label.

Preview

Screenshots use a local-only fixture that seeds canceled subscriptions in memory (not part of this PR); no real Swipe To Cancel was run. On the same fixture an open detail of a canceled subscription switched from STATUS "Active" and "EXPIRES" to "Expired" and "EXPIRED" at the end of its paid period without leaving the screen, and its row moved from ACTIVE to EXPIRED, with the ACTIVE count and monthly cost dropping accordingly.

Canceled, paid period not ended: list Canceled, paid period not ended: detail Expired: list Expired: detail

QA Notes

Journeys

  • updated cancel-and-delete.xml — after Swipe To Cancel the subscription stays in the ACTIVE section with "Expires ", and its detail shows STATUS "Active", "EXPIRES " and no Cancel. The added steps were not run end to end: the journey needs two Pubky identities on regtest

Manual Tests

N/A

Automated Checks

  • updated SubscriptionsScreenTest.kt — status, row subtitle, ACTIVE/EXPIRED grouping, monthly cost, timing and the end-of-period refresh for a canceled subscription before and after its paid period ends

@ovitrif
ovitrif force-pushed the fix/subscription-canceled-status branch from 972d4f5 to c392424 Compare October 1, 2026 19:50
@ovitrif ovitrif removed this from the 2.7.0 milestone Oct 1, 2026
@ovitrif ovitrif changed the title fix: show canceled subscriptions as canceled fix: keep canceled subscriptions active until they expire Oct 1, 2026
@ovitrif
ovitrif marked this pull request as ready for review October 1, 2026 22:07
@ovitrif
ovitrif requested review from a team, coreyphillips and pwltr and removed request for a team October 1, 2026 22:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-01T22:11:03.013809Z c392424 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Changes how canceled subscriptions display and behave.

This should not merge until canceled fixed-end subscriptions stop appearing active beyond their last paid period.

Findings

  1. P1 Canceled subscription stays active too long ▶

Summary

This PR keeps canceled subscriptions in ACTIVE through their paid period and schedules list and detail refreshes at expiration.

  • It aligns status, timing, section placement, and monthly cost around a shared predicate.
  • The predicate can instead use a later fixed plan end when one exists, extending ACTIVE beyond the paid period.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Canceled subscription] --> B[expiryDate]
  B --> C{Future date?}
  C -->|Yes| D[ACTIVE status, section and cost]
  C -->|No| E[EXPIRED status and section]
  F[Fixed plan end] --> B
  G[Last paid-period end] --> B
Loading

Reviews (1) · Last reviewed commit: "fix: keep canceled subscriptions active ..."

Comment thread app/src/main/java/to/bitkit/ui/screens/subscriptions/SubscriptionsScreen.kt Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c392424fe8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread journeys/subscriptions/cancel-and-delete.xml Outdated
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from fa6fb4c (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@ovitrif

ovitrif commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 5e05970: a canceled subscription now stops running at its last paid period even when it has a later fixed end date (status, subtitle, section, monthly cost, timing and the refresh timer all use that date), with a test for it, and the journey's last step now reads "Navigate back" so it matches the iOS twin. Answers the two bot threads above.

@ovitrif

ovitrif commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed ae6525c: the paid-through treatment now applies only to subscriptions the user pays. A canceled subscription the user created stays in CREATED and reads as expired as before (status, timing, dimming), covered by a new test that failed on the previous head. It also never moved sections on Android, because ACTIVE and EXPIRED already filter on the payer role. Same change as the iOS twin.

@coreyphillips

Copy link
Copy Markdown
Contributor

Two independent reviews, nothing blocking a merge.

worth doing, does not block

  • Ended canceled row shows the fixed end date while detail shows the paid-through date (app/src/main/java/to/bitkit/ui/screens/subscriptions/SubscriptionsScreen.kt:1070). For a subscription you pay that was canceled and has a fixed recurrence.endsAt later than its last paid period, the list row and the detail screen show different dates once the paid period ends. Before that moment the row reads "Expires Feb 1", which matches the detail. At Feb 1 the row moves to EXPIRED. rowSubtitleSpec then falls through to the else branch (SubscriptionsScreen.kt:1070), which uses recurrence.endsAt, so the row now reads "Expires June 1". The detail uses renewalText -> expiryDate() -> canceledPaidThrough(), so it reads "EXPIRED February 1". The row's date jumps later at the moment the subscription expires, and the description's claim that the two "cannot disagree" does not hold for this case. Before this PR, both sides used endsAt and agreed. Confirmed by tracing the fixture from the test canceled subscription stops running at its last paid period even with a later end date through rowSubtitleSpec(paidThrough). That test checks statusRes and expiryDate but not the row subtitle. This is non-blocking because the payer's own Cancel is hidden when endsAt is set (canCancel requires endsAt == null), so the state is only reachable if the counterparty cancels. A fix is to use canceledPaidThrough() ?: recurrence.endsAt in the else branch, or to call expiryDate() there for canceled subscriptions.

coreyphillips
coreyphillips previously approved these changes Oct 2, 2026
@ovitrif

ovitrif commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

@coreyphillips Pushed fa6fb4c for your note on the ended canceled row:

the list row and the detail screen show different dates once the paid period ends

The row now uses the last paid period's end for a canceled subscription you pay, so it reads "Expires February 1" like the detail's "EXPIRED February 1" instead of jumping to the later end date. The test for that case now asserts the row subtitle before and after the paid period ends, and it failed on the previous head. A canceled subscription without a fixed end date still reads "Expired" in the row.

@ovitrif
ovitrif requested a review from coreyphillips October 2, 2026 09:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: a canceled subscription reads expired while its paid period runs

2 participants