refs https://github.com/TryGhost/Team/issues/1037
Tiers have a new `type` column to differentiate between `free` and `paid` tiers. This change -
- sets type as paid for all new tiers created, as `free` tier is created by default
- excludes any price/stripe data change for free tier
- updates all usages of default product to fetch the first paid product from the products list in DB instead of just the first product it finds.
refs https://github.com/TryGhost/Team/issues/1275
We want to be able to track where member subscriptions came from, so
that we can use the information to reduce spam imports of members.
We were missing information when members were uploaded via the Admin
API, and setting the source to 'member' be default - this fixes that
both when creating members and when updating their subscription status.
no-issue
We have a special mapping for subscribed_to_emails -> subscribed in the
parse method, but were not mapping it in the unparse method, which meant
we were losing information during CSV imports.
no-issue
Running these in a transaction ensures that they do not partially execute or run
into race conditions with simultaneous operations via the API.
refs https://github.com/TryGhost/Team/issues/1257
This gets us closer to not having to reload the MembersAPI when config
is changed which will help stop bugs arising from multiple instances of
the MembersAPI being created.
refs https://github.com/TryGhost/Team/issues/1259
These errors are thrown by nodemailer and can occur when an invalid
email address is used. Without special handling these cause a 500 error.
refs https://github.com/TryGhost/Team/issues/789
We are still having issues with duplicate subscriptions being inserted,
despite running our code in transactions. For now we will catch these
errors and response ot Stripe with a 409 so that it'll retry later - and
it stops us from throwing 500's
refs https://github.com/TryGhost/Team/issues/1202
When importing we were transforming the CSV and add missing columns to
it before storing it in preparation to perform the import. This resulted
in the missing columns being updated for existing members with blank
data.
We've updated the Members CSV parsing library to take an options list of
columns to include, which then allows imports to not include all of the
default columns.
closes https://github.com/TryGhost/Team/issues/1238
- previously returned 500 errors when a subscription had multiple prices due to external tampering on Stripe directly
- instead now returns 400 Bad Request error when subscriptions don't have right number of prices
refs https://github.com/TryGhost/Team/issues/1067
This decouples the contents/type of email from the webhooks service,
allowing us to easily make changes to the type of email sent, without
having to make changes to the webhooks service.
refs https://github.com/TryGhost/Team/issues/1243
It's possible to get into strange states where a subscription in Ghost
doesn't have an associated Price. This then has knock on effects because
we're dealing with data in an undefined state. Rather than add guards
against this throughout the entire stack, we stop returning it from the
BREAD API. It might be worth considering removing these subscriptions
from the response of the repository, but for now this is the most
minimal change that fixes the problem.
refs https://github.com/TryGhost/Toolbox/issues/139
- The DynamicRedirectManager was initialized witht the same set of parameters throughout the test suite, so it made sense to initialize it once for all the tests. The sibiling describe block will have a similar setup for a redirects manager that has a subdirectory configured
refs https://github.com/TryGhost/Toolbox/issues/139
- As few more tests have been added a clearer pattern of reusable variable has emerged. Have extracted common bits into "beforeEach" block to keep the declarative part of the test to the minimum
refs https://github.com/TryGhost/Team/issues/1236
We want to be able to use the OfferName as the name property for a
Stripe Coupon - which has a maximum character length of 40.
refs https://github.com/TryGhost/Team/issues/1092
refs https://github.com/TryGhost/Team/issues/1135
This was missed in the initial due to the issue tracking the task being
superceded, and the task not being copied across to the superceding
issue.
A new method to remove coupons has been added, as opposed to updating
the existing change subscription price method, because the removal of a
coupon is not the concern of an auxillary stripe service, but a busines
concern that should be explicit in the members-api codebase.
refs https://github.com/TryGhost/Team/issues/1067
The auto-login behaviour obliterates the concept of a success URL,
because the Member is redirected in a logged in state, to the welcome
URL - rather than a logged out state to the success URL.
In order to not disrupt existing flows, we disable auto login if a
success URL is provided.
refs https://github.com/TryGhost/Team/issues/1067
This is the MVP for auto-login of Members, it does not support custom
redirects, and will always just redirect to the same place that the
signin & signup links do. Behind a feature flag whilst we iron out the
functionality.
refs https://github.com/TryGhost/Team/issues/887
Our invoice webhook handling code assumed that every invoice would be
for a subscription, but that is not the case. There are valid use-cases
of using the same Stripe account in order to sell items with a one-off
purchase. Here we update the handling to ignore all invoices which are
not for subscriptions.
no-issue
When adding a new member, we allow an email to be sent, and the type of
email to be chosen. This choice was being overriden by our signup email
logic - here we allow the BREAD API to have full control over which
email is sent.
refs https://github.com/TryGhost/Team/issues/885
This webhook isn't used and can cause issues when Checkout Sessions are
completed but with a failed payment. Removing it will remove those
errors.
no-issue
When attempting to read a non-existent offer we were running into issues
with calling toJSON() on `null`. This updates the handling to explicitly
return null - so that the controller can correctly throw a NotFoundError
refs https://github.com/TryGhost/Team/issues/1163
This allows users to not provide a title for an Offer. We store the lack
of a title as `NULL` in the DB, but we will always provide a string to
the API so that the title can safely be used in HTML.
refs https://github.com/TryGhost/Team/issues/1135
We use the OffersAPI to fetch Offers, so that we can be using the same
format for Offers in all of our APIs.
We will not attach the Offer to the Subscription if either the Tier or
the Cadence do not match. This is because the Offer would no longer
apply to this Subscription.
We do however retain the data, so that a Member can still be filtered on
the Offers which they've redeemed.
refs https://github.com/TryGhost/Team/issues/1166
We've moved the Stripe Coupon creation out of the Offers module as part
of the work for Stripe disconnect, so we have to make sure that we are
still creating coupons when an Offer is created.
refs https://github.com/TryGhost/Team/issues/1166
Since we removed the creation of coupons from the Offers module, we must
emit events so that the Payments module can handle creating Coupons when
Offers are created.
We also export the events from the module so that they can be listened
to by the Payments module.
We also export other internals of the module so that the types can be
used.
refs https://github.com/TryGhost/Team/issues/1166
By using the PaymentsService to fetch coupon information - we ensure
that the coupons are created if they're missing. Like in the case of a
Stripe disconnect/connect cycle.
refs https://github.com/TryGhost/Team/issues/1166
This is a new module which will eventually handle all payment related
things. This allows the Offers module to focus exclusively on the Ghost
concepts, and the Payments module will handle the association between
Offer & Stripe Coupon, Tier & Stripe Product, Cadence & Stripe Price.
This decoupling allows us to not have to consider the lack of Stripe
data for an Offer, which is the case after a Stripe Disconnect. Instead
all of the population/repopulation/lazy-creating can be handled here.
refs https://github.com/TryGhost/Team/issues/1166
This will be handled by a payments module instead. In order to
disconnect Stripe we must delete all Stripe related data, which means an
Offer doesn't inherently have a stripe coupon id. Instead we can use a
payments service which will get/create the coupon for us when we need
it.
refs linear.app/tryghost/issue/CORE-74/improve-the-test-situation
- this commit adds the codecov GitHub Action into CI so we can upload
coverage reports
- the coverage files need to be in XML for them to work with
codecov, so this commit also adds cobertura (XML) as a reporter
no-issue
Because we were checking for truthyness rather than existence when
updating properties on an Offer - it was impossible to set the
description to a blank string, as this is falsy.
no-issue
Without forcing linkSubscription to run inside a transaction - it's
possible to have race conditions where it is called twice, and attempt
to insert duplicate rows into the database.
refs https://github.com/TryGhost/Team/issues/1132
We have to include the Offer on the metadata for the Stripe Checkout -
as Offers with a duration of 'once' will not always be present on the
Subscription after fetching it.
Once we receive the Stripe Checkout webhook we emit an event for
subscription created - the reason we use an event is because this logic
should eventually live in a Payments/Stripe module - and we'd want to
decouple it from the Members module.
The Members module is in charge of writing Offer Redemptions - rather
than the Offers module - because Offer Redemptions are "owned" by a
Member - and merely reference and Offer. Eventually Offer Redemptions
could be replaced by Subscriptions.
refs https://github.com/TryGhost/Team/issues/1156
Because we were only attempting to add the product to the members if the
subscription was new AND active - we would not add it for incomplete
subscriptions transitioning to active.
Instead we always attempt to add the product to a member for an active
subscription - it doesn't matter if it's a new one. We later have logic
to filter out duplicate products if the member already has access to the
product.
refs https://github.com/TryGhost/Members/commit/5172e40646
When we updated to use the OffersAPI instead of OfferRepository this was
missed, and we were passing blank coupon to Stripe Checkout. This should
eventually be replaced with a call like `getCoupon(offerId)` from a
payments service.
refs https://github.com/TryGhost/Team/issues/1133
An archived Offer is intended to be disabled from a redemption point of
view. This ensures that we do not allow Stripe Checkout Sessions to be
created for them.
no-issue
The OfferRepository deals with domain objects in the Offers module, and
as such is not suitable for use with "external" services. This update
means that MembersAPI can deal with POJO DTOs so that there is not a
dependency on the internals of the Offers module. Just on the contract
it holds with the outside world.
refs https://github.com/TryGhost/Team/issues/1131
This adds a mapping between the status property used in the domain & API
and the active column used in the database. As we only have the usecase
of filtering by `status` right now, we have not added support for all
the other columns. Instead of these potentially erroring where the
column name does not match the property name in the domain/api - we've
added a transformer which will ignore all filters for properties other
than `status`. This follows postels law, in that we can be liberal with
the filters we accept, but conservative in the ones we implement.
refs https://github.com/TryGhost/Team/issues/1131
- Includes `status` on OfferDTO so client can use it
- Allows editing `status` of Offers
- Allows setting initial `status` when creating Offers
no-issue
This simplifies the handling of updating redirects for a code, and
doesn't affect our application layer because we never have the need to
change a code twice.
In future this should be replaced with events at the domain level - so
that we do not have to track changed properties and instead a redirect
service can listen to events, which would be dispatched on a successful
save by the repository.
refs https://github.com/TryGhost/Team/issues/1083
We combine the duration and duration_in_months into a single value
object which can be validated together, meaning we will never have
properties which are out of sync (e.g. forever durations with 2 months).
no-issue
Since we changed the type from "amount" to "fixed" the logic to send
back the currency was not working. This updates it to use the correct
values.
refs https://github.com/TryGhost/Team/issues/1083
We now allow creating offers for a fixed amount, rather than a
percentage. These require a currency to be passed as a fixed amount is
meaningless without one.
refs https://github.com/TryGhost/Team/issues/1083
Instead of Offers being hardcoded to the "once" duration this will allow
Admins to start creating offers of variable durations.
no-issue
This adds the concept of "Value Objects" to an Offers properties,
allowing us to move validation out and ensure that an Offer will only
ever have valid properties, without having to duplicate checks - or
leave them to the persistent layer. This means we can fail early, as
well as write unit tests for all of our validation.
refs https://github.com/TryGhost/Team/issues/1090
When creating a Stripe Checkout Session for an Offer - we need access to
the underlying Stripe Coupon. Exposing it here allows consumers of the
OfferRepository access.
refs https://github.com/TryGhost/Team/issues/1090
Instead of the hardcoded 1-day version for Offers, we can now talk
directly to the Offers repository and use the real values for Stripe
Checkout.
refs https://github.com/TryGhost/Team/issues/1090
When creating checkout session we will need to be able to look up Offers
from the OfferRepository. This exposes the repository so that it can be
passed as a dependency elsewhere.
refs https://github.com/TryGhost/Team/issues/1083
The Offers service is going to need access to the StripeAPIService too,
so we must pull its initialisation out of this module up to the Ghost
application layer, which will allow us to pass a reference of the
StripeAPIService to wherever needs it.
refs https://github.com/TryGhost/Team/issues/1075
Without this flag, payment for upgrading to a more expensive plan will
be taken at the end of the current billing cycle, which could be a year
from the date of the switch - this would effectively give free access to
more expensive prices.
refs https://github.com/TryGhost/Team/issues/1091
The Offers feature needs to be able to add and remove redirects to Ghost
- which is very similar to the custom redirects functionality. Here we've
pulled out the core of the dynamic redirect part of custom redirects so
that it can be used by both features and have code shared between them.
refs https://github.com/TryGhost/Team/issues/1090
This 1-day version of Offers allows us to test the full flow of the
Offers feature without having to implement all of it. The focus here is
that we can pass an Offer ID when creating a Stripe Checkout session and
have it apply. Here we use hardcoded Stripe Coupons as we haven't yet
got persistence implemented for Offers & their related Stripe Coupons
refs https://github.com/TryGhost/Team/issues/1090
This allows us to create Stripe Coupons and use them with Stripe
Checkout from the members-api module whilst we develop the Offers
feature.
no-issue
Without a return after ending the response, the code will continue to
attempt to send emails and then send another response which results in
an uncaught error.
refs https://github.com/TryGhost/Ghost/security/advisories/GHSA-65p7-pjj8-ggmr
The email address change flow was built on top of the unauthenticated
signin/signup flow. This meant that ownership of the email being changed
wasn't verified and allowed a malicious actore to change the email
address of arbitrary accounts to an email address which they controlled.
We remove the ability to change email addresses from the signin/signup
flow and instead create a dedicated, authenticated flow for changing
email address.
no refs
- the package `@tryghost/stripe-service` was already published and used in a different context, so this package was never able to get published and the references in Members package are incorrectly pointing to wrong package
- renames the package in members context
refs https://github.com/TryGhost/Team/issues/1054
In order to listen to events we must define them! This adds the missing
events that we need to listen to for member analytics.
refs https://github.com/TryGhost/Team/issues/1054
We need to instantiate the MemberAnalyticsService so that we can start
listening to events and storing them, this is the minium glue code
required to get us going.
refs https://github.com/TryGhost/Team/issues/1057
This method will validate a token, and then return the member associated
with it. Rather than exposing token validation and coupling consumers to
the structure of the token response data.
refs https://github.com/TryGhost/Team/issues/873
This ensures that all requests to the API will include the mock
subscriptions for comped members. Allowing the Admin to correctly show
the subscription information after adding and editing members. As well
as having the correct information when navigating from the list of
members to an individual member.
no-issue
This pulls out the StripeService from the @tryghost/members-api package.
The idea is to break the @tryghost/members-api package into smaller
modules, with the hope to make it easier to maintain and reason about.
no-issue
Previously we would not create an instance of the StripeAPIService if
Stripe was not configured, but that is not the case any more, instead we
have a configured flag on the service. The webhook route handler was not
updated to use this flag and so would attempt to handle webhooks without
having any of the required data. This would result in an uncaught error.
refs https://github.com/TryGhost/Team/issues/1006
When disconnecting from Stripe, we currently do not remove the webhooks,
this will result in the webhooks from Stripe failing, and tending toward
a 100% error rate, which will ultimately result in emails from Stripe
about the failing webhook.
In order to stop all of that from happening, we should make sure that we
actively remove the webhook from Stripe when disconnecting.
refs https://github.com/TryGhost/Team/issues/1006
As part of the work to handle cleaning up webhooks when we disconnect
from Stripe, I'm moving the logic to clear out the Stripe related data
from the database into a disconnectStripe method. This then allows us to
start handling the cleanup of webhooks via the Stripe API.
refs https://github.com/TryGhost/Team/issues/995
Since we reintroduced the comped status, we did not update the
subscription handling to correctly set members to a status of comped
when they were on a 'Complimentary' plan.
no-issue
Since updating the product repository to force transactions, the options
parameter was used in every call, meaning it wasn't optional any more,
which broke usage. This updates the parameter to have a default so that
existing usage still works.
no-issue
Since we run our product repository methods in transactions now we must
ensure that all database interations in the method use the transaction.
This adds the missing options to the reading of existing prices so that
they happen inside of the transaction.
refs https://github.com/TryGhost/Team/issues/982
These calls to the edit method were missing the transaction option from
the parent which meant that they ran outside of the transaction and
would cause the method to timeout.
no-issue
As subscriptions are a default relation, and we now require products to
populate subscriptions for comped members, we need to include products
by default when reading members.
refs https://github.com/TryGhost/Team/issues/986
The getMemberIdentityData is a relic of time past. Originally it was
used before we had anything like the member repository or bread
controller as a way for things inside of the Members ecosystem to get
access to member data.
This updates it to use the same interface as everything else for
fetching members so that we can rely on the shape of the data that we
consider a member.
This update will ensure that themes have access to the dummy
subscriptions created by the `read` method of the MemberBREADService.
refs https://github.com/TryGhost/Team/issues/979
This correctly handles updates to subscriptions so that if the product a
subscription is for has changed, we will remove the previous product, if
and only if there is not another subscription which gives access to it.
refs https://github.com/TryGhost/Team/issues/873
The `get` method of the member repository will return null when no
member is found - we must ensure that we don't attempt to call toJSON!
It is also possible for a member to not have any products, in which case
we should not attempt to iterate over them, and we can return early.
refs https://github.com/TryGhost/Team/issues/873
This is an unexpected state, but possible if the alpha version of tier
has been enabled previous to the events being added.
refs https://github.com/TryGhost/Team/issues/873
This adds a dummy subscription for each product that a member has
without an associated stripe subscription. It allows clients to deal
with things like a created date for comped members.
no-issue
The idea of this service is to sit infront of the repository and handle
application logic which does not belong at the data layer. The exact
naming and structure is TBC but this gives us a place to start pulling
logic out of the controllers, without having to mash it all into the
repository.
Also important to note is that is does not return instances of bookshelf
models, but a JSON representation of the model, this allows us to not
leak internal implementation to consumers.
refs https://github.com/TryGhost/Team/issues/873
This handles the creation of product events when a members access to
products is changed. This can happen on creation, update, and any
changes to stripe subscriptions.
We manually workout the difference between the current products and the
new products, and add the events accordingly.
no-issue
Calling ObjectId doesn't return a string a but an ObjectId object.
Whilst this object is cast to a string via the toJSON and toString
methods, this is not enough for MySQL. Instead we should explicitly cast
this to a string ourselves and the application level.
refs https://github.com/TryGhost/Team/issues/870
- using `c8` allows us to see test coverage for all packages in the repo
- this commit adds `c8` as a dev dependency and prepends the `mocha`
command with `c8` so it runs on all tests
refs https://github.com/TryGhost/Team/issues/958
- The import threshold has to become a dynamic number based on external parameters. Because of this it makes most sense to have it as an async function parameter
- There's a package API change for importThreshold constructor paramter becoming a fetchThreshold async funciton parameter
refs https://github.com/TryGhost/Team/issues/946
This adds the bulk edit method which handles bulk edit operations to members
to be used by the filtering feature. They have been combined into a single method
as that is how they are exposed to the API. This is definitely a candidate for a
refactor in the form of a service in front of the repository.
refs https://github.com/TryGhost/Ghost/commit/1dd52075
- Fixes bulkDestroy being passed the context
- Fixes passing options.search to the model layer
- Updates return value since the changes in referenced commit