From ab55c8670419eb0818dcea5b98cc160818fb5c1f Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:09:35 +0100 Subject: [PATCH 1/6] Correct how GMTU payments are identified in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README claimed payments were identified by Stripe charge metadata `id = "join-gmtu"` — a pre-Stripe Action Network hangover. The actual code in StripePaymentHistory.php matches subscription line items' product IDs against configured membership plan product IDs read from the `ck_join_flow_membership_plan_*` wp_options rows. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63eabbc..03818b0 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Membership standing is classified by counting **completed calendar months** sinc Additional rules: -- **Only GMTU payments count.** Payments are identified by Stripe charge metadata (`id = "join-gmtu"`). Other charges on the same Stripe customer are ignored. +- **Only GMTU payments count.** Payments are identified by the Stripe **product ID** on a subscription's line items: only subscriptions whose product ID matches a configured GMTU membership plan are counted. Configured product IDs are discovered by reading every `wp_options` row with the `ck_join_flow_membership_plan_` prefix (written by the parent plugin when plans are saved) and collecting each plan's `stripe_product_id`. Unrelated subscriptions on the same Stripe customer are ignored. - **Failed and refunded payments do not count** as paid months. - **Lapsed is permanent.** Once a member reaches Lapsed status, a later payment does not automatically reinstate them. They must rejoin via the join form. This state is stored persistently in the WordPress database (see below). - **New member exception.** If someone makes their very first successful GMTU payment in the current month, they are treated as Good standing immediately. From 9f52a322eb3f8ef660ceb9054ea38247bca7df78 Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:09:43 +0100 Subject: [PATCH 2/6] Correct the Stripe API reference in the lapse-hook description The README said the payment history came from the Stripe Charges API. StripePaymentHistory.php actually uses the Customers, Subscriptions, and Invoices APIs; there is no Charges call anywhere in the codebase. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03818b0..1791311 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Additional rules: Called by the parent plugin when a Stripe payment event signals that a member should be lapsed. This plugin: -1. Fetches the member's GMTU payment history from the Stripe Charges API. +1. Fetches the member's GMTU payment history by querying the Stripe Customers, Subscriptions, and Invoices APIs: find all Stripe customers for the email, list their subscriptions, filter to GMTU membership subscriptions (see above), and collect the `paid_at` timestamps of each subscription's paid invoices. 2. Classifies their standing using the rules above. 3. Returns `true` (allow lapse) only if the member is classified as **Lapsed** (7+ missed months). Records the lapsed flag. 4. Returns `false` (suppress lapse) for Good standing, Early arrears, or Lapsing -- the parent plugin is acting more aggressively than GMTU rules require. From fb26db24f325c1c2b516dabc94c2cc03ffbcc240 Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:09:54 +0100 Subject: [PATCH 3/6] Correct the paid-payment criterion in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README said "failed and refunded payments do not count". Failed payments never produce a paid invoice so they're correctly excluded, but refunded invoices keep their `paid` status in Stripe — the code does not detect refunds. Replace with an accurate description of the `status=paid` filter and note the refund caveat. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1791311..395da76 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Membership standing is classified by counting **completed calendar months** sinc Additional rules: - **Only GMTU payments count.** Payments are identified by the Stripe **product ID** on a subscription's line items: only subscriptions whose product ID matches a configured GMTU membership plan are counted. Configured product IDs are discovered by reading every `wp_options` row with the `ck_join_flow_membership_plan_` prefix (written by the parent plugin when plans are saved) and collecting each plan's `stripe_product_id`. Unrelated subscriptions on the same Stripe customer are ignored. -- **Failed and refunded payments do not count** as paid months. +- **Only paid invoices count.** The fetcher asks Stripe for invoices with `status=paid` and records each invoice's `status_transitions.paid_at` timestamp. Draft, open, void, and uncollectible invoices are skipped. (A later refund does not reset an invoice's `paid` status, so a refunded payment's month will still count — out-of-band refunds are rare enough for GMTU that this is acceptable.) - **Lapsed is permanent.** Once a member reaches Lapsed status, a later payment does not automatically reinstate them. They must rejoin via the join form. This state is stored persistently in the WordPress database (see below). - **New member exception.** If someone makes their very first successful GMTU payment in the current month, they are treated as Good standing immediately. From 05204f3dd2ece379463b6a73a865676c08f19c17 Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:10:04 +0100 Subject: [PATCH 4/6] Add StripePaymentHistory.php to Structure listing The Structure block in the README was missing the file that actually talks to Stripe, leaving the lapsing subsystem's description of "fetches payment history" with no entry pointing at the fetcher. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 395da76..8287500 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ src/ Notifications.php # Registers success notification hooks MembershipStanding.php # Pure GMTU standing classifier (no I/O, fully unit-tested) LapsedStore.php # Persists lapsed flag in wp_options - LapsingOverride.php # Hooks into parent lapsing filters using the above two + StripePaymentHistory.php # Fetches paid-invoice months from Stripe (Customers + Subscriptions + Invoices) + LapsingOverride.php # Hooks into parent lapsing filters using the above three ``` ## Configuration From 0a194c9d5d65433267224d1c4c248d7c93c5e688 Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:10:16 +0100 Subject: [PATCH 5/6] Document the provider=stripe gating on lapsing overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LapsingOverride.php returns the incoming decision unchanged if the context's `provider` is anything other than 'stripe'. The README described only the Stripe path and did not record that non-Stripe providers fall through untouched — worth making explicit so readers don't assume the override applies universally. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8287500..a27f6eb 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ Additional rules: ### How the override hooks work +Both override filters inspect the `provider` value on the context array passed by the parent plugin and only run their custom logic when `provider === 'stripe'`. Any non-Stripe provider returns the incoming decision unchanged, leaving the parent plugin's default behaviour in place. + **`ck_join_flow_should_lapse_member`** Called by the parent plugin when a Stripe payment event signals that a member should be lapsed. This plugin: From a0318a8b60ceb3d3306ec2f1e9087a0566a6102e Mon Sep 17 00:00:00 2001 From: Alex Worrad-Andrews Date: Sun, 19 Apr 2026 11:15:23 +0100 Subject: [PATCH 6/6] Complete the pre-handle-join row in README hook lifecycle The hook table said the filter only injects `$data["branch"]`, but BranchAssignment.php also writes `$data["customFields"]["branch"]` and ensures a `$data["customFieldsConfig"]` entry for branch. The join-gmtu.php docblock already described both writes; the README table was the only place the full behaviour was understated. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a27f6eb..c05b787 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The parent plugin fires hooks at each stage of member registration and membershi |---|------|------|------------| | 1 | `ck_join_flow_postcode_validation` (filter) | `PostcodeValidation.php` | Check outcode against branch map; return error if out of area | | 2 | `ck_join_flow_step_response` (filter) | `PostcodeValidation.php` | Second-line validation on form step submission | -| 3 | `ck_join_flow_pre_handle_join` (filter) | `BranchAssignment.php` | Look up postcode outcode, find branch, inject into `$data["branch"]` | +| 3 | `ck_join_flow_pre_handle_join` (filter) | `BranchAssignment.php` | Look up postcode outcode, find branch, inject into `$data["branch"]`, `$data["customFields"]["branch"]`, and `$data["customFieldsConfig"]` | | 4 | `ck_join_flow_add_tags` (filter) | `Tagging.php` | Append branch name to tags sent to external services | | 5 | `ck_join_flow_success` (action, priority 5) | `LapsingOverride.php` | Clear sticky-lapsed flag when a member explicitly rejoins | | 6 | `ck_join_flow_success` (action, priority 10) | `Notifications.php` | Send admin notification email |