Ghost/ghost/members-api/subscriptions/payment-processors/stripe/api.js
Fabien O'Carroll f99d66d8b9 Passed coupon from metadata through to stripe
no-issue

This will allow us to send through coupons from the api layer and have
stripe handle the rest :)
2019-05-07 17:35:17 +02:00

162 lines
4.9 KiB
JavaScript

const hash = data => require('crypto').createHash('sha256').update(data).digest('hex');
const isActive = x => x.active;
const isNotDeleted = x => !x.deleted;
const getPlanAttr = ({name, amount, interval, currency}, product) => ({
nickname: name,
amount,
interval,
currency,
product: product.id,
billing_scheme: 'per_unit'
});
const getProductAttr = ({name}) => ({name, type: 'service'});
const getCustomerAttr = ({email}) => ({email});
const getPlanHashSeed = (plan, product) => {
return product.id + plan.interval + plan.currency + plan.amount;
};
const getProductHashSeed = () => 'Ghost Subscription';
const getCustomerHashSeed = member => member.email;
const plans = createApi('plans', isActive, getPlanAttr, getPlanHashSeed);
const products = createApi('products', isActive, getProductAttr, getProductHashSeed);
const customers = createApi('customers', isNotDeleted, getCustomerAttr, getCustomerHashSeed);
function removeSubscription(stripe, member) {
return customers.get(stripe, member, member.email).then((customer) => {
// CASE customer has no subscriptions
if (!customer.subscriptions || customer.subscriptions.total_count === 0) {
throw new Error('Cannot remove subscription');
}
const subscription = customer.subscriptions.data[0];
return stripe.subscriptions.del(subscription.id);
});
}
function getSubscription(stripe, member) {
return customers.get(stripe, member, member.email).then((customer) => {
// CASE customer has either none or multiple subscriptions
if (!customer.subscriptions || customer.subscriptions.total_count !== 1) {
return {};
}
const subscription = customer.subscriptions.data[0];
// CASE subscription has multiple plans
if (subscription.items.total_count !== 1) {
return {};
}
const plan = subscription.plan;
return {
validUntil: subscription.current_period_end,
plan: plan.nickname,
amount: plan.amount,
status: subscription.status
};
}).catch(() => {
return {};
});
}
function createSubscription(stripe, member, metadata) {
return customers.ensure(stripe, member, member.email).then((customer) => {
if (customer.subscriptions && customer.subscriptions.total_count !== 0) {
throw new Error('Customer already has a subscription');
}
return stripe.customers.createSource(customer.id, {
source: metadata.stripeToken
}).then(() => {
return stripe.subscriptions.create({
customer: customer.id,
items: [{plan: metadata.plan.id}],
coupon: metadata.coupon
});
});
});
}
const subscriptions = {
create: createSubscription,
get: getSubscription,
remove: removeSubscription
};
module.exports = {
plans,
products,
customers,
subscriptions
};
function createGetter(resource, validResult) {
return function get(stripe, object, idSeed) {
const id = hash(idSeed);
return stripe[resource].retrieve(id)
.then((result) => {
if (validResult(result)) {
return result;
}
return get(stripe, object, id);
}, (err) => {
err.id_requested = id;
throw err;
});
};
}
function createCreator(resource, getAttrs) {
return function create(stripe, id, object, ...rest) {
return stripe[resource].create(
Object.assign(getAttrs(object, ...rest), {id})
);
};
}
function createRemover(resource, get, generateHashSeed) {
return function remove(stripe, object, ...rest) {
return get(stripe, object, generateHashSeed(object, ...rest)).then((res) => {
return stripe[resource].del(res.id).then((result) => {
return result;
}, (err) => {
throw err;
});
}).catch((err) => {
if (err.code !== 'resource_missing') {
throw err;
}
});
};
}
function createEnsurer(get, create, generateHashSeed) {
return function ensure(stripe, object, ...rest) {
return get(stripe, object, generateHashSeed(object, ...rest))
.catch((err) => {
if (err.code !== 'resource_missing') {
throw err;
}
const id = err.id_requested;
return create(stripe, id, object, ...rest);
});
};
}
function createApi(resource, validResult, getAttrs, generateHashSeed) {
const get = createGetter(resource, validResult);
const create = createCreator(resource, getAttrs);
const remove = createRemover(resource, get, generateHashSeed);
const ensure = createEnsurer(get, create, generateHashSeed);
return {
get, create, remove, ensure
};
}