{"record":{"id":"cf9ac06a8b2c1b32","repo":"TryGhost/Ghost","slug":"expected-redeemed-offer-member-emailaddress-to","errorCode":null,"errorMessage":"Expected redeemed offer member ${emailAddress} to have a subscription","messagePattern":"Expected redeemed offer member (.+?) to have a subscription","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"e2e/helpers/playwright/flows/offers.ts","lineNumber":98,"sourceCode":"    name: string;\n    subscription: MemberSubscription;\n}> {\n    const membersService = new MembersService(page.request);\n    const {emailAddress, name} = await completeOfferSignupViaPortal(page, stripe, opts);\n\n    const homePage = new HomePage(page);\n    await homePage.goto();\n    await homePage.openAccountPortal();\n\n    const accountPage = new PortalAccountPage(page);\n    await accountPage.waitForPortalToOpen();\n    await accountPage.emailText(emailAddress).waitFor({state: 'visible'});\n\n    const member = await membersService.getByEmailWithSubscriptions(emailAddress);\n    const subscription = member.subscriptions[0];\n\n    if (!subscription) {\n        throw new Error(`Expected redeemed offer member ${emailAddress} to have a subscription`);\n    }\n\n    return {\n        accountPage,\n        emailAddress,\n        name,\n        subscription\n    };\n}\n","sourceCodeStart":80,"sourceCodeEnd":108,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/helpers/playwright/flows/offers.ts#L80-L108","documentation":"Thrown in `redeemOfferViaPortal` after fetching the member via `membersService.getByEmailWithSubscriptions(emailAddress)`. It asserts `member.subscriptions[0]` exists — the offer redemption is expected to create an active subscription. A missing subscription means the checkout completed in the UI but Ghost's backend never recorded a subscription.","triggerScenarios":"Offer redemption navigated to the success URL, but `member.subscriptions` is empty or undefined when re-fetched from the members API. Typically a Stripe webhook (`invoice.paid` / `customer.subscription.created`) did not arrive or was not processed before the lookup.","commonSituations":"Stripe webhook delivery is slow or dropped in the test environment; the member was created but the subscription is still pending in Stripe; race between `page.goto(successUrl)` completing and backend subscription persistence; webhook signing secret mismatch.","solutions":["Add a short retry/poll loop around `getByEmailWithSubscriptions` to wait for webhook processing to persist the subscription.","Confirm the Stripe webhook client in the test environment is delivering `customer.subscription.created` events.","Verify the success URL navigation actually triggered Ghost's subscription-creation endpoint.","Check that the offer is configured to grant a paid tier subscription, not just a discount."],"exampleFix":"// before\nconst member = await membersService.getByEmailWithSubscriptions(emailAddress);\nconst subscription = member.subscriptions[0];\nif (!subscription) {\n    throw new Error(`Expected redeemed offer member ${emailAddress} to have a subscription`);\n}\n\n// after — wait for webhook to land\nlet member = await membersService.getByEmailWithSubscriptions(emailAddress);\nfor (let i = 0; i < 20 && !member.subscriptions?.length; i++) {\n    await new Promise(r => setTimeout(r, 500));\n    member = await membersService.getByEmailWithSubscriptions(emailAddress);\n}\nconst subscription = member.subscriptions?.[0];\nif (!subscription) {\n    throw new Error(`Expected redeemed offer member ${emailAddress} to have a subscription`);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function memberHasSubscription(m: unknown): m is {subscriptions: unknown[]} {\n    return !!m && Array.isArray((m as any).subscriptions) && (m as any).subscriptions.length > 0;\n}","tryCatchPattern":"let member = await membersService.getByEmailWithSubscriptions(emailAddress);\nfor (let i = 0; i < 20 && !memberHasSubscription(member); i++) {\n    await new Promise(r => setTimeout(r, 500));\n    member = await membersService.getByEmailWithSubscriptions(emailAddress);\n}\nif (!memberHasSubscription(member)) {\n    throw new Error(`Expected redeemed offer member ${emailAddress} to have a subscription`);\n}","preventionTips":["Poll the members API for the subscription to absorb webhook processing latency.","Confirm the Stripe webhook client delivers `customer.subscription.created` events.","Verify the offer grants a paid-tier subscription before redeeming."],"tags":["stripe","e2e","subscriptions","webhooks","race-condition"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}