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.