mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
feat(billing): add direct Stripe billing portal access
Replace dashboard redirect with Stripe Customer Portal session for the "Manage in Stripe" button, so users go directly to Stripe to manage invoices, payment methods, and billing details. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9c010dabd8
commit
309c05782e
4 changed files with 34 additions and 4 deletions
|
|
@ -40,7 +40,7 @@ import { triggerRun as triggerAgentScheduleRun } from '@x/core/dist/agent-schedu
|
|||
import { search } from '@x/core/dist/search/search.js';
|
||||
import { versionHistory, voice } from '@x/core';
|
||||
import { classifySchedule, processRowboatInstruction } from '@x/core/dist/knowledge/inline_tasks.js';
|
||||
import { getBillingInfo } from '@x/core/dist/billing/billing.js';
|
||||
import { getBillingInfo, getBillingPortalUrl } from '@x/core/dist/billing/billing.js';
|
||||
import { summarizeMeeting } from '@x/core/dist/knowledge/summarize_meeting.js';
|
||||
import { getAccessToken } from '@x/core/dist/auth/tokens.js';
|
||||
import { getRowboatConfig } from '@x/core/dist/config/rowboat.js';
|
||||
|
|
@ -759,5 +759,9 @@ export function setupIpcHandlers() {
|
|||
'billing:getInfo': async () => {
|
||||
return await getBillingInfo();
|
||||
},
|
||||
'billing:getPortalUrl': async () => {
|
||||
const url = await getBillingPortalUrl();
|
||||
return { url };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,8 +179,8 @@ export function AccountSettings({ dialogOpen }: AccountSettingsProps) {
|
|||
<p className="text-xs text-muted-foreground">Subscribe to access AI features</p>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => appUrl && window.open(`${appUrl}?intent=upgrade`)}>
|
||||
{!billing.subscriptionPlan ? 'Subscribe' : 'Change plan'}
|
||||
<Button variant="outline" size="sm" onClick={() => appUrl && window.open(appUrl)}>
|
||||
View plan
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -204,7 +204,14 @@ export function AccountSettings({ dialogOpen }: AccountSettingsProps) {
|
|||
variant="outline"
|
||||
size="sm"
|
||||
disabled={!billing?.subscriptionPlan}
|
||||
onClick={() => appUrl && window.open(appUrl)}
|
||||
onClick={async () => {
|
||||
try {
|
||||
const { url } = await window.ipc.invoke('billing:getPortalUrl', null);
|
||||
window.open(url);
|
||||
} catch {
|
||||
toast.error('Failed to open billing portal');
|
||||
}
|
||||
}}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
|
|
|
|||
|
|
@ -11,6 +11,19 @@ export interface BillingInfo {
|
|||
availableCredits: number;
|
||||
}
|
||||
|
||||
export async function getBillingPortalUrl(): Promise<string> {
|
||||
const accessToken = await getAccessToken();
|
||||
const response = await fetch(`${API_URL}/v1/billing/portal-session`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Portal session failed: ${response.status}`);
|
||||
}
|
||||
const body = await response.json() as { url: string };
|
||||
return body.url;
|
||||
}
|
||||
|
||||
export async function getBillingInfo(): Promise<BillingInfo> {
|
||||
const accessToken = await getAccessToken();
|
||||
const response = await fetch(`${API_URL}/v1/me`, {
|
||||
|
|
|
|||
|
|
@ -572,6 +572,12 @@ const ipcSchemas = {
|
|||
availableCredits: z.number(),
|
||||
}),
|
||||
},
|
||||
'billing:getPortalUrl': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
url: z.string(),
|
||||
}),
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue