curl -X POST https://www.wiseyield.co/api/billing/create-portal-session \
-H "Authorization: Bearer wy_YOUR_API_KEY"
const response = await fetch(
'https://www.wiseyield.co/api/billing/create-portal-session',
{
method: 'POST',
headers: {
'Authorization': 'Bearer wy_YOUR_API_KEY'
}
}
);
const { url } = await response.json();
// Redirect user to Dodo Payments billing portal
window.location.href = url;
async function openBillingPortal() {
try {
const response = await fetch('/api/billing/create-portal-session', {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}` }
});
if (!response.ok) {
const error = await response.json();
toast.error(error.error);
return;
}
const { url } = await response.json();
window.location.href = url;
} catch (error) {
toast.error('Failed to open billing portal');
}
}
// Usage
await openBillingPortal();
import requests
response = requests.post(
'https://www.wiseyield.co/api/billing/create-portal-session',
headers={'Authorization': 'Bearer wy_YOUR_API_KEY'}
)
data = response.json()
portal_url = data['url']
# Redirect user to this URL
print(f"Portal URL: {portal_url}")
{
"url": "https://portal.dodopayments.com/session/ps_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
{
"error": "Unauthorized"
}
{
"error": "No billing customer found"
}
{
"error": "Failed to create portal session"
}
Create Portal Session
Create a Dodo Payments billing portal session for managing subscription
POST
/
api
/
billing
/
create-portal-session
curl -X POST https://www.wiseyield.co/api/billing/create-portal-session \
-H "Authorization: Bearer wy_YOUR_API_KEY"
const response = await fetch(
'https://www.wiseyield.co/api/billing/create-portal-session',
{
method: 'POST',
headers: {
'Authorization': 'Bearer wy_YOUR_API_KEY'
}
}
);
const { url } = await response.json();
// Redirect user to Dodo Payments billing portal
window.location.href = url;
async function openBillingPortal() {
try {
const response = await fetch('/api/billing/create-portal-session', {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}` }
});
if (!response.ok) {
const error = await response.json();
toast.error(error.error);
return;
}
const { url } = await response.json();
window.location.href = url;
} catch (error) {
toast.error('Failed to open billing portal');
}
}
// Usage
await openBillingPortal();
import requests
response = requests.post(
'https://www.wiseyield.co/api/billing/create-portal-session',
headers={'Authorization': 'Bearer wy_YOUR_API_KEY'}
)
data = response.json()
portal_url = data['url']
# Redirect user to this URL
print(f"Portal URL: {portal_url}")
{
"url": "https://portal.dodopayments.com/session/ps_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
{
"error": "Unauthorized"
}
{
"error": "No billing customer found"
}
{
"error": "Failed to create portal session"
}
Endpoint
POST https://www.wiseyield.co/api/billing/create-portal-session
Authentication
Requires a valid API key withbilling:write scope.
Request Body
No request body required.Response
Returns a Dodo Payments billing portal URL.string
Dodo Payments billing portal URL to redirect user to
curl -X POST https://www.wiseyield.co/api/billing/create-portal-session \
-H "Authorization: Bearer wy_YOUR_API_KEY"
const response = await fetch(
'https://www.wiseyield.co/api/billing/create-portal-session',
{
method: 'POST',
headers: {
'Authorization': 'Bearer wy_YOUR_API_KEY'
}
}
);
const { url } = await response.json();
// Redirect user to Dodo Payments billing portal
window.location.href = url;
async function openBillingPortal() {
try {
const response = await fetch('/api/billing/create-portal-session', {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}` }
});
if (!response.ok) {
const error = await response.json();
toast.error(error.error);
return;
}
const { url } = await response.json();
window.location.href = url;
} catch (error) {
toast.error('Failed to open billing portal');
}
}
// Usage
await openBillingPortal();
import requests
response = requests.post(
'https://www.wiseyield.co/api/billing/create-portal-session',
headers={'Authorization': 'Bearer wy_YOUR_API_KEY'}
)
data = response.json()
portal_url = data['url']
# Redirect user to this URL
print(f"Portal URL: {portal_url}")
{
"url": "https://portal.dodopayments.com/session/ps_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
{
"error": "Unauthorized"
}
{
"error": "No billing customer found"
}
{
"error": "Failed to create portal session"
}
Errors
error
Unauthorized - Missing or invalid API key
error
Not Found - User has no billing customer ID (never subscribed)
error
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
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
Contact Information
- 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;
}
Manage Subscription Button
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>
);
}
Portal Button with Confirmation
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>
);
}
Portal Link in Dropdown Menu
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>;
}
Related Endpoints
- Get Subscription - GET current subscription status
- Create Checkout Session - POST to start new subscription
- Get Invoices - GET payment history
- Get Usage - GET current usage metrics