no issue
Using a designated source for members being added within the `importer` context will better describe the source in our members event table. It's more consistent with the usage of the other possible sources.
no issue
When importing members, the members-importer isn't aware of the context and therefore falls back to use `member` as a source in members event table. This makes it impossible to determine imported members from others.
- Added an `context` property to the options in the members-importer constructor
- Checked for `importer` context when creating member events and assigned the source `admin` to it
refs https://github.com/TryGhost/Team/issues/1478
- When creating or editing a member, we'll add the newsletter ids to the subscribe events
- When susbcribing to multiple newsletters, we'll create multiple subscribe events
- When removing newsletters from a member, we'll create one or more subscribe events
- Tests in https://github.com/TryGhost/Ghost/pull/14579
refs https://github.com/TryGhost/Team/issues/1520
- Instead of doing the matching of the offers and subscriptions by looking at the offer redemptions, we can now look at the offer_id from subscriptions.
- This also fixes an issue where we don't attach the offer object to subscriptions in the members' browse method
- Updated browse behaviour to match the read behaviour of members (product relation needs to get loaded because it is missing in member.products if the subscription is expired).
Tests in https://github.com/TryGhost/Ghost/pull/14515
refs https://github.com/TryGhost/Ghost/pull/14488
**Bug:**
When creating a new offer, sometimes no discount object is created in Stripe (only in MySQL). This issue causes tests to fail in https://github.com/TryGhost/Ghost/pull/14488
**Cause:**
When creating an offer with the OffersAPI, we create a new transaction to save the offer and check for uniqueness. Inside that transaction, we dispatch an OfferCreatedEvent event before the transaction is committed to the database.
When listening for this event in the `members-payments` package, we create the discount object in Stripe and save the stripe_coupon_id to the newly created offer. But because (in most cases) the offer doesn't yet exist in the database, that listener fails without warning and exits early.
**Solution:**
Creating a discount in Stripe should only happen after we succesfully committed the offer to the database. If we don't wait for the commit, the offer often (by chance) won't exist in the database, so the coupon won't be created inside Stripe. The solution is to wait for the transaction to succeed/be committed before dispatching the event.
refs https://github.com/TryGhost/Team/issues/1519
- Removed offer metadata again from offer (added in one of the previous commits)
- Added `getByStripeCouponId` method in offer repository (required to find an offer based on the stripe_coupon_id)
- Match discounts from Stripe based on the stripe_coupon_id instead of metadata
refs https://github.com/TryGhost/Team/issues/1519
- Added offer repository dependency to member repository (offerAPI didn't work because it creates a new transaction that resulted in a deadlock during tests)
- Store the offer id from the Stripe subscription metadata in the subscription (only if the discount is still active)
- Also added the offer id to the metadata for a Stripe coupon, this will make adding and removing coupons a bit more foolproof
- Prefer the usage of the offer metadata from a coupon if it is present
- When no discount is applied to a subscription, it always sets the offer id to null, even when the metadata still contains the offer
- The offer_id remains stored when a subscription is canceled/expired
refs https://github.com/TryGhost/Team/issues/1469
Previously, members were subscribed to all available newsletters by default when added. This change updates the default newsletters subscription for member to take into account newsletter preferences for auto opt-in as well as visibility.
refs: https://github.com/TryGhost/Toolbox/issues/293
Things needed to create this:
* MemberSubscriptionEvent now has an import source
* Importer now creates events with this type
* Verification trigger logic changed to use 30 day window of imports
* Updated member mrr_delta calculation to use stored mrr
refs https://github.com/TryGhost/Team/issues/1456
Simplifies the calculation of MRR deltas, which will make it easier to update MRR to include offers and cancellations in the future.
* Adjusted MRR and MRR delta calculation to consider "forever" duration offers
refs https://github.com/TryGhost/Team/issues/1451
Uses the discount information from Stripe to calculate the MRR (this was the easiest way to include it + also supports manually created discounts from users)
refs https://github.com/TryGhost/Team/issues/1490
With multiple newsletters, paid members can choose their newsletter preference at the time of signup via Portal. This change handles the newsletter preference via stripe checkout metadata and stores it against the member on completing signup.
refs https://github.com/TryGhost/Team/issues/1490
With multiple newsletters, free members can choose their newsletter subscription preference while signing up.
This change -
- includes newsletters data in magic link token creation
- attaches newsletter data to new members created via magic link
refs https://github.com/TryGhost/Team/issues/1456
- Sets the `mrr` column correctly when updating a subscription
- We can use this value in the future to have an improved calculation of mrr_delta, but we first need to have the migration in place
- Updated missing cancelled -> canceled
refs https://github.com/TryGhost/Team/issues/1469
When a new member signs up on the site, by default they should get subscribed to all newsletters. A new member can get added via different ways (stripe webhook, admin add, free signup), so this change updates the base member repository which gets used irrespective of how member is added to ensure default newsletters are always included for new member.
If a member already has a custom newsletters list attached, we don't change anything.
no issue
- When Ghost is running in a test environment, it is configured with an invalid Stripe key that looks like `sk_test***`. In this case the migrations try runnig creating request to Stripe, which fail. The failures pollute the output, which makes other valid errors lost.
- An example of such error log is following:
```
Invalid API Key provided: sk_test_******ripe
----------------------------------------
Error: Invalid API Key provided: sk_test_******ripe
at res.toJSON.then.StripeAPIError.message (/home/naz/Workspace/Ghost/Ghost/node_modules/stripe/lib/StripeResource.js:214:23)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
```
- There doesn't seem to be a good reason to do migrations in the test environment. Skipping them as a special case to fix the output pollution problem seems like a right solution
refs https://github.com/TryGhost/Team/issues/1469
With multiple newsletters, members can now have one or more newsletter subscriptions that is attached to them. This change updates the member BREAD service to handle attaching newsletter data to member, based on the newsletter flag.
- if newsletter flag is disabled, add/edit methods delete any newsletter data attached to member
- sets `newsletters` as a default relation for read/browse methods so a member always has newsletter data attached to them
no issue
- When a new member is added via the API, with a stripe_customer_id, the member bread service is passed also with a default option: `withRelated: ['labels']`.
- This option is passed along to the member repository, and further and is also passed when loading relations that don't have a relationship called 'labels'.
- This results in a `labels is not defined on the model` error when you try to create a new member with a stripe customer id.
- Test that found this issue will be added to the Ghost repo.
refs https://ghost.slack.com/archives/C02G9E68C/p1647599592576139
When you add a redirect multiple times, and remove it afterwards, an error is thrown:
`Cannot destructure property 'fromRegex' of 'this.redirects[redirectId]' as it is undefined.`
This was caused by `redirectIds` that contained the same id multiple times.
* Added a test for adding a redirect multiple times and removing it once
* Fixed adding same redirect multiple times throws an error on removal