mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Updated subscriptions for Members Admin API
refs https://github.com/TryGhost/Team/issues/616 We need a way to assign Products to Members via a Subscription, and we've followed the same pattern as the editSubscription method for the Members API controller, which acts upon Subscriptions as a nested resource. Subscriptions now are linked to products, and we've included those links by default in the Member Admin API as we already include subscriptions by default, and Products are now a core part of the Members feature-set.
This commit is contained in:
parent
7bce05ab86
commit
33f26fbf32
5 changed files with 87 additions and 12 deletions
|
@ -40,7 +40,7 @@ module.exports = {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
validation: {},
|
validation: {},
|
||||||
async query(frame) {
|
async query(frame) {
|
||||||
frame.options.withRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer'];
|
frame.options.withRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
||||||
const page = await membersService.api.members.list(frame.options);
|
const page = await membersService.api.members.list(frame.options);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
|
@ -65,7 +65,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
permissions: true,
|
permissions: true,
|
||||||
async query(frame) {
|
async query(frame) {
|
||||||
const defaultWithRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer'];
|
const defaultWithRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
||||||
|
|
||||||
if (!frame.options.withRelated) {
|
if (!frame.options.withRelated) {
|
||||||
frame.options.withRelated = defaultWithRelated;
|
frame.options.withRelated = defaultWithRelated;
|
||||||
|
@ -109,7 +109,7 @@ module.exports = {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
async query(frame) {
|
async query(frame) {
|
||||||
let member;
|
let member;
|
||||||
frame.options.withRelated = ['stripeSubscriptions', 'stripeSubscriptions.customer'];
|
frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
||||||
try {
|
try {
|
||||||
if (!membersService.config.isStripeConnected()
|
if (!membersService.config.isStripeConnected()
|
||||||
&& (frame.data.members[0].stripe_customer_id || frame.data.members[0].comped)) {
|
&& (frame.data.members[0].stripe_customer_id || frame.data.members[0].comped)) {
|
||||||
|
@ -185,7 +185,7 @@ module.exports = {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
async query(frame) {
|
async query(frame) {
|
||||||
try {
|
try {
|
||||||
frame.options.withRelated = ['stripeSubscriptions', 'labels'];
|
frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
||||||
const member = await membersService.api.members.update(frame.data.members[0], frame.options);
|
const member = await membersService.api.members.update(frame.data.members[0], frame.options);
|
||||||
|
|
||||||
const hasCompedSubscription = !!member.related('stripeSubscriptions').find(sub => sub.get('plan_nickname') === 'Complimentary' && sub.get('status') === 'active');
|
const hasCompedSubscription = !!member.related('stripeSubscriptions').find(sub => sub.get('plan_nickname') === 'Complimentary' && sub.get('status') === 'active');
|
||||||
|
@ -255,7 +255,51 @@ module.exports = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let model = await membersService.api.members.get({id: frame.options.id}, {
|
let model = await membersService.api.members.get({id: frame.options.id}, {
|
||||||
withRelated: ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer']
|
withRelated: ['labels', 'products', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']
|
||||||
|
});
|
||||||
|
if (!model) {
|
||||||
|
throw new errors.NotFoundError({
|
||||||
|
message: i18n.t('errors.api.members.memberNotFound')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createSubscription: {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
options: [
|
||||||
|
'id'
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
'stripe_price_id'
|
||||||
|
],
|
||||||
|
validation: {
|
||||||
|
options: {
|
||||||
|
id: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
stripe_price_id: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
method: 'edit'
|
||||||
|
},
|
||||||
|
async query(frame) {
|
||||||
|
await membersService.api.members.createSubscription({
|
||||||
|
id: frame.options.id,
|
||||||
|
subscription: {
|
||||||
|
stripe_price_id: frame.data.stripe_price_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let model = await membersService.api.members.get({id: frame.options.id}, {
|
||||||
|
withRelated: ['labels', 'products', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']
|
||||||
});
|
});
|
||||||
if (!model) {
|
if (!model) {
|
||||||
throw new errors.NotFoundError({
|
throw new errors.NotFoundError({
|
||||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
||||||
edit: createSerializer('edit', singleMember),
|
edit: createSerializer('edit', singleMember),
|
||||||
add: createSerializer('add', singleMember),
|
add: createSerializer('add', singleMember),
|
||||||
editSubscription: createSerializer('editSubscription', singleMember),
|
editSubscription: createSerializer('editSubscription', singleMember),
|
||||||
|
createSubscription: createSerializer('createSubscription', singleMember),
|
||||||
bulkDestroy: createSerializer('bulkDestroy', passthrough),
|
bulkDestroy: createSerializer('bulkDestroy', passthrough),
|
||||||
|
|
||||||
exportCSV: createSerializer('exportCSV', exportCSV),
|
exportCSV: createSerializer('exportCSV', exportCSV),
|
||||||
|
@ -197,11 +198,16 @@ function createSerializer(debugString, serialize) {
|
||||||
* @prop {null|string} customer.name
|
* @prop {null|string} customer.name
|
||||||
* @prop {string} customer.email
|
* @prop {string} customer.email
|
||||||
*
|
*
|
||||||
* @prop {Object} plan
|
* @prop {Object} price
|
||||||
* @prop {string} plan.id
|
* @prop {string} price.id
|
||||||
* @prop {string} plan.nickname
|
* @prop {string} price.nickname
|
||||||
* @prop {number} plan.amount
|
* @prop {number} price.amount
|
||||||
* @prop {string} plan.currency
|
* @prop {string} price.interval
|
||||||
|
* @prop {string} price.currency
|
||||||
|
*
|
||||||
|
* @prop {Object} price.product
|
||||||
|
* @prop {string} price.product.id
|
||||||
|
* @prop {string} price.product.product_id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,10 +7,14 @@ const StripeCustomerSubscription = ghostBookshelf.Model.extend({
|
||||||
return this.belongsTo('MemberStripeCustomer', 'customer_id', 'customer_id');
|
return this.belongsTo('MemberStripeCustomer', 'customer_id', 'customer_id');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stripePrice() {
|
||||||
|
return this.hasOne('StripePrice', 'stripe_price_id', 'stripe_price_id');
|
||||||
|
},
|
||||||
|
|
||||||
serialize(options) {
|
serialize(options) {
|
||||||
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);
|
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);
|
||||||
|
|
||||||
return {
|
const serialized = {
|
||||||
id: defaultSerializedObject.subscription_id,
|
id: defaultSerializedObject.subscription_id,
|
||||||
customer: {
|
customer: {
|
||||||
id: defaultSerializedObject.customer_id,
|
id: defaultSerializedObject.customer_id,
|
||||||
|
@ -32,6 +36,26 @@ const StripeCustomerSubscription = ghostBookshelf.Model.extend({
|
||||||
cancellation_reason: defaultSerializedObject.cancellation_reason,
|
cancellation_reason: defaultSerializedObject.cancellation_reason,
|
||||||
current_period_end: defaultSerializedObject.current_period_end
|
current_period_end: defaultSerializedObject.current_period_end
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (defaultSerializedObject.stripePrice) {
|
||||||
|
serialized.price = {
|
||||||
|
id: defaultSerializedObject.stripePrice.stripe_price_id,
|
||||||
|
nickname: defaultSerializedObject.stripePrice.nickname,
|
||||||
|
amount: defaultSerializedObject.stripePrice.amount,
|
||||||
|
interval: defaultSerializedObject.stripePrice.interval,
|
||||||
|
currency: String.prototype.toUpperCase.call(defaultSerializedObject.stripePrice.currency)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (defaultSerializedObject.stripePrice.stripeProduct) {
|
||||||
|
serialized.price.product = {
|
||||||
|
id: defaultSerializedObject.stripePrice.stripeProduct.stripe_product_id,
|
||||||
|
name: defaultSerializedObject.stripePrice.stripeProduct.name,
|
||||||
|
product_id: defaultSerializedObject.stripePrice.stripeProduct.product_id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -121,6 +121,7 @@ module.exports = function apiRoutes() {
|
||||||
router.put('/members/:id', mw.authAdminApi, http(apiCanary.members.edit));
|
router.put('/members/:id', mw.authAdminApi, http(apiCanary.members.edit));
|
||||||
router.del('/members/:id', mw.authAdminApi, http(apiCanary.members.destroy));
|
router.del('/members/:id', mw.authAdminApi, http(apiCanary.members.destroy));
|
||||||
|
|
||||||
|
router.post('/members/:id/subscriptions/', mw.authAdminApi, http(apiCanary.members.createSubscription));
|
||||||
router.put('/members/:id/subscriptions/:subscription_id', mw.authAdminApi, http(apiCanary.members.editSubscription));
|
router.put('/members/:id/subscriptions/:subscription_id', mw.authAdminApi, http(apiCanary.members.editSubscription));
|
||||||
|
|
||||||
router.get('/members/:id/signin_urls', mw.authAdminApi, http(apiCanary.memberSigninUrls.read));
|
router.get('/members/:id/signin_urls', mw.authAdminApi, http(apiCanary.memberSigninUrls.read));
|
||||||
|
|
|
@ -234,7 +234,7 @@ describe('Members API', function () {
|
||||||
should.exist(jsonResponse2);
|
should.exist(jsonResponse2);
|
||||||
should.exist(jsonResponse2.members);
|
should.exist(jsonResponse2.members);
|
||||||
jsonResponse2.members.should.have.length(1);
|
jsonResponse2.members.should.have.length(1);
|
||||||
localUtils.API.checkResponse(jsonResponse2.members[0], 'member', 'subscriptions');
|
localUtils.API.checkResponse(jsonResponse2.members[0], 'member', ['subscriptions', 'products']);
|
||||||
jsonResponse2.members[0].name.should.equal(memberChanged.name);
|
jsonResponse2.members[0].name.should.equal(memberChanged.name);
|
||||||
jsonResponse2.members[0].email.should.equal(memberChanged.email);
|
jsonResponse2.members[0].email.should.equal(memberChanged.email);
|
||||||
jsonResponse2.members[0].email.should.not.equal(memberToChange.email);
|
jsonResponse2.members[0].email.should.not.equal(memberToChange.email);
|
||||||
|
|
Loading…
Add table
Reference in a new issue