Stripe integration flow
This commit is contained in:
@@ -28,18 +28,31 @@ export default class AuthHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getProfile(req, res) {
|
||||
getRequestEmail(req, res) {
|
||||
const token = req.cookies?.auth_token;
|
||||
if (!token) return res.status(401).send({ error: "No auth token" });
|
||||
|
||||
try {
|
||||
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const email = payload.email;
|
||||
return email
|
||||
} catch (e) {
|
||||
console.error("Error getting profile: ", e)
|
||||
throw new Error("Error getting email: invalid auth token")
|
||||
}
|
||||
}
|
||||
|
||||
getProfile(req, res) {
|
||||
try {
|
||||
let email = global.auth.getRequestEmail(req, res)
|
||||
|
||||
const user = db.members.getByEmail(email);
|
||||
let connections = db.MEMBER_IN_NETWORK.getByMember(db.members.prefix + "-" + user.id)
|
||||
let userOrgs = connections.map((c) => {
|
||||
return db.networks.get(c.to)
|
||||
let network = db.networks.get(c.to)
|
||||
delete network.stripeAccountId
|
||||
delete network.stripeAccessToken
|
||||
return network
|
||||
})
|
||||
|
||||
res.send({
|
||||
|
||||
@@ -37,6 +37,8 @@ export default class Database {
|
||||
return model.indices[1] - model.indices[0] + 1
|
||||
}
|
||||
|
||||
// add a get in here that returns a safe copy, not the actual reference
|
||||
|
||||
addNode(prefix, node) {
|
||||
try {
|
||||
let model = nodeModels[prefix]
|
||||
@@ -76,7 +78,8 @@ export default class Database {
|
||||
}
|
||||
}
|
||||
|
||||
editNode(prefix, id, toEdit) {
|
||||
updateNode(prefix, id, toEdit) {
|
||||
console.log("update node, ", toEdit)
|
||||
try {
|
||||
let model = nodeModels[prefix]
|
||||
if(model) {
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class Post {
|
||||
postToEdit.edited = true;
|
||||
|
||||
let { authorId, ...rest } = postToEdit
|
||||
global.db.editNode(this.prefix, id, rest)
|
||||
global.db.updateNode(this.prefix, id, rest)
|
||||
|
||||
return postToEdit
|
||||
} catch(e) {
|
||||
|
||||
@@ -43,15 +43,6 @@ export default class Member {
|
||||
}
|
||||
}
|
||||
|
||||
getByNetwork(id) {
|
||||
let connections = db.MEMBER_IN_NETWORK.getByNetwork(id)
|
||||
let members = []
|
||||
connections.forEach((conn) => {
|
||||
members.push(this.getByID(conn.from))
|
||||
})
|
||||
return members
|
||||
}
|
||||
|
||||
async getPersonalData(id) {
|
||||
const filePath = path.join(global.db.PERSONAL_DATA_PATH, id, "db.json");
|
||||
|
||||
@@ -63,6 +54,15 @@ export default class Member {
|
||||
return result
|
||||
}
|
||||
|
||||
getByNetwork(id) {
|
||||
let connections = db.MEMBER_IN_NETWORK.getByNetwork(id)
|
||||
let members = []
|
||||
connections.forEach((conn) => {
|
||||
members.push(this.getByID(conn.from))
|
||||
})
|
||||
return members
|
||||
}
|
||||
|
||||
getByID(id) {
|
||||
if(typeof id === 'string') {
|
||||
id = id.split("-")[1]
|
||||
|
||||
@@ -14,14 +14,18 @@ export default class Network {
|
||||
apps: z.array(z.string()),
|
||||
logo: z.string(),
|
||||
abbreviation: z.string(),
|
||||
created: z.string()
|
||||
created: z.string(),
|
||||
stripeAccountId: z.string().nullable(),
|
||||
stripeAccessToken: z.string().nullable()
|
||||
})
|
||||
.strict()
|
||||
|
||||
get(stringID) {
|
||||
let id = stringID.split("-")[1]
|
||||
get(id) {
|
||||
if(typeof id === "string") {
|
||||
id = id.split("-")[1]
|
||||
}
|
||||
let index = this.indices[0] + (id - 1)
|
||||
return global.db.nodes[index]
|
||||
return structuredClone(global.db.nodes[index])
|
||||
}
|
||||
|
||||
getByAbbreviation(abbreviation) {
|
||||
@@ -33,6 +37,21 @@ export default class Network {
|
||||
return null
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
if(data.id || data.created) {
|
||||
throw new Error("Can't update node id or created time!")
|
||||
}
|
||||
let currentObject = this.get(id)
|
||||
let newObject = {...currentObject, ...data}
|
||||
|
||||
try {
|
||||
global.db.updateNode(this.prefix, newObject.id, newObject)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw new global.ServerError(400, "Failed to add member!");
|
||||
}
|
||||
}
|
||||
|
||||
save(n) {
|
||||
let id = `${this.prefix}-${n.id}`
|
||||
let result = this.schema.safeParse(n)
|
||||
|
||||
@@ -28,6 +28,7 @@ class Server {
|
||||
/* Stripe */
|
||||
router.post("/create-checkout-session", PaymentsHandler.newSubscription)
|
||||
router.post("/webhook", express.raw({ type: "application/json" }), PaymentsHandler.webhook)
|
||||
router.post('/api/stripe/onboarded', PaymentsHandler.finishConnectSetup)
|
||||
|
||||
/* Auth */
|
||||
router.post('/login', this.auth.login)
|
||||
@@ -39,32 +40,10 @@ class Server {
|
||||
router.get('/db/images/*', this.getUserImage)
|
||||
router.get('/api/orgdata/*', this.getOrgData)
|
||||
router.get('/api/mydata/*', this.getPersonalData)
|
||||
router.get('/api/stripe/onboarded', this.stripeOnboarded)
|
||||
router.get('/*', this.get)
|
||||
return router
|
||||
}
|
||||
|
||||
async stripeOnboarded() {
|
||||
const { code } = req.body;
|
||||
|
||||
const response = await stripe.oauth.token({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
});
|
||||
|
||||
const { stripe_user_id, access_token } = response;
|
||||
|
||||
await db.users.update({
|
||||
where: { id: req.user.id },
|
||||
data: {
|
||||
stripeAccountId: stripe_user_id,
|
||||
stripeAccessToken: access_token,
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
}
|
||||
|
||||
getPersonalData = async (req, res, next) => {
|
||||
try {
|
||||
const memberId = req.params[0]
|
||||
@@ -243,8 +222,10 @@ class Server {
|
||||
|
||||
constructor() {
|
||||
this.db = new Database()
|
||||
global.db = this.db
|
||||
this.auth = new AuthHandler()
|
||||
global.db = this.db
|
||||
global.auth = this.auth
|
||||
global.payments = PaymentsHandler
|
||||
const app = express();
|
||||
app.post("/webhook", express.raw({ type: "application/json" }), PaymentsHandler.webhook)
|
||||
const allowedOrigins = new Set([
|
||||
|
||||
@@ -6,6 +6,56 @@ const stripe = new Stripe(process.env.STRIPE_SECRET);
|
||||
|
||||
export default class PaymentsHandler {
|
||||
|
||||
static async finishConnectSetup(req, res) {
|
||||
const { code, networkId } = req.body;
|
||||
console.log("onboarded", networkId)
|
||||
|
||||
const response = await stripe.oauth.token({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
});
|
||||
|
||||
const { stripe_user_id, access_token } = response;
|
||||
|
||||
await db.networks.update(
|
||||
networkId,
|
||||
{
|
||||
stripeAccountId: stripe_user_id,
|
||||
stripeAccessToken: access_token, // rarely used, long-term access token for the platform
|
||||
}
|
||||
);
|
||||
|
||||
res.json({ success: true });
|
||||
}
|
||||
|
||||
static async getProfile(networkId) {
|
||||
let network = global.db.networks.get(networkId)
|
||||
if (network) {
|
||||
if (network.stripeAccountId) {
|
||||
const account = await stripe.accounts.retrieve(network.stripeAccountId);
|
||||
return {
|
||||
chargesEnabled: account.charges_enabled,
|
||||
payoutsEnabled: account.payouts_enabled,
|
||||
detailsSubmitted: account.details_submitted,
|
||||
email: account.email,
|
||||
country: account.country,
|
||||
}
|
||||
} else {
|
||||
return { connected: false }
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Network ${networkId} not found`)
|
||||
}
|
||||
}
|
||||
|
||||
static async getCustomers() {
|
||||
const customers = await stripe.customers.list(
|
||||
{ limit: 100 },
|
||||
{ stripeAccount: 'acct_connected_account_id' }
|
||||
);
|
||||
console.log(customers)
|
||||
}
|
||||
|
||||
static async newSubscription(req, res) {
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
|
||||
@@ -2,16 +2,30 @@ import { WebSocket, WebSocketServer } from 'ws';
|
||||
import { z } from 'zod';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import * as serverFunctions from "../../ui/_/code/bridge/serverFunctions.js"
|
||||
import server from "../../ui/_/code/bridge/serverFunctions.js"
|
||||
import ForumHandler from "./handlers/ForumHandler.js"
|
||||
import MessagesHandler from "./handlers/MessagesHandler.js"
|
||||
|
||||
export default class Socket {
|
||||
wss;
|
||||
messageSchema = z.object({
|
||||
|
||||
functionCallSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
args: z.array(z.any()),
|
||||
|
||||
data: z.union([
|
||||
z.object({}).passthrough(), // allows any object
|
||||
z.array(z.any()) // allows any array
|
||||
]).optional()
|
||||
})
|
||||
.strict()
|
||||
|
||||
appOperationSchema = z.object({
|
||||
id: z.string(),
|
||||
app: z.string().optional(),
|
||||
operation: z.string().optional(),
|
||||
|
||||
msg: z.union([
|
||||
z.object({}).passthrough(), // allows any object
|
||||
z.array(z.any()) // allows any array
|
||||
@@ -69,43 +83,64 @@ export default class Socket {
|
||||
// Build a system where the ws obj is updated every time on navigate, so it already has context
|
||||
// this way, we can only send broadcast messages to clients that actually have that app / subapp open
|
||||
handleMessage = async (msg, ws) => {
|
||||
|
||||
try {
|
||||
const text = msg.toString();
|
||||
const req = JSON.parse(text);
|
||||
if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!")
|
||||
|
||||
let responseData;
|
||||
switch (req.app) {
|
||||
case "FORUM":
|
||||
responseData = await ForumHandler.handle(req.operation, req.msg, ws)
|
||||
break;
|
||||
console.log(req)
|
||||
|
||||
case "MESSAGES":
|
||||
responseData = MessagesHandler.handle(req.operation, req.msg, ws)
|
||||
break;
|
||||
|
||||
default:
|
||||
if(!req.app) {
|
||||
let func = req.msg
|
||||
responseData = serverFunctions[func.name](...args)
|
||||
} else {
|
||||
console.error("unknown ws message")
|
||||
}
|
||||
if(this.appOperationSchema.safeParse(req).success) {
|
||||
this.handleAppOperation(req, ws)
|
||||
} else if(this.functionCallSchema.safeParse(req).success) {
|
||||
this.handleFunction(req, ws)
|
||||
} else {
|
||||
throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!")
|
||||
}
|
||||
|
||||
let response = {
|
||||
...req
|
||||
}
|
||||
response.msg = responseData
|
||||
|
||||
if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!")
|
||||
ws.send(JSON.stringify(response))
|
||||
|
||||
} catch (e) {
|
||||
console.error("Invalid WS message:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAppOperation(req, ws) {
|
||||
let responseData;
|
||||
switch (req.app) {
|
||||
case "FORUM":
|
||||
responseData = await ForumHandler.handle(req.operation, req.msg, ws)
|
||||
break;
|
||||
|
||||
case "MESSAGES":
|
||||
responseData = MessagesHandler.handle(req.operation, req.msg, ws)
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("unknown ws message")
|
||||
}
|
||||
|
||||
let response = {
|
||||
...req
|
||||
}
|
||||
response.msg = responseData
|
||||
|
||||
if(!this.appOperationSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!")
|
||||
ws.send(JSON.stringify(response))
|
||||
}
|
||||
|
||||
async handleFunction(req, ws) {
|
||||
console.log("func call: ", req.name, req.args)
|
||||
let responseData = await server[req.name](...req.args)
|
||||
|
||||
let response = {
|
||||
...req
|
||||
}
|
||||
response.data = responseData
|
||||
|
||||
console.log(response)
|
||||
|
||||
if(!this.functionCallSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!")
|
||||
ws.send(JSON.stringify(response))
|
||||
}
|
||||
|
||||
broadcast(event) {
|
||||
if (!this.wss) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user