Skip to content

Commit a452ca8

Browse files
committed
handle org and update serverless
1 parent 1583cde commit a452ca8

24 files changed

+3897
-685
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ node_modules
33
jspm_packages
44

55
# Serverless directories
6-
.serverless
6+
.serverless
7+
8+
# secrets
9+
config.dev.yml
10+
config.production.yml

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
### Secrets
2+
3+
Create 2 files called `config.dev.yml` and `config.production.yml` containing
4+
5+
```yml
6+
GITHUB_HOST: github.com
7+
GITHUB_PORT: 443
8+
GITHUB_PATH: /login/oauth/access_token
9+
GITHUB_METHOD: POST
10+
11+
GITHUB_CLIENT_ID: XXXXXXX
12+
GITHUB_CLIENT_SECRET: XXXXXXX
13+
14+
STRIPE_SECRET: XXXXXX
15+
STRIPE_ENDPOINT_SECRET: XXXXXX
16+
```
17+
18+
### ORGS
19+
20+
1. user login with github on the web
21+
2. if already part of 1 org -> skip
22+
else if already part of multiple orgs
23+
if admin of 1 org -> show this one
24+
else show an org picker
25+
else pay with stripe -> create a valid org with the user as an admin
26+
3. if the user is not the admin of the org -> only display the members without possibility to do anything
27+
else show a dashboard to add/promote/remove members and a button to pay again if the org is not valid anymore
28+
29+
Adding a member adds a subscription item
30+
Removing a member remove the subscription item

api/_handleError.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const makeCallback = require('./_makeCallback')
2+
3+
module.exports = callback => err => {
4+
console.error(err)
5+
if (!err.type) {
6+
makeCallback(callback, err.message, 500)
7+
return
8+
}
9+
switch (err.type) {
10+
case 'StripeCardError':
11+
// A declined card error
12+
const message = err.message // => e.g. "Your card's expiration year is invalid."
13+
makeCallback(callback, message, 400)
14+
break
15+
case 'RateLimitError':
16+
// Too many requests made to the API too quickly
17+
makeCallback(callback, 'Server is a bit overloaded, try again in a bit', 503)
18+
break
19+
case 'StripeInvalidRequestError':
20+
// Invalid parameters were supplied to Stripe's API
21+
makeCallback(callback, 'Bad request', 400)
22+
break
23+
case 'StripeAPIError':
24+
// An error occurred internally with Stripe's API
25+
makeCallback(callback, 'Stripe failed, sorry about that', 500)
26+
break
27+
case 'StripeConnectionError':
28+
// Some kind of error occurred during the HTTPS communication
29+
makeCallback(callback, 'Stripe is down, sorry about that', 500)
30+
break
31+
case 'StripeAuthenticationError':
32+
// You probably used an incorrect API key
33+
makeCallback(callback, 'How did that happen!?', 500)
34+
break
35+
default:
36+
// Handle any other types of unexpected errors
37+
makeCallback(callback, err.message, 500)
38+
break
39+
}
40+
}

api/_makeCallback.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = function (callback, body, status) {
2+
try {
3+
callback(null, {
4+
statusCode: status || 200,
5+
headers: {
6+
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
7+
'Access-Control-Allow-Credentials': true // Required for cookies, authorization headers with HTTPS
8+
},
9+
body: JSON.stringify(body)
10+
})
11+
} catch (err) {
12+
callback(null, {
13+
statusCode: 500,
14+
headers: {
15+
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
16+
'Access-Control-Allow-Credentials': true // Required for cookies, authorization headers with HTTPS
17+
},
18+
body: err.message
19+
})
20+
}
21+
}

api/checkUnlocked.js

-34
This file was deleted.

api/coupon.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const stripe = require('stripe')(process.env.STRIPE_SECRET)
2+
const makeCallback = require('./_makeCallback')
23

34
module.exports.handler = (event, context, callback) => {
4-
const coupon = decodeURIComponent(event.path.coupon)
5-
const requestId = parseInt(event.query.requestId)
5+
const coupon = decodeURIComponent(event.pathParameters.coupon)
6+
const requestId = parseInt(event.queryStringParameters.requestId)
67

78
return stripe.coupons.retrieve(coupon)
89
.then(coupon => {
@@ -16,7 +17,7 @@ module.exports.handler = (event, context, callback) => {
1617
? (' for ' + coupon.duration_in_months + ' month' +
1718
(coupon.duration_in_months > 1 ? 's' : ''))
1819
: '')
19-
return callback(null, {
20+
return makeCallback(callback, {
2021
ok: true,
2122
percent_off: coupon.percent_off,
2223
amount_off: coupon.amount_off,
@@ -26,7 +27,7 @@ module.exports.handler = (event, context, callback) => {
2627
requestId: requestId
2728
})
2829
} else {
29-
return callback(null, {
30+
return makeCallback(callback, {
3031
ok: true,
3132
error: 'Coupon not valid anymore',
3233
requestId: requestId
@@ -35,7 +36,7 @@ module.exports.handler = (event, context, callback) => {
3536
})
3637
.catch((err) => {
3738
console.error(err)
38-
return callback(null, {
39+
return makeCallback(callback, {
3940
ok: true,
4041
error: 'Coupon not found',
4142
requestId: requestId

api/getOne.js

-17
This file was deleted.

api/githubOAuth.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
var https = require('https')
2+
var qs = require('querystring')
3+
var axios = require('axios')
4+
const storage = require('../storage')()
5+
const makeCallback = require('./_makeCallback')
6+
7+
function authenticate (code, cb) {
8+
var data = qs.stringify({
9+
client_id: process.env.GITHUB_CLIENT_ID,
10+
client_secret: process.env.GITHUB_CLIENT_SECRET,
11+
code: code
12+
})
13+
14+
var reqOptions = {
15+
host: process.env.GITHUB_HOST,
16+
port: process.env.GITHUB_PORT || 443,
17+
path: process.env.GITHUB_PATH,
18+
method: process.env.GITHUB_METHOD || 'POST',
19+
headers: { 'content-length': data.length }
20+
}
21+
22+
var body = ''
23+
var req = https.request(reqOptions, (res) => {
24+
res.setEncoding('utf8')
25+
res.on('data', (chunk) => {
26+
body += chunk
27+
})
28+
res.on('end', () => {
29+
var token = qs.parse(body).access_token
30+
cb(!token && new Error('missing access token'), token)
31+
})
32+
})
33+
34+
req.write(data)
35+
req.end()
36+
req.on('error', (e) => {
37+
cb(e.message)
38+
})
39+
}
40+
41+
module.exports.handler = (event, context, callback) => {
42+
authenticate(event.queryStringParameters.code, function (err, token) {
43+
if (err) {
44+
console.log(err)
45+
makeCallback(callback, 'Bad Code', 400)
46+
return
47+
}
48+
axios({
49+
url: 'https://api.github.com/user',
50+
headers: {
51+
Authorization: 'Token ' + token,
52+
Accept: 'application/vnd.github.v3+json'
53+
}
54+
}).then(user => user.data)
55+
.then(user => {
56+
return storage.findOne(user.id)
57+
})
58+
.then(user => {
59+
if (!user) {
60+
makeCallback(callback, 'User not found, please create a Kactus account first', 404)
61+
return
62+
}
63+
return storage.findOrgs(user.orgs).then(orgs => {
64+
user.orgs = orgs || []
65+
return makeCallback(callback, {
66+
ok: true,
67+
user,
68+
token
69+
})
70+
})
71+
})
72+
.catch(err => makeCallback(callback, err.message, 500))
73+
})
74+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function createNewSubscription (stripe, {org, enterprise, coupon, members}) {
2+
// create a new subscription
3+
return stripe.subscriptions.create({
4+
customer: org.stripeId,
5+
items: [{
6+
plan: enterprise ? 'kactus-enterprise-1-month' : 'kactus-1-month',
7+
quantity: members
8+
}],
9+
coupon: coupon || undefined
10+
})
11+
}
12+
module.exports.createNewSubscription = createNewSubscription
13+
14+
function updateSubscription (stripe, {org, fromPlan, toPlan, coupon, members}) {
15+
// need to update the existing subscription
16+
return stripe.subscriptions.list({
17+
customer: org.stripeId,
18+
plan: fromPlan === 'enterprise' ? 'kactus-enterprise-1-month' : 'kactus-1-month',
19+
limit: 100
20+
}).then((subscriptions) => {
21+
const subscriptionToUpdate = subscriptions.data.find(s => s.status === 'active')
22+
if (!subscriptionToUpdate) {
23+
return createNewSubscription(stripe, {org, enterprise: toPlan === 'enterprise', coupon, members})
24+
}
25+
if (toPlan !== fromPlan) {
26+
return stripe.subscriptions.update(subscriptionToUpdate.id, {
27+
items: [{
28+
plan: toPlan === 'enterprise' ? 'kactus-enterprise-1-month' : 'kactus-1-month',
29+
quantity: members
30+
}],
31+
coupon: coupon || undefined
32+
})
33+
}
34+
return stripe.subscriptionItems.update(subscriptionToUpdate.items.data[0].id, {
35+
quantity: members,
36+
prorate: true
37+
})
38+
})
39+
}
40+
module.exports.updateSubscription = updateSubscription

0 commit comments

Comments
 (0)