Commit Graph

97 Commits

Author SHA1 Message Date
Fabien 'egg' O'Carroll
da00444961 Added support for cancellation_reason (#221)
refs https://github.com/TryGhost/Ghost/issues/12403

Adds support for sending a cancellation_reason when cancelling a plan and store the reason on the Subscription metadata
2020-11-23 16:28:35 +00:00
Rishabh Garg
c996c7b576 Added unpaid and past_due subscription status as paid member (#211)
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.
2020-10-27 15:15:23 +05:30
Matt Hanley
4359517e75 Added Stripe webhook listener for subscription created event (#208)
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.
2020-10-15 16:25:36 +05:30
Fabien 'egg' O'Carroll
8efc4c7016 Removed old webhook cleanup code (#207)
no-issue

This is no longer necessary anymore, it would delete all webhooks which
matched the current webhook handler URL, which is undesirable
2020-10-05 09:21:20 +01:00
Fabien O'Carroll
f41f366b5a Updated customer when member email is changed
refs: https://github.com/TryGhost/Ghost/issues/12055

This ensures that newsletters and billing related emails are all sent to
the same address
2020-09-28 16:57:51 +01:00
Fabien O'Carroll
b189584f98 Added method to to update customer email address
refs: https://github.com/TryGhost/Ghost/issues/12055

This will be used by the users module when updating a members email
address to keep the Stripe Customer email in sync.
2020-09-28 16:57:51 +01:00
Kristian Freeman
6ec7eeae33 Added support for promo codes in Stripe Checkout (#194)
no-issue

This commit adds support for Stripe's newly-added promotional code
parameter when creating a new Stripe Checkout session.

ref: https://stripe.com/docs/payments/checkout/set-up-a-subscription#coupons
2020-09-21 11:53:36 +01:00
Nazar Gargol
30f758e297 🐛 Fixed create and update user methods to account for created_at and subscribed fields
refs https://github.com/TryGhost/Ghost/issues/12156

- During the refactor - 117309b4e8 (diff-3daeef67d07a2a0f94c89a86cafcede9R44), `subscribed` and `created_at` fields have been overlooked. All fields accepted by Ghost's `POST /members` and `PUT /members/:id` should be supported
2020-08-24 18:29:03 +12:00
Rish
1fe75532e5 🐛 Fixed incorrect stripe method for cancelling subscriptions
refs https://github.com/TryGhost/Ghost/issues/12150

- `destroy` method was using incorrect cancel subscriptions method - stripe.cancelStripeSubscriptions - which doesn't exist
- Fixes call with intended method - `stripe.cancelAllSubscriptions` - to cancel all subscriptions
2020-08-21 16:11:24 +05:30
Rishabh Garg
0dad6d147f Added update subscription method to members api (#198)
refs TryGhost/Ghost#12127

- Adds new `updateSubscription` method to members-api which allows updating individual subscription for a member
- New method only allows toggling of cancellation at period end for a subscription at the moment
2020-08-20 14:24:29 +05:30
Fabien O'Carroll
66b099222f Fixed throttling of Stripe API requests
no-issue

This ensures any requests during exponential backoff are correctly rate
limited too
2020-08-18 11:28:15 +01:00
Fabien O'Carroll
2d347cd5fd Fixed LeakyBucket params for test and live mode
no-issue

These were the wrong way round initially, and not caught when testing
with live api keys
2020-08-18 10:38:26 +01:00
Fabien 'egg' O'Carroll
c7ea226d9e Updated stripe module for the bulk importer (#196)
no-issue

* Added LeakyBucket rate limiting for all Stripe requests
* Added createCustomer method
* Added createComplimentarySubscription method
* Replaced getStripeCustomer with getCustomer
* Exported createStripeCustomer & createComplimentarySubscription
2020-08-17 17:35:18 +01:00
Fabien 'egg' O'Carroll
117309b4e8 Used models internally and for exported API (#195)
no-issue

Using models internally and in the exported API means that we avoid expensive
`toJSON` calls, which affects performance when looping through large lists of
members. It also allows us to take advantage of the new relations used in the
models.

The addition of "ByID" methods for linking stripe customers and setting
complimentary subscriptions allows bulk imports to avoid the overhead of creating
a model for each members, instead passing an id string. n.b. currently the impl
_does_ still create models, but it makes it easier to optimise and refactor in the 
future.
2020-08-12 12:57:28 +01:00
Fabien 'egg' O'Carroll
e7484638e3 Ensured that we do not insert orphaned rows (#190)
no-issue

Previously we would blindly put subscriptions into the database when we
received a webhook, which could result in orphaned rows that were not
linked to a customer (and by extension a member)

This updates the logic so that we will only add subscriptions if we have
a record of their customer.

Customers are only added during a checkout.session.completed webhook, at
which point a member is guarunteed, but for formailty and safety against
changes in the flow, the logic has been applied to inserting customers
too.
2020-07-24 15:39:01 +02:00
Fabien O'Carroll
d63484e99a Handled subscription deletion errors with logging
refs https://github.com/TryGhost/Ghost/issues/11557

If a subscription failed to delete, we would error and bailout of the
process, this updates it to log the error so that site owners have a
record of the error in the logs, but also to continue through the rest
of the subscriptions.
2020-07-24 13:46:38 +02:00
Fabien O'Carroll
b435d6a8c1 Renamed destroyStripeSubscriptions to cancelStripeSubscriptions
no-issue

Destroy is terminology we usually use for the model layer and was a
little confusing without context, this method is used in one place so
it's a low effort cleanup with minimal repercussions
2020-07-24 13:46:38 +02:00
Fabien 'egg' O'Carroll
bf38d836d4 Updated webhooks cleanup to handle all older webhooks (#186)
refs https://github.com/TryGhost/Ghost/issues/12074

Some sites may have had duplicate webhooks created due to a race
condition. This updates the members-api to cleanup _all_ webhooks before
starting, allowing it to create webhooks on a fresh slate, and removing
possible causes of 401 errors due to incorrect webhook secrets.
2020-07-22 12:27:48 +02:00
Fabien 'egg' O'Carroll
d1cd0fe80e Caught & handled 'resource_already_exists' errors (#185)
refs https://github.com/TryGhost/Ghost/issues/12065

This protects us against multiple instances of the members-api being
started simultaneously and race conditions where inbetween the initial
"GET" of a plan which returns empty, and the "POST" of a plan to create
it, another instance has already created it.
2020-07-21 13:40:49 +02:00
Fabien 'egg' O'Carroll
400dba62a9 Added cleanup on startup for old webhooks (#181)
refs https://github.com/TryGhost/Ghost/issues/12061

Due to a bug in Ghost webhooks are now created with a trailing "/" which
meant that the previous webhooks to that (without a slash) was never
removed.

This results in users receiving emails from stripe about failed webhook
delivery, which is not good at all.

This fix lists out the webhooks and finds (if present) the webhook which
matches the current URL, minus the trailing slash. If found it will then
attempt to delete that webhook thus stopping the emails from Stripe.

I've added a note to remove this code as it should only ever need to run
once, and can be removed for the Ghost release after these changes.
2020-07-20 17:54:22 +02:00
Fabien 'egg' O'Carroll
ac923af0f7 Refactored webhook creation (#175)
no-issue

* Refactored model dependencies
  This groups all of the model depenencies into a single models object,
  and renames the models with more concise identifiers

* Fixed spacing
* Added webhook support to metadata
* Refactored stripe configure to have better logging
* Refactored webhook creation to reuse existing webhook
* Installed @types/stripe
2020-07-09 16:40:48 +02:00
Hannah Wolfe
ebaf9538b6 Adding INR currency support
- We have many customers asking for INR as there are special rules in Stripe for this currency
- As well as a desire for local-selling
- Meaning it's not valid to use e.g. USD instead
2020-06-12 08:58:49 +01:00
Nazar Gargol
d83525b54b Added stripe customer fetching method to member's API
no issue

- This method is needed to be able to validate if customer exist in configured Stripe account before attempting to link one with local member.
2020-06-12 15:35:16 +12:00
Fabien 'egg' O'Carroll
48260dedba Used plans trial by default for checkout sessions (#158)
no-issue

Without this flag the checkout session will ignore any default trial periods
attached to the plan. Now we are able to give basic support for trials, by
attaching a trial period in Stripe Dashboard
2020-05-21 10:14:36 +02:00
Rish
a1f29d8ede Updated member update method
no issue

- Makes passing `name` and `note` field in member update data as optional instead of making them undefined
- Allows email to be updated
- Adds stripe subscriptions list to updated member's response data to make update consistent with get method
2020-05-19 20:35:36 +05:30
Rish
2f90c97629 Added metadata option to stripe checkout session
refs TryGhost/members.js#29

- Allows passing metadata to checkout session API
- Metadata is passed to stripe's checkout session on creation and read back from webhook event
- Allows clients like members.js to pass custom info like member name to Stripe flow
2020-05-19 13:54:25 +05:30
Rishabh Garg
b015a08c43 Added plan update option to stripe subscription update API (#154)
no issue

- Current update stripe subscription API calls only allowed cancelling a plan
- This change adds option to pass plan's nickname as `planName` in request to update subscription to new plan
- Checks if plan name is valid and updates stripe subscription to new plan at default prorate behavior
2020-05-19 12:59:39 +05:30
Rish
fac6c3d97e Added ability to prefill customer email for anonymous checkouts
refs https://github.com/TryGhost/members.js/issues/10

- Allows passing an additional `customerEmail` value to our checkout creation API
- This value is used to pass `customer_email` option to stripe's checkout session - https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-customer_email.

The `customer_email` allows pre-filling the customer's email field in case of an anonymous checkout as customer doesn't exist already, and also ensures the stripe subscription is created with same email address as given by user during signup flow.
2020-04-30 16:01:22 +05:30
Nazar Gargol
076e328f20 Added currency and currency_symbol properties to plans
no issue

- Adding these properties allows specifying which currency is currently used on member's plan.
- Supported currencies list: USD, AUD, CAD, GBP, EUR
- They were chosen based on the most used/requested currencies within Ghost
- With adding multiple available currencies that can be setup also had to add handling of Stripes limitation of having single currency per paying customer
2020-03-04 11:33:19 +08:00
Kevin Ansfield
615a482c48 Store geolocation data during member signup/signin (#128)
requires f38d490886

- adds `lib/geolocation.js` with `getGeolocationFromIP()` function which uses https://geojs.io to lookup geolocation data from an IPv4 or IPv6 address
- updates `create/updateMember()` functions to work with a `geolocation` property in the passed in object
  - if `geolocation` is `undefined` when updating a member do not reset any existing property
- updates `sendMagicLink` middleware to extract the IP address from the request and stores it as part of the token payload
- updates `getMemberDataFromMagicLinkToken()` method to extract the IP address from the token payload and perform a geolocation lookup if we have an IP address and a matching member does not already have geolocation data
2020-02-27 10:29:36 +00:00
Nazar Gargol
9a783f9f0c Revert "Added precaution to avoid creating multiple Complimentary plans"
This reverts commit 5f0d2168f3.

After discussing the best approach to multipe currency problem would be
to allow creating multiple "Complimentary" plans. All security related
checks should stay strictly based on name and would not cause issues.
2020-02-27 16:55:03 +08:00
Nazar Gargol
5f0d2168f3 Added precaution to avoid creating multiple Complimentary plans
refs https://github.com/TryGhost/Ghost-Admin/pull/1430

- When the client creates a complimentary plan with other currency than USD we should not allow for it to avoid creating a mess in the Stripe plans
2020-02-27 13:53:05 +08:00
Naz
b34b7bfa9c Added middleware to handle billing updates (#122)
refs https://github.com/TryGhost/Ghost/pull/11571 

- Allows updating members billing information through Stripe's setup intent (stripe.com/docs/payments/checkout/subscriptions/updating#set)
- Accepts 2 new parameter to handle redirects specific to billing update.
2020-02-26 12:09:09 +08:00
Rishabh Garg
789462aa5f Added labels to member signup flow (#124)
no issue

refs https://github.com/TryGhost/Ghost/pull/11538
2020-02-12 16:42:49 +05:30
Nazar Gargol
a669cda605 Added fallback plan nickname to inteval instead of empty string
no issue

- On model layer in Ghost empty string is always converted to `null` for not nullable fields, which wasn't letting the value through to the database
- Current solution is a stopgap to fix imports of cyclic plans without nicknames. Ideally nickname field should become nullable in the future so this logic can be simplified
2020-02-11 14:02:40 +08:00
Naz
f2a7790cc9 Added plan nickname fallback to empty string (#126)
no issue

- This solves a problem when connected Stripe plan doesn't have plan `nickname` filled out (possible with older versions of Stripe API)
- Defaulting to empty string instead of creating a migration because SQLite doesn't support `ALTER ... MODIFY` syntax and thus knex can't altter the table that easy
- "Marks the column as an alter / modify, instead of the default add. Note: This only works in .alterTable() and is not supported by SQlite or Amazon Redshift. Alter is not done incrementally over older column type so if you like to add notNull and keep the old default value, the alter statement must contain both .notNull().defaultTo(1).alter(). If one just tries to add .notNull().alter() the old default value will be dropped." (ref. https://knexjs.org/#Chainable)
2020-02-10 18:59:52 +08:00
Naz Gargol
96aea55270 Added ability to link member to existing stripe customer (#120)
refs https://github.com/TryGhost/Ghost/pull/11539

- Method needed to allow linking existing Stripe customers and subscriptions with members
2020-01-28 19:00:28 +07:00
Naz Gargol
28d3a37824 Added "complimentary" subscription handling (#118)
refs https://github.com/TryGhost/Ghost/pull/11537

- Adds ability to assign and cancel "complimentary" type of subscriptions to the member
- The functionality is needed to be able to provide free premium plans for members (e.g. family members, trials, gifts)
- When member already has an active paid subscription and complimentary one is applied the old one is upgraded. Proration is not given
- When deleting a subscription we need to update localy stored records right away to be albe to reflect the change in the UI. This behavior will also be in line with how subscriptions updates/creates are handled
- Blocked any client update for complimentary subscription. We should prevent non authenticated clients from upgrading/subscribing themselves to "complimentary" plan.
2020-01-27 12:34:22 +07:00
Nazar Gargol
726ffaf1f8 🐛 Fixed creation of extra customer when updating plans
no issue

- `customers` property contains an array of customer for which 'for..of' syntax is more appropriate
- Bug was causing creation of multiple customers in Stripe when new checkout session was initiated for existing customer
- Discussed in https://github.com/TryGhost/Members/pull/90/files#r368889289
2020-01-22 12:53:27 +07:00
Rishabh Garg
a1ad80f6ac 🐛 Fixed incorrect fetch of empty stripe subscriptions (#116)
no issue

refs e19e06f9b3

While refactoring user CRUD for Ghost core, we inadvertently changed the members subscriptions object returned by nesting the value as object. This also broke the deserialization in Ghost-Admin for members subscription object [here](https://github.com/TryGhost/Ghost-Admin/blob/master/app/transforms/member-subscription.js#L9).
2020-01-20 13:28:59 +05:30
Naz Gargol
e19e06f9b3 Refactored user CRUD to be usable by Ghost core (#113)
refs https://github.com/TryGhost/Members/pull/105

- It's a follow up to a series of refactorings in the module mostly discussed in refed PR
- The sendEmailWithMagicLink and destroyStripeSubscriptions were exposed through members API so that Ghost  could call it from the controller level
2020-01-15 15:35:15 +07:00
Nazar Gargol
46f6ce8db3 Removed console.log statement in favor of common logger
no issue

- Using console is a very bad practice and probably was left here by mistake. Using common logging instead
2020-01-13 19:16:51 +07:00
Nazar Gargol
3b14e7c1fa Removed redundant empty string handling logic in users module
no issue

- Since the Member model started to be used the logic handling empty strings -> null conversion is now handled in the core here https://github.com/TryGhost/Ghost/blob/8fd1e81/core/server/models/base/index.js#L492-L499
2020-01-13 18:49:25 +07:00
Nazar Gargol
08fbcf25ec Extracted metadata get/set methods into internal metadata module
no issue

- This is the refactor similar to what has been done with Memeber model being passed in directly in the constructor
- Relevent discussion here https://github.com/TryGhost/Members/pull/105#pullrequestreview-324254267
2020-01-13 15:45:22 +07:00
Naz Gargol
ff5fceafc8 Added subscription update middleware (#107)
refs #https://github.com/TryGhost/Ghost/pull/11434

- Added method to allow updating single subscription. Only `cancel_at_period_end` field can be updated. 
- Middleware is needed to allow Ghost Core to cancel/uncancel member's subscription. 
- Relies on the request containing identity information to be able to verify if subscription belongs to the user
- When member could not be identified by the identity information present in the request we should throw instead of continuing processing
- Handling and messaging inspired by https://github.com/TryGhost/Ghost/blob/3.1.1/core/server/services/mega/mega.js#L132
- When the user initiates subscription cancellation we can safely mark the subscription as canceled so that it's not shown in the interface on subsequent request. Otherwise, we end up in a situation where we still return the subscription in the period until Stripe triggers the webhook.
- Added boolean coercion for cancel_at_period_end parameter. If anything but boolean is passed to Stripe API it throws an error.  Coercing the value on our side is a gives a better dev experience
2019-12-12 15:19:36 +07:00
Fabien O'Carroll
94ef530b3c Fixed bug in cancelAllSubscriptions
no-issue

We filter out previously cancelled subscriptions, but used the wrong string "cancelled" instead of "canceled"
https://stripe.com/docs/billing/lifecycle#subscription-states
2019-12-09 15:55:37 +02:00
Fabien O'Carroll
7db503b13b Fixed local webhooks when using localhost urls
no-issue

When using localhost urls the call to `create` will error and end in teh
catch block - so we need to use the environment variable there, too.

Introduced in 0149dd8f
2019-12-09 14:29:59 +02:00
Naz Gargol
3060e11a4e Changed members-api constructor to accept Member model directly (#105)
no issue

- As members have become a part of Ghost core there is no need to proxy methods like this anymore and we can allow members-api to work on the model directly
- Methods come from Ghost core: https://github.com/TryGhost/Ghost/blob/cc39786/core/server/services/members/api.js#L11-L110
2019-12-05 18:16:18 +07:00
Naz Gargol
0149dd8f4d Added priority to webhook secret if present in env (#103)
no issue

- When debugging Stripe with using: `stripe listen \
  --forward-to http://ghost.local/members/webhooks/stripe/` this priority is nice to have so that Ghost process can be initialized using WEBHOOK_SECRET env variable
- It was not working in current form because Stripe recognized `ghost.local` as a valid domain and didn't throw any errors
- Removed unneeded secret assignment in a catch statement. It is redundant with the new implementation
2019-11-25 13:15:28 +07:00
Fabien O'Carroll
19148dab4e Included subscription information when listing members
no-issue
2019-11-05 16:12:20 +07:00