Skip to content

Commit f44c963

Browse files
committed
add basic Stripe integration
1 parent 18848ab commit f44c963

File tree

6 files changed

+95
-8
lines changed

6 files changed

+95
-8
lines changed

netlify/functions/github.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const connect = require('../../src/db');
55
const extrovert = require('extrovert');
66
const githubOAuth = require('../../src/integrations/githubOAuth');
77
const mongoose = require('mongoose');
8+
const stripe = require('../../src/integrations/stripe');
89

910
const GithubOAuthParams = new Archetype({
1011
code: {
@@ -66,11 +67,16 @@ module.exports = extrovert.toNetlifyFunction(async function github(params) {
6667
await invitation.save();
6768

6869
roles = invitation.roles;
70+
71+
if (workspace.stripeSubscriptionId && !user.isFreeUser) {
72+
const users = await User.find({ _id: { $in: workspace.members.map(member => member.userId) }, isFreeUser: { $ne: true } });
73+
const seats = users.length;
74+
await stripe.updateSubscriptionSeats(workspace.stripeSubscriptionId, seats);
75+
}
6976
}
7077
} else {
7178
roles = member.roles;
7279
}
7380

74-
7581
return { user, accessToken, roles };
7682
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
const extrovert = require('extrovert');
4+
5+
module.exports = extrovert.toNetlifyFunction(require('../../src/actions/removeFromWorkspace'));

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"extrovert": "0.0.26",
1313
"mongoose": "8.x",
1414
"ramda": "0.28.0",
15+
"stripe": "9.9.0",
1516
"time-commando": "1.0.1"
1617
},
1718
"devDependencies": {

src/actions/removeFromWorkspace.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const Archetype = require('archetype');
4+
const connect = require('../../src/db');
5+
const mongoose = require('mongoose');
6+
7+
const RemoveFromWorkspaceParams = new Archetype({
8+
authorization: {
9+
$type: 'string',
10+
$required: true
11+
},
12+
workspaceId: {
13+
$type: mongoose.Types.ObjectId,
14+
$required: true
15+
},
16+
userId: {
17+
$type: mongoose.Types.ObjectId,
18+
$required: true
19+
}
20+
}).compile('InviteToWorkspaceParams');
21+
22+
module.exports = async function removeFromWorkspace(params) {
23+
const db = await connect();
24+
const { AccessToken, Workspace } = db.models;
25+
26+
const { authorization, workspaceId, userId } = new RemoveFromWorkspaceParams(params);
27+
28+
// Verify access token
29+
const accessToken = await AccessToken.findById(authorization).orFail(new Error('Invalid or expired access token'));
30+
if (accessToken.expiresAt < new Date()) {
31+
throw new Error('Access token has expired');
32+
}
33+
const initiatedByUserId = accessToken._id;
34+
35+
const workspace = await Workspace.findById(workspaceId).orFail(new Error('Workspace not found'));
36+
const initiatedByUserRoles = workspace.members.find(member => member.userId.toString() === initiatedByUserId.toString())?.roles;
37+
if (initiatedByUserRoles == null || (!initiatedByUserRoles.includes('admin') && !initiatedByUserRoles.includes('owner'))) {
38+
throw new Error('Forbidden');
39+
}
40+
41+
const memberIndex = workspace.members.findIndex(member => member.userId.toString() === userId.toString());
42+
if (memberIndex === -1) {
43+
throw new Error('Member not found in the workspace');
44+
}
45+
46+
workspace.members.splice(memberIndex, 1);
47+
await workspace.save();
48+
49+
return { workspace };
50+
};

src/db/workspace.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,14 @@ const workspaceSchema = new mongoose.Schema({
3535
required: true
3636
},
3737
stripeCustomerId: {
38-
type: String,
39-
unique: true,
40-
sparse: true
38+
type: String
4139
},
4240
stripeSubscriptionId: {
43-
type: String,
44-
unique: true,
45-
sparse: true
41+
type: String
4642
},
4743
subscriptionTier: {
4844
type: String,
49-
enum: ['pro']
45+
enum: ['free', 'pro']
5046
}
5147
}, { timestamps: true, id: false });
5248

src/integrations/stripe.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
5+
6+
exports.listSubscriptions = async function listSubscriptions() {
7+
const subscriptions = await stripe.subscriptions.list();
8+
return subscriptions;
9+
};
10+
11+
12+
exports.getSubscription = async function getSubscription(subscriptionId) {
13+
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
14+
return subscription;
15+
};
16+
17+
exports.updateSubscriptionSeats = async function updateSubscriptionSeats(subscriptionId, newSeatCount) {
18+
assert(Number.isInteger(newSeatCount) && newSeatCount >= 0, 'Seat count must be a non-negative integer.');
19+
20+
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
21+
const subscriptionItemId = subscription.items.data[0].id;
22+
23+
const updatedSubscription = await stripe.subscriptionItems.update(
24+
subscriptionItemId,
25+
{ quantity: newSeatCount }
26+
);
27+
28+
return updatedSubscription;
29+
};

0 commit comments

Comments
 (0)