Stripe integration flow

This commit is contained in:
metacryst
2026-03-05 00:29:34 -06:00
parent bdd260c2b5
commit 661bf86a1a
17 changed files with 303 additions and 117 deletions

View File

@@ -1,4 +1,4 @@
const IS_NODE =
export const IS_NODE =
typeof process !== "undefined" &&
process.versions?.node != null
@@ -9,9 +9,7 @@ async function bridgeSend(name, args) {
args: args
})
const json = await res.json()
if (!res.ok) throw new Error(json.error)
return json.result
return res
}
/**
@@ -24,8 +22,6 @@ export function createBridge(funcs) {
get(target, prop) {
const orig = target[prop]
if (typeof orig !== "function") return orig
return function (...args) {
if (IS_NODE) {
return orig(...args)

View File

@@ -0,0 +1,14 @@
import fs from "fs"
const handlers = {
getProfile(one, two) {
fs.writeFileSync("output.txt", `${one} ${two}`)
return "written to disk"
},
async getStripeProfile(networkId) {
return global.payments.getProfile(networkId)
}
}
export default handlers

View File

@@ -1,11 +1,10 @@
import fs from "fs"
import { createBridge } from "./bridge.js"
import { createBridge, IS_NODE } from "./bridge.js"
const handlers = {
getProfile(one, two) {
fs.writeFileSync("output.txt", `${one} ${two}`)
return "written to disk"
},
let handlers = {}
if (IS_NODE) {
const mod = await import("./handlers.js")
handlers = mod.default
}
export const { getProfile } = createBridge(handlers)
export default createBridge(handlers)

View File

@@ -1,6 +1,7 @@
/*
Sam Russell
Captured Sun
3.4.26 - Making horizontalAlign() and verticalAlign() methods of checking for stacks more robust
2.27.26 - Adding parentShadow() function
2.16.26 - Adding event objects to the onTouch callbacks
1.16.26 - Moving nav event dispatch out of pushState, adding null feature to attr()
@@ -673,11 +674,11 @@ HTMLElement.prototype.centerY = function () {
};
HTMLElement.prototype.verticalAlign = function (value) {
// if(!this.classList.contains("HStack") && !this.classList.contains("VStack")) {
// throw new Error("verticalAlign can be only be used on HStacks or VStacks!")
// }
const direction = getComputedStyle(this).flexDirection;
if(!direction) {
throw new Error("verticalAlign can be only be used on HStacks or VStacks!")
}
if (direction === "column" || direction === "column-reverse") {
this.style.justifyContent = value;
} else {
@@ -687,11 +688,11 @@ HTMLElement.prototype.verticalAlign = function (value) {
}
HTMLElement.prototype.horizontalAlign = function (value) {
const direction = getComputedStyle(this).flexDirection;
if(!direction) {
if(!this.classList.contains("HStack") && !this.classList.contains("VStack")) {
throw new Error("horizontalAlign can be only be used on HStacks or VStacks!")
}
const direction = getComputedStyle(this).flexDirection;
if (direction === "column" || direction === "column-reverse") {
this.style.alignItems = value;
} else {

View File

@@ -5,6 +5,7 @@ class Connection {
constructor(receiveCB) {
this.receiveCB = receiveCB;
this.init()
}
init = async () => {

View File

@@ -1,13 +1,22 @@
import env from "/_/code/env.js"
import server from "/_/code/bridge/serverFunctions.js"
import "../../components/LoadingCircleSmall.js"
class Settings extends Shadow {
stripeDetails = null
handleConnectStripe = () => {
const state = btoa(JSON.stringify({
returnTo: window.location.href,
networkId: global.currentNetwork.id
}));
const params = new URLSearchParams({
response_type: 'code',
client_id: env.client_id,
scope: 'read_write',
redirect_uri: `${env.baseURL}/stripe/onboardingcomplete`,
state,
});
window.location.href = `https://connect.stripe.com/oauth/authorize?${params}`;
@@ -21,22 +30,38 @@ class Settings extends Shadow {
.marginLeft(5, pct)
VStack(() => {
// HStack(() => {
// p("Stripe Integration")
// button("Set up Stripe")
// .maxWidth(10, em)
// .onClick((done) => {
// this.handleConnectStripe()
// })
// })
// .gap(10, pct)
// .verticalAlign("center")
HStack(() => {
p("Stripe Integration")
if(this.stripeDetails && this.stripeDetails.data.email) {
p("connected")
} else if(this.stripeDetails && this.stripeDetails.data.connected === false) {
button("Set up Stripe")
.maxWidth(10, em)
.onClick((done) => {
this.handleConnectStripe()
})
} else {
LoadingCircleSmall()
}
})
.gap(10, pct)
.verticalAlign("center")
})
.gap(0.5, em)
.paddingLeft(5, pct)
.marginTop(4, em)
})
}
async getStripeProfile() {
this.stripeDetails = await server.getStripeProfile(global.currentNetwork.id)
this.rerender()
}
connectedCallback() {
this.getStripeProfile()
}
}
register(Settings)

View File

@@ -6,9 +6,29 @@ class AppMenu extends Shadow {
"Settings": {src: "settings-src", size: "1.7em"}
}
unselectedIconStyle(el) {
return el
.paddingBottom(5, px)
.borderBottom("")
}
selectedIconStyle(el) {
return el
.paddingBottom(4, px)
.borderBottom("1px solid var(--accent)")
}
onAppChange() {
let icons = this.$$("img")
icons.forEach((icon) => {
icon.styles(this.unselectedIconStyle)
})
let selected = this.$(`img[app="${global.currentApp}"]`)
selected.styles(this.selectedIconStyle)
}
render() {
VStack(() => {
function cssVariable(value) {
return getComputedStyle(document.documentElement)
.getPropertyValue("--" + value)
@@ -25,8 +45,7 @@ class AppMenu extends Shadow {
img(cssVariable(this.images[app].src), this.images[app].size)
.attr({app: app})
.padding(0.3, em)
.paddingBottom(currentApp === app ? 4 : 5, px)
.borderBottom(currentApp === app ? "1px solid var(--accent)" : "")
.styles(currentApp === app ? this.selectedIconStyle : this.unselectedIconStyle)
.onClick(function (done) {
if(!done) {
this.style.transform = "translateY(10%)"
@@ -54,7 +73,7 @@ class AppMenu extends Shadow {
})
.onEvent("appchange", () => {
// console.log("app hello?") // BUG: Quill is not acknowledging this event unless there is something else in the function body
this.rerender()
this.onAppChange()
})
.onEvent("networkchange", () => {
// console.log(global.currentApp)

View File

@@ -0,0 +1,23 @@
class LoadingCircleSmall extends Shadow {
render() {
div()
.borderRadius(100, pct)
.width(1, em).height(1, em)
.backgroundColor("var(--accent")
.transition("transform 1.75s ease-in-out")
.onAppear(function () {
let growing = true;
setInterval(() => {
if (growing) {
this.style.transform = "scale(1.5)";
} else {
this.style.transform = "scale(0.7)";
}
growing = !growing;
}, 750);
});
}
}
register(LoadingCircleSmall)

View File

@@ -27,23 +27,30 @@ let Global = class {
return json
}
async handleStripeOnboarding() {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const { returnTo, networkId } = JSON.parse(atob(params.get("state")));
if (code) {
console.log("success!: ", code)
await fetch("/api/stripe/onboarded", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, networkId })
});
window.location.href = returnTo
} else {
throw new Error("Stripe code is not present!")
}
}
onNavigate = async () => {
console.log("onnavigate", this.getFirstPathSegment())
if(this.getFirstPathSegment() === "stripe") {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
if (code) {
console.log("success!: ", code)
await fetch("/api/stripe/connect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
});
} else {
throw new Error("Stripe code is not present!")
}
this.handleStripeOnboarding()
}
let selectedNetwork = this.networkFromPath()