- users imported from CSV with no created_at date where having their created_at date being stored as an int rather than a datetime.
- this was causing parsing issues with the graph so this commit fixes the formatting
closes#12045
- When member's email is updated to an already existing email of different member it caused table's unique constraint error, which was not handled properly.
- Added handling for this error similar to one in members `add` method.
no issue
- When an import was done and there were no "global labels" present Ghost created generic `import-[data]` label which later helped to find a specific batch of imported data
- It did not make sense to create such generic label when user provided their own unique label
- The rules that work now are:
1. When there is no global provided Ghost generates on and removes it in case there are no imported records
2. When there is a unique new global label provided no new label is generated, but the label stays even if there are no imported records
no issue
- This is handled on input sanitization layer with date
format check in JSON schema validation, so there's no need to do this
check again in the importer.
no issue
- When bulk insert fails there is no transactional logic to revert
related records form being inserted. Also, previously there were no
attempts to "retry" the insert.
- To avoid complex retry logic, an iterative one-by-one insert retry
approach was taken. If this becomes a bottleneck in the future, the
retry algorithm could be improved.
- To avoid a lot of code duplication refactored model's `bulkAdd` & `bulkDestroy`
methods to use 'bulk-operations' module.
- Updated error handling and logging for bulk delete operations. It's very
unlikely for error to happen here, but still need to make sure there is
a proper logging in place to trace back the failure.
- Added debug logs. This should improve debugging experience and
performance measurements.
- Added handling for unrecognized errors. Handling inspired by current unrecognized
error handling by ghost importer -10e5d5f3d4/core/server/data/importer/importers/data/base.js (L148-L154)
no issue
- Similar handling to one introduced in 31db3c86800b3268da5485417b16e0fcd8e6579a
- Having granular tracking for failed to remove id's would make it possible to return more specific errors to the client
no issue
- When batch insert fails handling should be more granular and aim to retry and insert as many records from the batch as possible.
- Added retry logic for failed member's batch inserts. It's a sequential insert for each record in the batch. This implementation was chosen to keep it as simple as possible
- Added filtering of "toCreate" records when member fails to insert. We should not try inserting related members_labels/members_stripe_customers/members_stripe_customer_subscriptions records because they would definitely fail insertion without associated member record
no issue
- When stripe is disconnected and there are Stripe-connected records present in imported set they should not be processed and proper error should be thrown
no-issue
* Added bulkAdd method to Member,Customer&Subscription model
This allows us to keep the db access in the model layer
* Updated @tryghost/members-api to 0.27.2
This includes fixes for rate-limiting of requests, and exposes necessary
Stripe methods for creating customers and complimentary subscriptions,
without affecting the database.
* Refactored importer to parallelise tasks where possible
By parallelising our tasks we are able to improve the speed at which the
entire import completes.
no issue
- There were many failed import records due to rate-limit errors. With concurrency of 9 imports go through with 100% success
- Would need to verify these limits with live API to make the most of it
no-issue
* Added stripeSubscriptions relation to member model
This allows us to fetch the subscriptions for a member via standard
model usage, e.g. `withRelated: ['stripeSubscriptions']` rather than
offloading to loops and `decorateWithSubscriptions` functions, this is
more performant and less non-standard than the existing method.
* Updated serialize methods to match existing format
The current usage of `decorateWithSubscriptions` and the usage of
members throughout the codebase has a subscriptions array on a stripe
object on the member, this ensures that when we serialize members to
JSON that we are using the same format.
There is definitely room to change this in future, but this is an
attempt to create as few breaking changes as possible.
* Installed @tryghost/members-api@0.26.0
This includes the required API changes so that everywhere can use
members-api directly rather than models and/or helper methods
no issue
- The code in controller was becoming hard to reason about.
- Having a single module shows exactly how many dependencies are there to do an import for single batch.
- Having a separate module would make it easier to extract into it's own package in Members monorepo
refs https://github.com/TryGhost/Team/issues/919
As we pass the `benefits` to the Product model on creation, we do not
need to manually fetch them again. In fact doing so causes a strange SQL
error, where we attempt to run `SELECT undefined.*`.
refs https://github.com/TryGhost/Team/issues/908
The `cookies` module will unset a cookie if `null` or `undefined` is
passed as the value, or if the value is not passed. The previous call
was passing the options, which were being read as the value, and
resulting in `'[Object object]'` being stored as a cookie.
Explicitly passing `null` as the value makes this code correct and
easier to maintain.
refs https://github.com/TryGhost/Team/issues/664
The well known controller is designed to handle any requests to the
/.well-known endpoint where the members app is mounted. The first and
only requirement so far is that we expose a JSON Web Key Set so that
external services are able to validate Members JWT's
closes https://github.com/TryGhost/Team/issues/778
- cleans up the stripe migration to add default monthly/yearly prices for sites, which had a possibility of using complimentary (0 amount prices) in edge cases
- adds missing return in the same migration for an unlikely failure to parse stripe plans
refs https://github.com/TryGhost/Team/issues/542
Importing members with a created_at date will incorrectly create events
for the member for the date of the import. This updates our event
handling to use either the passed created_at date, or in the case of
subscriptions the start_date of the subscription. We're using start_date
for subscriptions rather than created, as this is more accurate because
start_date works correctly for backdated subscriptions in Stripe.
closes https://github.com/TryGhost/Team/issues/660
All subscriptions in Ghost are expected to have a corresponding price details in `stripe_price` table, which is used to determine the Stripe price a subscription is on. In some edge cases, specially before we started deleted old Stripe data during Stripe disconnect, it's possible that a subscription exists in DB without having a corresponding Stripe price in the DB. These subscriptions are not active for the connected Stripe account, and are save to remove. Going forward, all existing subscriptions with connected account will be removed when disconnecting stripe so we shouldn't have invalid subscriptions in DB in future.
The goal of this migration is to clean all such subscriptions from the DB to avoid any issues around missing price with invalid subscriptions.
refs https://github.com/TryGhost/Team/issues/858
Replacing the check for subscriptions with products ensures that Stripe
Checkout is not able to be opened by comped members.
refs https://github.com/TryGhost/Team/issues/790
We were missing a check for the existence of memberData.products before
attempting to read the length property from it, which can result in an
Uncaught TypeError
refs https://github.com/TryGhost/Team/issues/790
When linking a subscription to a member we must also update their
status. We could be in one of many states, so we start with the initial
values of either 'free' or 'comped' based on whether the member has any
products. We then make sure to updated the status to 'paid' if we find
any active subscriptions associated with the member, otherwise we leave
it as the initial value.
refs https://github.com/TryGhost/Team/issues/790
Both creating and updating members only ever need to explicitly set
either the 'comped' or 'free' status as these methods do not deal with
Stripe. When updating a member if the products are not changing, we do
not attempt to change the status either.
no issue
- we're killing off `ghost-ignition` in favor of explicit packages
containing its individual components
- this commit switches the Members repo to using the
`@tryghost/ignition-errors` and `@tryghost/debug` dependencies,
updates the code with relevant changes and removes the `ghost-ignition`
dependency
no issue
- these dependencies are used within the `members-csv` repo but were
never included in the `package.json`
- this commit adds in the missing dependencies
refs https://github.com/TryGhost/Team/issues/765
Support for multiple products means we can no longer map a members state
to a csv row using just the `complimentary_plan` option. Instead we must
include the product(s) that a member has. This ensures that we can read
and write this data from/to csv files.
refs https://github.com/TryGhost/Team/issues/748
This ensures that you cannot add more than one product to a Member.
However it does allow a Member which already exists with more than one
Product to continue using the API. This is to account for edgecases such
as a Member going through the Stripe flow twice and ending up with
multiple subscriptions for multiple products
refs https://github.com/TryGhost/Team/issues/748
Previously when a change happened to a subscription the member's
products would be reset purely based on the subscriptions. Since we now
support setting products outside of subscriptions, we must make sure
than a change to a subscription will only affect the product for which
the subscription was for
refs https://github.com/TryGhost/Team/issues/775
Exposing this to the Admin API means that we want to be able to cancel
by id, not just email. The same pattern from editSubscription has been
used.
refs 7d8e18c802
Since we have now gone back to using `monthly` / `yearly` named values in portal plan settings instead of price ids, the old portal plan migration which was converting named values to ids is now redundant. Moreoever, the old migration is incorrectly resetting the `portal_plans` setting to `[free]` by removing the paid plans as its not found in `stripePlans` setting anymore.
refs fd40e04105
The current implementation of setting legacy prices as inactive has a bug where it also sets the active price to inactive if only one of monthly/yearly price is edited while the other is not changed. This is a temporary reversal till we can narrow down the issue and fix to correctly set legacy plans to inactive.
closes https://github.com/TryGhost/Team/issues/753
Currently, the portal_plans setting is storing price ids for active monthly/yearly prices for the default product, which was done to allow multiple prices in Portal. Since we only want to limit the prices for a Product to monthly/yearly, we are reverting the earlier migration and only store the available prices as monthly / yearly in portal setting instead of ids. Its also in sync with the approach in theme/API where we use named prices for monthly/yearly instead of price id list.
refs https://github.com/TryGhost/Team/issues/711
As this migration relies on the settings being populated, it cannot be a
standard migration in Ghost core, as the settings are populated by these
code migrations.
refs https://github.com/TryGhost/Team/issues/704
Currently when attempting to create stripe_prices without a Stripe
connection, it will fail silently. This is an issue when initially
configuring Members as the Stripe connection can take some time to be
established. By erroring we allow the client to be notifed that the
connection does not yet exist, so that it can be retried later.
refs https://github.com/TryGhost/Team/issues/635
It's possible that we have subscriptions in the system which have been
created externally, and so using an interval of week or day. This change
ensures that we handle the mrr_delta for these subscriptions correctly.
refs https://github.com/TryGhost/Team/issues/657
Since we have removed the getPlan method we need to update the migration
which is the last piece of code which used it. Prices are backwards
compatible with Plans - so this will continue to work as expected.
refs https://github.com/TryGhost/Team/issues/657
- Removes the use of "plans" from token service - method wasn't used
- Updates to use async/await so the code is clearer
- Install types for the node-jose module
- Install types for the jsonwebtoken module
refs https://github.com/TryGhost/Team/issues/657
We must still use the "plan" approach of creating Checkout Sessions so
that we are able to maintain our current functionality of setting the
free trial from the one associated with the Plan/Price used for the
Checkout Session.
In future we would handle free trials internally in Ghost and apply them
on a subscription-by-subscription basis.
refs https://github.com/TryGhost/Team/issues/698
As this migration relies on the `stripe_prices` table being populated,
it can not be in a standard versioned migration in Ghost core.
refs https://github.com/TryGhost/Team/issues/693
With the new system of Custom Products, the concept of Complimentary is
not longer a thing, and will instead be handled by explicitly creating
prices with no amount. This means that the 'comped' status for members
will be replaced with 'paid'.
closes https://github.com/TryGhost/Team/issues/682
This ensures that the Stripe Product name is updated during the
migrations of an existing site and any future updates to the Product
name.
no-issue
Since we do not necessarily have a single stripe product anymore, we
should be checking if an invoice webhook is for a stripe product which
we know about. We use the Products repository to search our database for
one.
closes https://github.com/TryGhost/Team/issues/650
Despite the fact we're getting rid of the concept of Complimentary, we
must maintain backwards compatibility for the `comped` flag in the Admin
API & the importer. This flag is handled via the `setComplimentary`
method, which has been updated to work with the new system.
refs https://github.com/TryGhost/Team/issues/581
refs https://github.com/TryGhost/Team/issues/582
Content gating in Ghost is being expanded from basic public/members/free/paid to allowing full NQL queries. To facilitate quick matching of a member to the visibility query we need details of the associated labels and products alongside the basic member data.
no issue
- `setMemberGeolocationFromIp()` was passing a raw model instance through to `users.update()` rather than a data object causing the `model.edit()` call to overwrite existing data such as labels because `modelInstance.labels` is a function rather than an array
- removed unnecessary `withRelated: ['labels']` as it's not necessary for the update, member data is fetched again with all necessary includes by `getMemberIdentityData()` before returning
refs https://github.com/TryGhost/Team/issues/637
As we move away from `plan` data to `prices`, this change updates member identity data to include `prices` object in subscriptions object to get the price data for subscription instead of relying on `plan` data.
refs https://github.com/TryGhost/Team/issues/637
- Adds one-off migration that reads from current `stripe_plans data` for a price, and ensures that the corresponding price is present in `stripe_prices` table at start.
- Currently, the portal_plans setting is used to determine the prices available to Portal for showing on Signup or Subscription change screen. The values allowed in portal_plans currently only allow [free, monthly, yearly] , which needs to be updated now to store price ids of available prices instead. Uses above migration to populate `portal_plans` with ids instead of names.
refs https://github.com/TryGhost/Team/issues/637
All the APIs that currently work with price names needs to be updated to work with price ids instead to work with custom prices/products. This change updates APIs to work with Price IDs in `checkout` , `updateSubscription` and other APIs/methods.
no-issue
When updating a Product we can pass existing Stripe Prices, these will
either be adding to the database, or updated if they already exist. When
updating them we were attemping to use the `id` passed in the update,
which is not necessarily included. Instead we should use the `id` of the
StripePrice which we have already retrieved from the database.
refs https://github.com/TryGhost/Team/issues/616
This is a generic method for adding a subscription to a member for a
particular price/product pair. This will be used in the Admin for e.g.
giving complimentary subscriptions.
refs https://github.com/TryGhost/Team/issues/616
For existing prices linked to a Product, we only allow site owners/admins to edit their nickname and nothing else. This change handles the update in Ghost, but needs extension to update the name in Stripe as well.
closes https://github.com/TryGhost/Team/issues/628
refs 9010a62d54
Following up on last commit, this moves up the expansion of Stripe customer fetch to always include `subscriptions` by default in api service so we don't accidentally miss it.
no refs
The latest version of Stripe doesn't return the `subscriptions` object on `Customer` resource by default and needs an extra param to do so. As we recently updated Members to use the latest Stripe version, the importer tries to fetch all subscriptions of a customer to map in Ghost but was failing due to missing `subscriptions` data. Fix updates the Stripe API to include `subscriptions` by default in response.
refs https://github.com/TryGhost/Team/issues/591
It's possible to have sites which still have subscriptions in their DB from old Stripe accounts, most likely added when we allowed Stripe Direct, as those subscriptions were not cleaned up. While populating plans and products for existing subscriptions, we want to ignore these old subscriptions which are not part of current Stripe account.
refs https://github.com/TryGhost/Team/issues/619
On linking a stripe subscription to a member, this change -
- Adds missing stripe price or stripe product from subscription to DB
- Missing Stripe price is attached to the first Ghost Product if no matching Product exists
- Updates usage from plan to price in the `linkSubscription` method
- Updates products associated with a member based on active subscriptions
refs https://github.com/TryGhost/Team/issues/616
Working with ProductRepository as a separate package was more trouble
than it was worth, so it's been moved into members-api. We expose the
product repository so that Ghost Admin API can access it.
refs https://github.com/TryGhost/Team/issues/586
On Ghost Boot, as part of configuring Stripe, this populates stripe products and prices for existing stripe customers in the newly created `stripe_prices` and `stripe_products` table, which allows us to map existing customers to default Ghost product and on current prices. The population script on boot is only run if we find -
- A Ghost Product
- No rows in `stripe_products`
- No rows in `stripe_prices`
- One or more rows in `members_stripe_customers_subscriptions`
refs https://github.com/TryGhost/Team/issues/593
- Bumps `stripe` node library major version to v8 - 8.142
- Bumps default Stripe version to latest - '2020-08-27'
- Updated webhook Stripe version to latest - '2020-08-27'
- Removes `@types/stripe` in favor of first-class types support in `stripe` lib directly
- Updates types across files
refs https://github.com/TryGhost/Team/issues/595
For a canceled subscription, the desired MRR delta is to reduce by negative of original amount, but our logic was incorrectly reducing it by double which led to big gap between real MRR and one shown on Dashboard.
- Fixes calculation for MRR change for canceled subscriptions
no-issue
The Admin API uses a Member id rather than email to update
subscriptions, this ensures that we provide an interface that will
continue to work with the Admin API
https://github.com/TryGhost/Team/issues/530
This option will check to see if a subscription is in an unpaid or past
due state, and if so, will cancel the subscription immediately, rather
than cancelling at the period end.
refs https://github.com/TryGhost/Team/issues/530
The RouterController was a grab bag of all controller methods, making it
difficult to mock & test. This adds a MemberController with a smaller
API - making it easier to test.
no-issue
We would like to release @tryghost/members-csv as version 1.0.0 but
lerna will only allow us to release packages which have had changes in
them since their last release. Well, this is a change. And it's solely
for the purpose of allowing us to release 1.0.0. This version is not
tagged, nor is it published to npm.
no-issue
When seeding the database with fake members & stripe data, it's possible
to create stripe plans without a nickname. Similarly some other services
do not have a nickname on their plans. This ensures that we do not error
when working with these plans.
no-issue
If we are to perform the `linkSubscription` method inside of a
transaction, the addition of the paid subscription events would happen
outside of the transaction, and cause errors. This ensures that we pass
the options object (containing the transaction) to the models calls to
add paid subscription events
no-issue
1. We do not want to store payment events for payments of 0 value
2. Stripe webhooks can arrive and be processed "out of order", which can
result in us attempting to add a payment event for a member which
does not yet exist. The change here will 404 in such (edge) cases, so
that Stripe will retry the webhook at a later point, when the Member
has been created, allowing us to store the payment event.
refs https://github.com/TryGhost/Team/issues/469
In order to reduce noise, we want to only display newsletter
subscription events which are not likely to be the result of a member
signup. The approach we've taken is to remove any newsletter
subscription (not unsubscription) event, if when sorted in chronological
order, it is to reside next to a signup event for the same member.
An improvement to this approach might be to add some kind of transaction
id to events which would allow us to group together events which should
be considered to have happened simultaneously.
refs https://github.com/TryGhost/Team/issues/469
Signup events are captured by status changes with no `from_status`, this
means that the member did not have a status (did not exist) before this
change.
refs https://github.com/TryGhost/Team/issues/469
We order the set of all events by created_at, but were not fetching the
individual events with the same order applies, this resulted in
incorrect results.
refs https://github.com/TryGhost/Ghost/issues/12711
We must wait for the stripeSubscriptions relation to be loaded before
attempting to loop through them. As well as this we should use `upsert`
so that we can edit a subscription record by `subscription_id`, rather
than the (internal) `id`
no refs
The member id assigned when creating a new status event on member creation was incorrectly using `data.id` instead of `member.id`, which was undefined causing a validation error.
refs https://github.com/TryGhost/Ghost/issues/12602
* Added Event Repository
** Added method for MRR over time
** Added method for newsletter subscriptions over time
** Added method for gross volume over time
** Added method for status segment size over time
* Captured login events
* Captured newsletter subscription/unsubscription
* Captured email address change events
* Captured paid subscription events
* Captured payment events
* Captured status events
no refs
The data structure for subscriptions object on member has changed in 4.x from` stripe.subscriptions` to direct `subscriptions`, the change here updates parsing of subscriptions data
refs https://github.com/TryGhost/Team/issues/479
* Fixed updating payment method for canceled subscriptions
Stripe considers canceled subscriptions as non-existent, so any attempts
to update them will fail with a 404 not found. Prior to this change we
were attempting to update *all* subscriptions for a customer, including
those which were canceled. This would cause an error and the loop to
break.
* 🐛 Fixed errors for members with multiple active customers
Members with multiple active customers would have their first active
customer found updated with the new payment method. We would then
iterate through *all* active subscription, and attempt to update their
payment method. If a subscription was not owned by the customer that was
just updated, it would error and cause the loop to break out.
* Added ability to update a specific subscription's payment method
In order to remove ambiguity we add the ability to update the payment
method for a specific subscription. This will remove room for errors as
we will not have to worry about if a subscription belong to the customer
or not.
refs https://github.com/TryGhost/Team/issues/475
The subscription object here is a database model rather than an
ISubscription from the stripe library, and we need to 1) use the `get`
method to read attributes and 2) read the `subscription_id` attribute,
as the `id` is our internal one.
no-issue
Fetching relations via the model returns a promise, and this was missing
the `await` keyword. We also need to `get` the subscription_id attribute
as we're working with models rather than subscription objects.
no-issue
If we receive webhooks out of order, e.g. a
`customer.subscription.updated` with a status of 'active', followed by a
`customer.subscription.created` with a status of 'incomplete'. We would
overwrite the correct value with data from the "older" webhook. This
ensures that we always fetch the latest data from the Stripe API before
storing in the database.
no-issue
related to ef9cb0862c
In the last patch which fixes the bug for not passing custom redirect urls when a checkout session is created, we missed updating the case where a logged in member tries to update the subscription.
no issue
The logic to fetch member for a checkout session was incorrectly creating a new customer instead of finding an existing one, so the member's billing details was not getting updated.
no-issue
This refactors the members-api module so that it is easier to test going forward,
as well as easier to understand & navigate. The Stripe API no longer contains
storage code, this is all handled via the member repository. And we have dedicated
services for webhooks, and stripe plans initialisation.
no-issue
- Was not used by the importer and removed for simplicity.
- Updates the header mapping to happen in place, rather than in a loop
- Updates the parsing of values to give correct types
no issue
- In case of a member updating their email, the `updateEmail` type was overridden with `signup` or `signin` without the force option. The fix forces the correct email type for when member requests to update their email.
refs https://github.com/TryGhost/Ghost/issues/12253
Allows reading `requestSrc` from Stripe checkout flow metadata to send signup emails with customized action param when requesting from Portal
no issue
`getSigninUrl` takes an optional param `requestSrc` to allow customizing signin url based on source like Portal. Fixes tests and adds default value in case no `requestSrc` is present.
no issue
refs https://github.com/TryGhost/Ghost/issues/12253
Currently, Ghost uses standard query params like action, success and stripe for all actions and redirects to a site for member events. This needed to be extended to allow for portal specific query params so it doesn't overlap with specific theme handling or custom notifications.
The change here adds an extra option - `requestSrc` - which can be passed when using magic link API to send a link which is passed down to `getSigninURL`, and allows the `action` param to configured to `portal-action` when magic links are sent from Portal
no issue
Previously, we were sending a magic link email to signup in case a logged-in member upgrades to a paid account, as we didn't check for logged in status while sending the magic link and always sent one on finishing checkout. Since Portal allows members to upgrade their account from free, it doesn't make sense to send another email to signup after completing checkout.
The fix here adds a metadata `checkoutType` to checkout session creation which can be passed in with `upgrade` value to denote an existing member is upgrading and doesn't need an email.
refs https://github.com/TryGhost/Ghost/issues/12256 , https://github.com/TryGhost/Ghost/issues/12255
Currently when listing subscriptions for Members, we were only showing the subscriptions which have a status of trialing or active.
Based on discussion, the `unpaid` and `past_due` states on Stripe also represent owner's intention of considering a subscription as active instead of `cancelled`, so we allow any subscriptions under these 2 states to be also listed for a member and consider them as `paid`.
- Subscriptions will go into a past_due state if the payment is missed, this should be considered a grace period where the member still has access.
- After this the subscriptions will either go to the unpaid or the cancelled state - this can be configured on an account by account basis in the Stripe dashboard. `unpaid` is considered as an intention to keep the subscription to allow for re-activation later.
refs https://github.com/TryGhost/Ghost/issues/12270
Previously we would create the member, and then update their name from
stripe data, this mean that webhooks would be sent _without_ a name,
despite us possibly having the information to provide one.
Here we've updated the creation of members to include the name attached
to the default billing method, this will ensure that webhooks are sent
with all availiable information.
no-issue
Subscription created events are required for migrating Stripe subscriptions from
alternative platforms, which involves creating a new subscription for a customer
(outside of Ghost) before cancelling the original subscription.
no issue
- Email update magic link was not sent out for sites which did not allow self signup as it didn't find the member on new email, which is expected.
- Updates sending magic link check in case an old email is found to correctly trigger update email