Documentation Index Fetch the complete documentation index at: https://docs.wiseyield.co/llms.txt
Use this file to discover all available pages before exploring further.
Endpoint
POST https://www.wiseyield.co/api/billing/create-portal-session
Authentication
Requires a valid API key with billing:write scope.
Request Body
No request body required.
Response
Returns a Dodo Payments billing portal URL.
Dodo Payments billing portal URL to redirect user to
cURL
JavaScript
JavaScript (With Error Handling)
Python
curl -X POST https://www.wiseyield.co/api/billing/create-portal-session \
-H "Authorization: Bearer wy_YOUR_API_KEY"
200 Success
401 Unauthorized
404 Not Found
500 Internal Server Error
{
"url" : "https://portal.dodopayments.com/session/ps_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
Errors
Unauthorized - Missing or invalid API key
Not Found - User has no billing customer ID (never subscribed)
Internal Server Error - Failed to create Dodo Payments portal session
How It Works
Portal Flow :
API Call : Client requests portal session
Customer Lookup : System finds user’s Dodo Payments customer ID
Session Creation : Dodo Payments portal session created with return URL
Redirect : User redirected to Dodo Payments portal
Self-Service : User manages subscription on Dodo Payments
Return : User clicks “Return to…” and comes back to app
Return URL : https://www.wiseyield.co/dashboard/billing
Customer Requirement : User must have subscribed at least once. If no subscription history exists, returns 404 error.
Customer Portal Features
Dodo Payments portal allows users to :
Subscription Management
Change subscription plan (upgrade/downgrade)
Cancel subscription
Resume canceled subscription
View upcoming invoice
Payment Methods
Update credit card
Add new payment method
Set default payment method
Remove old payment methods
Billing History
View all past invoices
Download invoice PDFs
See payment history
Update billing email
Update billing address
Update tax ID
Use Cases
Open Billing Portal
async function manageBilling () {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
if ( ! response . ok ) {
if ( response . status === 404 ) {
toast . error ( 'No subscription found. Subscribe first!' );
router . push ( '/pricing' );
return ;
}
toast . error ( 'Failed to open billing portal' );
return ;
}
const { url } = await response . json ();
window . location . href = url ;
}
function ManageSubscriptionButton () {
const [ loading , setLoading ] = useState ( false );
async function handleClick () {
setLoading ( true );
try {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
if ( ! response . ok ) {
toast . error ( 'Unable to open billing portal' );
return ;
}
const { url } = await response . json ();
window . location . href = url ;
} finally {
setLoading ( false );
}
}
return (
< button onClick = { handleClick } disabled = { loading } >
{ loading ? 'Opening...' : 'Manage Subscription' }
</ button >
);
}
Check Before Opening Portal
async function openPortalIfEligible () {
// First check if user has subscription
const subResponse = await fetch ( '/api/billing/subscription' , {
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { subscription } = await subResponse . json ();
if ( ! subscription ) {
toast . error ( 'You need an active subscription to access the billing portal' );
router . push ( '/pricing' );
return ;
}
// Open portal
const portalResponse = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { url } = await portalResponse . json ();
window . location . href = url ;
}
Best Practices
Check Subscription First : Verify user has subscription before showing “Manage Billing” button.
Loading State : Show loading indicator while creating portal session.
Return URL Handling : Portal returns to /dashboard/billing. Ensure this page shows updated subscription data.
New Tab Option : Consider opening portal in new tab to prevent losing app state.
No Subscription = 404 : Users who never subscribed will get 404 error. Handle this gracefully.
Portal URL Expires : Portal session URLs expire after a short time. Create new session each time.
Changes Apply Immediately : Subscription changes in portal take effect immediately via webhooks.
Integration Examples
Billing Settings Page
function BillingSettingsPage () {
const [ subscription , setSubscription ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
async function loadSubscription () {
const response = await fetch ( '/api/billing/subscription' , {
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { subscription } = await response . json ();
setSubscription ( subscription );
setLoading ( false );
}
loadSubscription ();
}, []);
async function openPortal () {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { url } = await response . json ();
window . location . href = url ;
}
if ( loading ) return < div > Loading... </ div > ;
return (
< div className = "billing-page" >
< h1 > Billing & Subscription </ h1 >
{ subscription ? (
<>
< div className = "subscription-info" >
< h2 > Current Plan: { subscription . plan } </ h2 >
< p > Status: { subscription . status } </ p >
< p > Next billing: {new Date ( subscription . currentPeriodEnd ). toLocaleDateString () } </ p >
</ div >
< button onClick = { openPortal } className = "manage-button" >
Manage Subscription
</ button >
< p className = "help-text" >
Update payment method, change plan, or cancel subscription
</ p >
</>
) : (
<>
< p > No active subscription </ p >
< a href = "/pricing" > View Plans </ a >
</>
) }
</ div >
);
}
function BillingPortalButton ({ actionType = 'manage' }) {
const [ opening , setOpening ] = useState ( false );
async function handleClick () {
const messages = {
manage: 'Open billing portal to manage your subscription?' ,
cancel: 'Open portal to cancel your subscription?' ,
upgrade: 'Open portal to upgrade your plan?' ,
};
const confirmed = confirm ( messages [ actionType ]);
if ( ! confirmed ) return ;
setOpening ( true );
try {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
if ( ! response . ok ) {
toast . error ( 'Unable to open portal' );
return ;
}
const { url } = await response . json ();
window . location . href = url ;
} finally {
setOpening ( false );
}
}
const buttonText = {
manage: 'Manage Billing' ,
cancel: 'Cancel Subscription' ,
upgrade: 'Change Plan' ,
};
return (
< button onClick = { handleClick } disabled = { opening } >
{ opening ? 'Opening...' : buttonText [ actionType ] }
</ button >
);
}
function UserMenu () {
const [ subscription , setSubscription ] = useState ( null );
useEffect (() => {
async function loadSubscription () {
const response = await fetch ( '/api/billing/subscription' , {
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { subscription } = await response . json ();
setSubscription ( subscription );
}
loadSubscription ();
}, []);
async function openPortal () {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { url } = await response . json ();
window . location . href = url ;
}
return (
< DropdownMenu >
< DropdownMenuItem onClick = { () => router . push ( '/dashboard' ) } >
Dashboard
</ DropdownMenuItem >
< DropdownMenuItem onClick = { () => router . push ( '/settings' ) } >
Settings
</ DropdownMenuItem >
{ subscription && (
< DropdownMenuItem onClick = { openPortal } >
Manage Billing
</ DropdownMenuItem >
) }
< DropdownMenuItem onClick = { logout } >
Sign Out
</ DropdownMenuItem >
</ DropdownMenu >
);
}
Portal in New Tab
async function openPortalInNewTab () {
const response = await fetch ( '/api/billing/create-portal-session' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
if ( ! response . ok ) {
toast . error ( 'Failed to open billing portal' );
return ;
}
const { url } = await response . json ();
// Open in new tab instead of redirect
window . open ( url , '_blank' );
toast . success ( 'Billing portal opened in new tab' );
}
Portal Return Handling
// In /dashboard/billing page
function BillingPage () {
useEffect (() => {
// Refresh subscription data when returning from portal
async function refreshSubscription () {
const response = await fetch ( '/api/billing/subscription' , {
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
});
const { subscription } = await response . json ();
// Update UI with latest subscription data
setSubscription ( subscription );
// Show success message if recent change
const recentlyUpdated = subscription &&
( new Date () - new Date ( subscription . updatedAt )) < 60000 ; // 1 minute
if ( recentlyUpdated ) {
toast . success ( 'Subscription updated successfully' );
}
}
refreshSubscription ();
}, []);
return < div > Billing Dashboard </ div > ;
}