mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +03:00
Added @tryghost/stripe-service
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.
This commit is contained in:
parent
eed346e4ec
commit
7e082b7eb5
6
ghost/stripe-service/.eslintrc.js
Normal file
6
ghost/stripe-service/.eslintrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/node'
|
||||
]
|
||||
};
|
21
ghost/stripe-service/LICENSE
Normal file
21
ghost/stripe-service/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-2021 Ghost Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
34
ghost/stripe-service/README.md
Normal file
34
ghost/stripe-service/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Payment Service
|
||||
|
||||
## Install
|
||||
|
||||
`npm install @tryghost/payment-service --save`
|
||||
|
||||
or
|
||||
|
||||
`yarn add @tryghost/payment-service`
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
## Develop
|
||||
|
||||
This is a mono repository, managed with [lerna](https://lernajs.io/).
|
||||
|
||||
Follow the instructions for the top-level repo.
|
||||
1. `git clone` this repo & `cd` into it as usual
|
||||
2. Run `yarn` to install top-level dependencies.
|
||||
|
||||
|
||||
## Run
|
||||
|
||||
- `yarn dev`
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
- `yarn lint` run just eslint
|
||||
- `yarn test` run lint and tests
|
||||
|
||||
|
1
ghost/stripe-service/index.js
Normal file
1
ghost/stripe-service/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./lib/StripeService');
|
582
ghost/stripe-service/lib/StripeService.js
Normal file
582
ghost/stripe-service/lib/StripeService.js
Normal file
@ -0,0 +1,582 @@
|
||||
const {VersionMismatchError} = require('@tryghost/errors');
|
||||
const debug = require('@tryghost/debug');
|
||||
const Stripe = require('stripe').Stripe;
|
||||
const LeakyBucket = require('leaky-bucket');
|
||||
const EXPECTED_API_EFFICIENCY = 0.95;
|
||||
|
||||
const STRIPE_API_VERSION = '2020-08-27';
|
||||
|
||||
/**
|
||||
* @typedef {import('stripe').Stripe.Customer} ICustomer
|
||||
* @typedef {import('stripe').Stripe.DeletedCustomer} IDeletedCustomer
|
||||
* @typedef {import('stripe').Stripe.Product} IProduct
|
||||
* @typedef {import('stripe').Stripe.Plan} IPlan
|
||||
* @typedef {import('stripe').Stripe.Price} IPrice
|
||||
* @typedef {import('stripe').Stripe.WebhookEndpoint} IWebhookEndpoint
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} ILogger
|
||||
* @prop {(x: any) => void} error
|
||||
* @prop {(x: any) => void} info
|
||||
* @prop {(x: any) => void} warn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} IStripeServiceConfig
|
||||
* @prop {string} secretKey
|
||||
* @prop {string} publicKey
|
||||
* @prop {object} appInfo
|
||||
* @prop {string} appInfo.name
|
||||
* @prop {string} appInfo.version
|
||||
* @prop {string} appInfo.partner_id
|
||||
* @prop {string} appInfo.url
|
||||
* @prop {boolean} enablePromoCodes
|
||||
*/
|
||||
|
||||
module.exports = class StripeService {
|
||||
/**
|
||||
* StripeService
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {ILogger} params.logger
|
||||
* @param {IStripeServiceConfig} params.config
|
||||
*/
|
||||
constructor({config, logger}) {
|
||||
/** @type {Stripe} */
|
||||
this._stripe = null;
|
||||
this.logging = logger;
|
||||
this._configured = false;
|
||||
if (config.secretKey) {
|
||||
this.configure(config);
|
||||
}
|
||||
}
|
||||
|
||||
get configured() {
|
||||
return this._configured;
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this._testMode ? 'test' : 'live';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {IStripeServiceConfig} config
|
||||
* @returns {void}
|
||||
*/
|
||||
configure(config) {
|
||||
this._stripe = new Stripe(config.secretKey, {
|
||||
apiVersion: STRIPE_API_VERSION
|
||||
});
|
||||
this._config = config;
|
||||
this._testMode = config.secretKey && config.secretKey.startsWith('sk_test_');
|
||||
if (this._testMode) {
|
||||
this._rateLimitBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 25, 1);
|
||||
} else {
|
||||
this._rateLimitBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 100, 1);
|
||||
}
|
||||
this._configured = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.name
|
||||
*
|
||||
* @returns {Promise<IProduct>}
|
||||
*/
|
||||
async createProduct(options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const product = await this._stripe.products.create(options);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.product
|
||||
* @param {boolean} options.active
|
||||
* @param {string} options.nickname
|
||||
* @param {string} options.currency
|
||||
* @param {number} options.amount
|
||||
* @param {'recurring'|'one-time'} options.type
|
||||
* @param {Stripe.Price.Recurring.Interval|null} options.interval
|
||||
*
|
||||
* @returns {Promise<IPrice>}
|
||||
*/
|
||||
async createPrice(options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const price = await this._stripe.prices.create({
|
||||
currency: options.currency,
|
||||
product: options.product,
|
||||
unit_amount: options.amount,
|
||||
active: options.active,
|
||||
nickname: options.nickname,
|
||||
recurring: options.type === 'recurring' ? {
|
||||
interval: options.interval
|
||||
} : undefined
|
||||
});
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
* @param {boolean} options.active
|
||||
* @param {string=} options.nickname
|
||||
*
|
||||
* @returns {Promise<IPrice>}
|
||||
*/
|
||||
async updatePrice(id, options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const price = await this._stripe.prices.update(id, {
|
||||
active: options.active,
|
||||
nickname: options.nickname
|
||||
});
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
* @param {string} options.name
|
||||
*
|
||||
* @returns {Promise<IProduct>}
|
||||
*/
|
||||
async updateProduct(id, options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const product = await this._stripe.products.update(id, {
|
||||
name: options.name
|
||||
});
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {import('stripe').Stripe.CustomerRetrieveParams} options
|
||||
*
|
||||
* @returns {Promise<ICustomer|IDeletedCustomer>}
|
||||
*/
|
||||
async getCustomer(id, options = {}) {
|
||||
debug(`getCustomer(${id}, ${JSON.stringify(options)})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
if (options.expand) {
|
||||
options.expand.push('subscriptions');
|
||||
} else {
|
||||
options.expand = ['subscriptions'];
|
||||
}
|
||||
const customer = await this._stripe.customers.retrieve(id, options);
|
||||
debug(`getCustomer(${id}, ${JSON.stringify(options)}) -> Success`);
|
||||
return customer;
|
||||
} catch (err) {
|
||||
debug(`getCustomer(${id}, ${JSON.stringify(options)}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param {any} member
|
||||
*
|
||||
* @returns {Promise<ICustomer>}
|
||||
*/
|
||||
async getCustomerForMemberCheckoutSession(member) {
|
||||
await member.related('stripeCustomers').fetch();
|
||||
const customers = member.related('stripeCustomers');
|
||||
for (const data of customers.models) {
|
||||
try {
|
||||
const customer = await this.getCustomer(data.get('customer_id'));
|
||||
if (!customer.deleted) {
|
||||
return /** @type {ICustomer} */(customer);
|
||||
}
|
||||
} catch (err) {
|
||||
debug(`Ignoring Error getting customer for member ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
debug(`Creating customer for member ${member.get('email')}`);
|
||||
const customer = await this.createCustomer({
|
||||
email: member.get('email')
|
||||
});
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('stripe').Stripe.CustomerCreateParams} options
|
||||
*
|
||||
* @returns {Promise<ICustomer>}
|
||||
*/
|
||||
async createCustomer(options = {}) {
|
||||
debug(`createCustomer(${JSON.stringify(options)})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const customer = await this._stripe.customers.create(options);
|
||||
debug(`createCustomer(${JSON.stringify(options)}) -> Success`);
|
||||
return customer;
|
||||
} catch (err) {
|
||||
debug(`createCustomer(${JSON.stringify(options)}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} email
|
||||
*
|
||||
* @returns {Promise<ICustomer>}
|
||||
*/
|
||||
async updateCustomerEmail(id, email) {
|
||||
debug(`updateCustomerEmail(${id}, ${email})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const customer = await this._stripe.customers.update(id, {email});
|
||||
debug(`updateCustomerEmail(${id}, ${email}) -> Success`);
|
||||
return customer;
|
||||
} catch (err) {
|
||||
debug(`updateCustomerEmail(${id}, ${email}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createWebhook.
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {import('stripe').Stripe.WebhookEndpointUpdateParams.EnabledEvent[]} events
|
||||
*
|
||||
* @returns {Promise<IWebhookEndpoint>}
|
||||
*/
|
||||
async createWebhookEndpoint(url, events) {
|
||||
debug(`createWebhook(${url})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const webhook = await this._stripe.webhookEndpoints.create({
|
||||
url,
|
||||
enabled_events: events,
|
||||
api_version: STRIPE_API_VERSION
|
||||
});
|
||||
debug(`createWebhook(${url}) -> Success`);
|
||||
return webhook;
|
||||
} catch (err) {
|
||||
debug(`createWebhook(${url}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deleteWebhookEndpoint(id) {
|
||||
debug(`deleteWebhook(${id})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
await this._stripe.webhookEndpoints.del(id);
|
||||
debug(`deleteWebhook(${id}) -> Success`);
|
||||
return;
|
||||
} catch (err) {
|
||||
debug(`deleteWebhook(${id}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} url
|
||||
* @param {import('stripe').Stripe.WebhookEndpointUpdateParams.EnabledEvent[]} events
|
||||
*
|
||||
* @returns {Promise<IWebhookEndpoint>}
|
||||
*/
|
||||
async updateWebhookEndpoint(id, url, events) {
|
||||
debug(`updateWebhook(${id}, ${url})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const webhook = await this._stripe.webhookEndpoints.update(id, {
|
||||
url,
|
||||
enabled_events: events
|
||||
});
|
||||
if (webhook.api_version !== STRIPE_API_VERSION) {
|
||||
throw new VersionMismatchError('Webhook has incorrect api_version');
|
||||
}
|
||||
debug(`updateWebhook(${id}, ${url}) -> Success`);
|
||||
return webhook;
|
||||
} catch (err) {
|
||||
debug(`updateWebhook(${id}, ${url}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parseWebhook.
|
||||
*
|
||||
* @param {string} body
|
||||
* @param {string} signature
|
||||
* @param {string} secret
|
||||
*
|
||||
* @returns {import('stripe').Stripe.Event}
|
||||
*/
|
||||
parseWebhook(body, signature, secret) {
|
||||
debug(`parseWebhook(${body}, ${signature}, ${secret})`);
|
||||
try {
|
||||
const event = this._stripe.webhooks.constructEvent(body, signature, secret);
|
||||
debug(`parseWebhook(${body}, ${signature}, ${secret}) -> Success ${event.type}`);
|
||||
return event;
|
||||
} catch (err) {
|
||||
debug(`parseWebhook(${body}, ${signature}, ${secret}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} priceId
|
||||
* @param {ICustomer} customer
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {Object.<String, any>} options.metadata
|
||||
* @param {string} options.successUrl
|
||||
* @param {string} options.cancelUrl
|
||||
* @param {string} options.customerEmail
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Checkout.Session>}
|
||||
*/
|
||||
async createCheckoutSession(priceId, customer, options) {
|
||||
const metadata = options.metadata || undefined;
|
||||
const customerEmail = customer ? customer.email : options.customerEmail;
|
||||
await this._rateLimitBucket.throttle();
|
||||
const session = await this._stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
success_url: options.successUrl,
|
||||
cancel_url: options.cancelUrl,
|
||||
customer_email: customerEmail,
|
||||
// @ts-ignore - we need to update to latest stripe library to correctly use newer features
|
||||
allow_promotion_codes: this._config.enablePromoCodes,
|
||||
metadata,
|
||||
/*
|
||||
line_items: [{
|
||||
price: priceId
|
||||
}]
|
||||
*/
|
||||
// This is deprecated and using the old way of doing things with Plans.
|
||||
// It should be replaced with the line_items entry above when possible,
|
||||
// however, this would lose the "trial from plan" feature which has also
|
||||
// been deprecated by Stripe
|
||||
subscription_data: {
|
||||
trial_from_plan: true,
|
||||
items: [{
|
||||
plan: priceId
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ICustomer} customer
|
||||
* @param {object} options
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Checkout.Session>}
|
||||
*/
|
||||
async createCheckoutSetupSession(customer, options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const session = await this._stripe.checkout.sessions.create({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
success_url: options.successUrl,
|
||||
cancel_url: options.cancelUrl,
|
||||
customer_email: customer.email,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customer.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
getPublicKey() {
|
||||
return this._config.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* getPrice
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Price>}
|
||||
*/
|
||||
async getPrice(id, options = {}) {
|
||||
debug(`getPrice(${id}, ${JSON.stringify(options)})`);
|
||||
|
||||
return await this._stripe.prices.retrieve(id, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSubscription.
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {import('stripe').Stripe.SubscriptionRetrieveParams} options
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async getSubscription(id, options = {}) {
|
||||
debug(`getSubscription(${id}, ${JSON.stringify(options)})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.retrieve(id, options);
|
||||
debug(`getSubscription(${id}, ${JSON.stringify(options)}) -> Success`);
|
||||
return subscription;
|
||||
} catch (err) {
|
||||
debug(`getSubscription(${id}, ${JSON.stringify(options)}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cancelSubscription.
|
||||
*
|
||||
* @param {string} id
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async cancelSubscription(id) {
|
||||
debug(`cancelSubscription(${id})`);
|
||||
try {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.del(id);
|
||||
debug(`cancelSubscription(${id}) -> Success`);
|
||||
return subscription;
|
||||
} catch (err) {
|
||||
debug(`cancelSubscription(${id}) -> ${err.type}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id - The ID of the Subscription to modify
|
||||
* @param {string} [reason=''] - The user defined cancellation reason
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async cancelSubscriptionAtPeriodEnd(id, reason = '') {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.update(id, {
|
||||
cancel_at_period_end: true,
|
||||
metadata: {
|
||||
cancellation_reason: reason
|
||||
}
|
||||
});
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id - The ID of the Subscription to modify
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async continueSubscriptionAtPeriodEnd(id) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.update(id, {
|
||||
cancel_at_period_end: false,
|
||||
metadata: {
|
||||
cancellation_reason: null
|
||||
}
|
||||
});
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} subscriptionId - The ID of the Subscription to modify
|
||||
* @param {string} id - The ID of the SubscriptionItem
|
||||
* @param {string} price - The ID of the new Price
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async updateSubscriptionItemPrice(subscriptionId, id, price) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.update(subscriptionId, {
|
||||
items: [{
|
||||
id,
|
||||
price
|
||||
}],
|
||||
cancel_at_period_end: false,
|
||||
metadata: {
|
||||
cancellation_reason: null
|
||||
}
|
||||
});
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} customer - The ID of the Customer to create the subscription for
|
||||
* @param {string} price - The ID of the new Price
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async createSubscription(customer, price) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const subscription = await this._stripe.subscriptions.create({
|
||||
customer,
|
||||
items: [{price}]
|
||||
});
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {import('stripe').Stripe.SetupIntentRetrieveParams} options
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.SetupIntent>}
|
||||
*/
|
||||
async getSetupIntent(id, options = {}) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
return await this._stripe.setupIntents.retrieve(id, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} customer
|
||||
* @param {string} paymentMethod
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async attachPaymentMethodToCustomer(customer, paymentMethod) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
await this._stripe.paymentMethods.attach(paymentMethod, {customer});
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.PaymentMethod|null>}
|
||||
*/
|
||||
async getCardPaymentMethod(id) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const paymentMethod = await this._stripe.paymentMethods.retrieve(id);
|
||||
if (paymentMethod.type !== 'card') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} subscription
|
||||
* @param {string} paymentMethod
|
||||
*
|
||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||
*/
|
||||
async updateSubscriptionDefaultPaymentMethod(subscription, paymentMethod) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
return await this._stripe.subscriptions.update(subscription, {
|
||||
default_payment_method: paymentMethod
|
||||
});
|
||||
}
|
||||
};
|
33
ghost/stripe-service/package.json
Normal file
33
ghost/stripe-service/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@tryghost/stripe-service",
|
||||
"version": "0.0.0",
|
||||
"repository": "https://github.com/TryGhost/Members/tree/main/packages/stripe-service",
|
||||
"author": "Ghost Foundation",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "echo \"Implement me!\"",
|
||||
"test": "NODE_ENV=testing c8 --check-coverage mocha './test/**/*.test.js'",
|
||||
"lint": "eslint . --ext .js --cache",
|
||||
"posttest": "yarn lint"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "7.8.0",
|
||||
"mocha": "9.1.1",
|
||||
"should": "13.2.3",
|
||||
"sinon": "11.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tryghost/debug": "^0.1.4",
|
||||
"@tryghost/errors": "^0.2.13",
|
||||
"leaky-bucket": "^2.2.0",
|
||||
"stripe": "^8.174.0"
|
||||
}
|
||||
}
|
6
ghost/stripe-service/test/.eslintrc.js
Normal file
6
ghost/stripe-service/test/.eslintrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/test'
|
||||
]
|
||||
};
|
10
ghost/stripe-service/test/hello.test.js
Normal file
10
ghost/stripe-service/test/hello.test.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Switch these lines once there are useful utils
|
||||
// const testUtils = require('./utils');
|
||||
require('./utils');
|
||||
|
||||
describe('Hello world', function () {
|
||||
it('Runs a test', function () {
|
||||
// TODO: Write me!
|
||||
'hello'.should.eql('hello');
|
||||
});
|
||||
});
|
11
ghost/stripe-service/test/utils/assertions.js
Normal file
11
ghost/stripe-service/test/utils/assertions.js
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Custom Should Assertions
|
||||
*
|
||||
* Add any custom assertions to this file.
|
||||
*/
|
||||
|
||||
// Example Assertion
|
||||
// should.Assertion.add('ExampleAssertion', function () {
|
||||
// this.params = {operator: 'to be a valid Example Assertion'};
|
||||
// this.obj.should.be.an.Object;
|
||||
// });
|
11
ghost/stripe-service/test/utils/index.js
Normal file
11
ghost/stripe-service/test/utils/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Test Utilities
|
||||
*
|
||||
* Shared utils for writing tests
|
||||
*/
|
||||
|
||||
// Require overrides - these add globals for tests
|
||||
require('./overrides');
|
||||
|
||||
// Require assertions - adds custom should assertions
|
||||
require('./assertions');
|
10
ghost/stripe-service/test/utils/overrides.js
Normal file
10
ghost/stripe-service/test/utils/overrides.js
Normal file
@ -0,0 +1,10 @@
|
||||
// This file is required before any test is run
|
||||
|
||||
// Taken from the should wiki, this is how to make should global
|
||||
// Should is a global in our eslint test config
|
||||
global.should = require('should').noConflict();
|
||||
should.extend();
|
||||
|
||||
// Sinon is a simple case
|
||||
// Sinon is a global in our eslint test config
|
||||
global.sinon = require('sinon');
|
1088
ghost/stripe-service/yarn.lock
Normal file
1088
ghost/stripe-service/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user