From dd04104c463bdda4634ec73ececba27558b3ab56 Mon Sep 17 00:00:00 2001 From: Qian Li Date: Wed, 4 Dec 2024 17:03:30 -0800 Subject: [PATCH 1/3] Support return url --- package-lock.json | 43 +++++++++++++++++++++---------------------- package.json | 2 +- src/endpoints.ts | 3 ++- src/subscription.ts | 8 ++++---- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82f53da..837d931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "dbos", "version": "0.0.1", "dependencies": { - "@dbos-inc/dbos-sdk": "^1.23.8", + "@dbos-inc/dbos-sdk": "^1.28.6", "axios": "^1.7.4", "jwks-rsa": "^3.1.0", "koa-jwt": "^4.0.4", @@ -754,14 +754,14 @@ } }, "node_modules/@dbos-inc/dbos-sdk": { - "version": "1.23.8", - "resolved": "https://registry.npmjs.org/@dbos-inc/dbos-sdk/-/dbos-sdk-1.23.8.tgz", - "integrity": "sha512-WG63Mwt1r8qiHgxJ7nQiOwvLXOuVX5YqIigsA5ytK0x52jVKKL5f+7/0boj8bkiyqwWTKNGE7gaPOWaK0O4S8Q==", + "version": "1.28.6", + "resolved": "https://registry.npmjs.org/@dbos-inc/dbos-sdk/-/dbos-sdk-1.28.6.tgz", + "integrity": "sha512-F3FhPFxH17X4GmordBBdx0I8ZxqCv1LRfr8fq7NsneUvHXzGr2ysC1T16IpkX3Ho+zc9W+aXkiLknpHNTun5WA==", "dependencies": { "@inquirer/prompts": "^5.3.8", "@koa/bodyparser": "5.0.0", "@koa/cors": "5.0.0", - "@koa/router": "12.0.1", + "@koa/router": "13.1.0", "@opentelemetry/api": "1.8.0", "@opentelemetry/api-logs": "0.49.1", "@opentelemetry/core": "1.22.0", @@ -1666,18 +1666,16 @@ } }, "node_modules/@koa/router": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz", - "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", "dependencies": { - "debug": "^4.3.4", "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^6.3.0" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/@nodelib/fs.scandir": { @@ -3587,9 +3585,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -6483,17 +6481,18 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6984,9 +6983,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, "node_modules/path-type": { "version": "4.0.0", diff --git a/package.json b/package.json index c8193b0..4f9d491 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "typescript": "^5.2.2" }, "dependencies": { - "@dbos-inc/dbos-sdk": "^1.23.8", + "@dbos-inc/dbos-sdk": "^1.28.6", "axios": "^1.7.4", "jwks-rsa": "^3.1.0", "koa-jwt": "^4.0.4", diff --git a/src/endpoints.ts b/src/endpoints.ts index e79d7b7..608f5cf 100644 --- a/src/endpoints.ts +++ b/src/endpoints.ts @@ -57,7 +57,8 @@ export class CloudSubscription { @PostApi('/create-customer-portal') static async createCustomerPortal(ctxt: HandlerContext) { const auth0User = ctxt.authenticatedUser; - const sessionURL = await ctxt.invokeWorkflow(Utils).createStripeCustomerPortal(auth0User); + const returnUrl = (ctxt.request.body as {return_url: string}).return_url as string ?? 'https://www.dbos.dev/pricing'; + const sessionURL = await ctxt.invokeWorkflow(Utils).createStripeCustomerPortal(auth0User, returnUrl); if (!sessionURL) { throw new DBOSResponseError("Failed to create customer portal!", 500); } diff --git a/src/subscription.ts b/src/subscription.ts index 40ac60a..212743d 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -61,13 +61,13 @@ export class Utils { // Workflow to create a Stripe customer portal @Workflow() - static async createStripeCustomerPortal(ctxt: WorkflowContext, auth0UserID: string): Promise { + static async createStripeCustomerPortal(ctxt: WorkflowContext, auth0UserID: string, returnUrl: string): Promise { const stripeCustomerID = await ctxt.invoke(Utils).findStripeCustomerID(auth0UserID); if (!stripeCustomerID) { ctxt.logger.error(`Cannot find stripe customer for user ${auth0UserID}`); return null; } - const sessionURL = await ctxt.invoke(Utils).createStripeBillingPortal(stripeCustomerID); + const sessionURL = await ctxt.invoke(Utils).createStripeBillingPortal(stripeCustomerID, returnUrl); return sessionURL; } @@ -113,10 +113,10 @@ export class Utils { // Create a Stripe billing portal for a customer @Communicator({intervalSeconds: 10, maxAttempts: 2}) - static async createStripeBillingPortal(_ctxt: CommunicatorContext, customerID: string): Promise { + static async createStripeBillingPortal(_ctxt: CommunicatorContext, customerID: string, returnUrl: string): Promise { const session = await stripe.billingPortal.sessions.create({ customer: customerID, - return_url: 'https://www.dbos.dev/pricing' + return_url: returnUrl, }); return session.url; } From 9efa235aa99a81767ac072f8240784354a01bf58 Mon Sep 17 00:00:00 2001 From: Qian Li Date: Wed, 4 Dec 2024 17:10:22 -0800 Subject: [PATCH 2/3] Support success and cancel URLs --- src/endpoints.ts | 5 ++++- src/subscription.ts | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/endpoints.ts b/src/endpoints.ts index 608f5cf..1395323 100644 --- a/src/endpoints.ts +++ b/src/endpoints.ts @@ -45,7 +45,10 @@ export class CloudSubscription { const auth0UserID = ctxt.authenticatedUser; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const userEmail = ctxt.koaContext.state.user["https://dbos.dev/email"] as string; - const sessionURL = await ctxt.invokeWorkflow(Utils).createSubscription(auth0UserID, userEmail); + const body = ctxt.request.body as {success_url: string, cancel_url: string}; + const successUrl = body.success_url as string ?? 'https://console.dbos.dev'; + const cancelUrl = body.cancel_url as string ?? 'https://www.dbos.dev/pricing'; + const sessionURL = await ctxt.invokeWorkflow(Utils).createSubscription(auth0UserID, userEmail, successUrl, cancelUrl); if (!sessionURL) { throw new DBOSResponseError("Failed to create a checkout session!", 500); } diff --git a/src/subscription.ts b/src/subscription.ts index 212743d..5cb327a 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -44,7 +44,7 @@ export class Utils { // Workflow to create a Stripe checkout session @Workflow() - static async createSubscription(ctxt: WorkflowContext, auth0UserID: string, userEmail: string): Promise { + static async createSubscription(ctxt: WorkflowContext, auth0UserID: string, userEmail: string, successUrl: string, cancelUrl: string): Promise { // First, look up the customer from the accounts table let stripeCustomerID = await ctxt.invoke(Utils).findStripeCustomerID(auth0UserID); @@ -55,7 +55,7 @@ export class Utils { } // Finally, create a Stripe checkout session. - const res = await ctxt.invoke(Utils).createStripeCheckout(stripeCustomerID); + const res = await ctxt.invoke(Utils).createStripeCheckout(stripeCustomerID, successUrl, cancelUrl); return res; } @@ -123,7 +123,7 @@ export class Utils { // Create a Stripe checkout session for a customer @Communicator({intervalSeconds: 10, maxAttempts: 2}) - static async createStripeCheckout(_ctxt: CommunicatorContext, stripeCustomerID: string): Promise { + static async createStripeCheckout(_ctxt: CommunicatorContext, stripeCustomerID: string, successUrl: string, cancelUrl: string): Promise { const session = await stripe.checkout.sessions.create({ customer: stripeCustomerID, billing_address_collection: 'auto', @@ -134,8 +134,8 @@ export class Utils { }, ], mode: 'subscription', - success_url: `https://docs.dbos.dev`, - cancel_url: `https://www.dbos.dev/pricing`, + success_url: successUrl, + cancel_url: cancelUrl, allow_promotion_codes: true, }); return session.url; From 6fb370a5de2c883b793fa7e39e1b6809b76a83a8 Mon Sep 17 00:00:00 2001 From: Qian Li Date: Wed, 4 Dec 2024 17:13:58 -0800 Subject: [PATCH 3/3] Support success and cancel URLs --- src/endpoints.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/endpoints.ts b/src/endpoints.ts index 1395323..2404728 100644 --- a/src/endpoints.ts +++ b/src/endpoints.ts @@ -46,8 +46,8 @@ export class CloudSubscription { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const userEmail = ctxt.koaContext.state.user["https://dbos.dev/email"] as string; const body = ctxt.request.body as {success_url: string, cancel_url: string}; - const successUrl = body.success_url as string ?? 'https://console.dbos.dev'; - const cancelUrl = body.cancel_url as string ?? 'https://www.dbos.dev/pricing'; + const successUrl = body.success_url ?? 'https://console.dbos.dev'; + const cancelUrl = body.cancel_url ?? 'https://www.dbos.dev/pricing'; const sessionURL = await ctxt.invokeWorkflow(Utils).createSubscription(auth0UserID, userEmail, successUrl, cancelUrl); if (!sessionURL) { throw new DBOSResponseError("Failed to create a checkout session!", 500); @@ -60,7 +60,7 @@ export class CloudSubscription { @PostApi('/create-customer-portal') static async createCustomerPortal(ctxt: HandlerContext) { const auth0User = ctxt.authenticatedUser; - const returnUrl = (ctxt.request.body as {return_url: string}).return_url as string ?? 'https://www.dbos.dev/pricing'; + const returnUrl = (ctxt.request.body as {return_url: string}).return_url ?? 'https://www.dbos.dev/pricing'; const sessionURL = await ctxt.invokeWorkflow(Utils).createStripeCustomerPortal(auth0User, returnUrl); if (!sessionURL) { throw new DBOSResponseError("Failed to create customer portal!", 500);