Skip to content

Commit bce47f1

Browse files
committed
added paid extension boilerplate
1 parent a583804 commit bce47f1

File tree

6 files changed

+538
-1
lines changed

6 files changed

+538
-1
lines changed

pages/Billing.jsx

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import React, { useState, useEffect } from "react";
2+
import { useParams, useNavigate } from 'react-router-dom';
3+
import "./style/billing.css";
4+
import axios from "axios";
5+
import urlJoin from "url-join";
6+
7+
const EXAMPLE_MAIN_URL = window.location.origin;
8+
9+
export const Billing = () => {
10+
const [plans, setPlans] = useState([]);
11+
const [currentSubscription, setCurrentSubscription] = useState(null);
12+
const [loading, setLoading] = useState(true);
13+
const [error, setError] = useState(null);
14+
const { company_id, application_id } = useParams();
15+
const navigate = useNavigate();
16+
17+
useEffect(() => {
18+
fetchPlans();
19+
fetchCurrentSubscription();
20+
}, [company_id]);
21+
22+
const fetchPlans = async () => {
23+
try {
24+
const { data } = await axios.get(urlJoin(EXAMPLE_MAIN_URL, '/api/billing/plans'), {
25+
headers: {
26+
"x-company-id": company_id,
27+
},
28+
params: { company_id }
29+
});
30+
31+
// Handle the response structure: {"plans": [...]}
32+
const plansArray = data.plans || data;
33+
34+
// Ensure data is an array
35+
if (Array.isArray(plansArray)) {
36+
setPlans(plansArray);
37+
} else {
38+
console.error("Plans data is not an array:", plansArray);
39+
setPlans([]);
40+
}
41+
} catch (error) {
42+
console.error("Error fetching plans:", error);
43+
setError("Failed to load subscription plans");
44+
setPlans([]);
45+
}
46+
};
47+
48+
const fetchCurrentSubscription = async () => {
49+
try {
50+
const { data } = await axios.get(urlJoin(EXAMPLE_MAIN_URL, '/api/billing/subscription'), {
51+
headers: {
52+
"x-company-id": company_id,
53+
},
54+
params: { company_id }
55+
});
56+
console.log('Current subscription data:', data);
57+
58+
// If we have a subscription with plan_id, fetch the plan details
59+
if (data && data.plan_id) {
60+
try {
61+
const planResponse = await axios.get(urlJoin(EXAMPLE_MAIN_URL, '/api/billing/plans'), {
62+
headers: {
63+
"x-company-id": company_id,
64+
},
65+
params: { company_id }
66+
});
67+
68+
const plans = planResponse.data.plans || planResponse.data;
69+
const currentPlan = plans.find(plan => plan.id === data.plan_id);
70+
71+
if (currentPlan) {
72+
// Merge plan details with subscription data
73+
setCurrentSubscription({
74+
...data,
75+
plan_name: currentPlan.name,
76+
plan_price: currentPlan.price,
77+
plan_interval: currentPlan.interval
78+
});
79+
} else {
80+
setCurrentSubscription(data);
81+
}
82+
} catch (planError) {
83+
console.error("Error fetching plan details:", planError);
84+
setCurrentSubscription(data);
85+
}
86+
} else {
87+
setCurrentSubscription(data);
88+
}
89+
} catch (error) {
90+
console.error("Error fetching subscription:", error);
91+
setCurrentSubscription(null);
92+
} finally {
93+
setLoading(false);
94+
}
95+
};
96+
97+
const handleSubscribe = async (planId) => {
98+
try {
99+
console.log('Subscribing to plan:', planId);
100+
// Use the same callback URL pattern as the working extension
101+
const callbackUrl = `${EXAMPLE_MAIN_URL}/company/${company_id}/subscription-status`;
102+
const { data } = await axios.post(urlJoin(EXAMPLE_MAIN_URL, '/api/billing/subscribe'), {
103+
company_id,
104+
plan_id: planId,
105+
callback_url: callbackUrl
106+
}, {
107+
headers: {
108+
"x-company-id": company_id,
109+
}
110+
});
111+
112+
console.log('Subscription response:', data);
113+
114+
// Redirect to Fynd's billing page
115+
if (data.redirect_url) {
116+
window.location.href = data.redirect_url;
117+
}
118+
} catch (error) {
119+
console.error("Error subscribing to plan:", error);
120+
setError("Failed to subscribe to plan");
121+
}
122+
};
123+
124+
const handleContinueToExtension = () => {
125+
// Navigate to the main extension page
126+
if (application_id) {
127+
navigate(`/company/${company_id}/application/${application_id}`);
128+
} else {
129+
navigate(`/company/${company_id}/`);
130+
}
131+
};
132+
133+
if (loading) {
134+
return <div className="loader">Loading billing information...</div>;
135+
}
136+
137+
if (error) {
138+
return (
139+
<div className="billing-container">
140+
<div className="error-message">
141+
<h3>Error</h3>
142+
<p>{error}</p>
143+
<button onClick={() => window.location.reload()}>Retry</button>
144+
</div>
145+
</div>
146+
);
147+
}
148+
149+
return (
150+
<div className="billing-container">
151+
<div className="title">
152+
Subscription Plans
153+
</div>
154+
155+
{currentSubscription && currentSubscription.status === 'active' && (
156+
<div className="current-subscription">
157+
<h3>Current Subscription</h3>
158+
<div className="subscription-details">
159+
<p><strong>Plan:</strong> {currentSubscription.plan_name}</p>
160+
<p><strong>Status:</strong> {currentSubscription.status}</p>
161+
<p><strong>Activated:</strong> {new Date(currentSubscription.activated_on).toLocaleDateString()}</p>
162+
<p><strong>Price:</strong>{currentSubscription.plan_price?.amount}/{currentSubscription.plan_interval}</p>
163+
</div>
164+
<button
165+
className="continue-button"
166+
onClick={handleContinueToExtension}
167+
>
168+
Continue to Extension
169+
</button>
170+
</div>
171+
)}
172+
173+
{(!currentSubscription || currentSubscription.status !== 'active') && (
174+
<div className="plans-grid">
175+
{Array.isArray(plans) && plans.length > 0 ? (
176+
plans.map((plan) => (
177+
<div key={plan.id} className="plan-card">
178+
<div className="plan-header">
179+
<h3>{plan.name}</h3>
180+
<div className="plan-price">
181+
{plan.price.amount}/{plan.interval}
182+
</div>
183+
</div>
184+
<div className="plan-tagline">{plan.tagline}</div>
185+
<ul className="plan-features">
186+
{plan.features.map((feature, index) => (
187+
<li key={index}>{feature}</li>
188+
))}
189+
</ul>
190+
<button
191+
className="subscribe-button"
192+
onClick={() => handleSubscribe(plan.id)}
193+
disabled={currentSubscription?.plan_id === plan.id && currentSubscription?.status === 'active'}
194+
>
195+
{currentSubscription?.plan_id === plan.id && currentSubscription?.status === 'active' ? 'Current Plan' : 'Subscribe'}
196+
</button>
197+
</div>
198+
))
199+
) : (
200+
<div className="no-plans">
201+
<p>No subscription plans available at the moment.</p>
202+
<button
203+
className="continue-button"
204+
onClick={handleContinueToExtension}
205+
>
206+
Continue to Extension
207+
</button>
208+
</div>
209+
)}
210+
</div>
211+
)}
212+
</div>
213+
);
214+
};

pages/SubscriptionStatus.jsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { useEffect, useState } from "react";
2+
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
3+
import axios from "axios";
4+
import urlJoin from "url-join";
5+
import "./style/subscription-status.css";
6+
7+
const EXAMPLE_MAIN_URL = window.location.origin;
8+
9+
export const SubscriptionStatus = () => {
10+
const [loading, setLoading] = useState(true);
11+
const [error, setError] = useState(null);
12+
const { company_id } = useParams();
13+
const [searchParams] = useSearchParams();
14+
const navigate = useNavigate();
15+
16+
const subscription_id = searchParams.get('subscription_id');
17+
const approved = searchParams.get('approved');
18+
19+
useEffect(() => {
20+
if (approved === 'true' && subscription_id) {
21+
updateSubscriptionStatus();
22+
} else {
23+
// Subscription not approved, redirect to billing
24+
navigate(`/company/${company_id}/billing`);
25+
}
26+
}, [approved, subscription_id]);
27+
28+
const updateSubscriptionStatus = async () => {
29+
try {
30+
console.log('Updating subscription status for:', subscription_id);
31+
32+
const { data } = await axios.post(
33+
urlJoin(EXAMPLE_MAIN_URL, `/api/billing/subscription/${subscription_id}/status`),
34+
{},
35+
{
36+
headers: {
37+
"x-company-id": company_id,
38+
}
39+
}
40+
);
41+
42+
console.log('Subscription status response:', data);
43+
44+
if (data && data.status === "active") {
45+
// Success - redirect to main app
46+
navigate(`/company/${company_id}`);
47+
} else {
48+
// Failed - redirect to billing
49+
console.log('Subscription not active, redirecting to billing');
50+
navigate(`/company/${company_id}/billing`);
51+
}
52+
} catch (err) {
53+
console.error("Error updating subscription status:", err);
54+
setError("Failed to update subscription status");
55+
// Still redirect to billing page after a delay
56+
setTimeout(() => {
57+
navigate(`/company/${company_id}/billing`);
58+
}, 3000);
59+
} finally {
60+
setLoading(false);
61+
}
62+
};
63+
64+
if (loading) {
65+
return (
66+
<div className="subscription-status-container">
67+
<div className="loader">Processing subscription...</div>
68+
</div>
69+
);
70+
}
71+
72+
if (error) {
73+
return (
74+
<div className="subscription-status-container">
75+
<div className="error-message">
76+
<h3>Error</h3>
77+
<p>{error}</p>
78+
<p>Redirecting to billing page...</p>
79+
</div>
80+
</div>
81+
);
82+
}
83+
84+
return null; // Component will redirect
85+
};

0 commit comments

Comments
 (0)