TutorialsAPI call
Any file in /server/{handler}.ts in the
/
folder are API endpoints.
Protected API calls
Supabase automatically handles the authentication with cookies. Just make a normal API call on the front-end like this:
/app/services/api/api.service.ts
...
@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
async createBillingPortal(data: { returnUrl: string }) {
const {
data: { session },
} = await this.authService.getSession();
if (!session) {
throw new Error('User must be logged in');
}
return this.http
.post<{
url: string;
}>(`/api/stripe/create-billing-portal`, data, {
headers: { authorization: `Bearer ${session.access_token}` },
})
.toPromise();
}
...
/app/pages/home.component.html
<button class="btn btn-primary" (click)="handleBilling()">
<span *ngIf="isLoading" class="loading loading-spinner loading-sm"></span>
Purchase
</button>
/app/pages/home.component.ts
export class HomeComponent {
constructor(private apiService: ApiService) {
}
handleBilling() {
try {
await this.apiService.createBillingPortal({returnUrl: 'https://shipangular.com'})
}catch(err){
// handle error
}
}
The API file should look like this:
/server/stripe.ts
router.post('/create-checkout-session', bodyParser.json(), async (req, res) => {
const token = req.headers.authorization?.split('Bearer ')[1];
if (!token) {
return res.status(401).send('Unauthorized');
}
const {
data: { user },
} = await supabase.auth.getUser(token);
if (!user) {
return res.status(401).send('Unauthorized');
}
const { priceId, successUrl, cancelUrl } = req.body;
if (!priceId) {
res.status(400).send({ message: 'Price ID is required' });
return;
} else if (!successUrl || !cancelUrl) {
res.status(400).send({ message: 'Success and cancel URLs are required' });
return;
}
const stripeSession = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [
{
price: priceId,
quantity: 1,
},
],
discounts: couponId
? [
{
coupon: couponId,
},
]
: [],
customer_creation: 'always',
client_reference_id: user.id,
success_url: successUrl,
cancel_url: cancelUrl,
tax_id_collection: { enabled: true },
});
return res.status(200).send({ url: stripeSession.url });
});
There are two ways to make api calls based on our preference:
- Through API Routes defined in
/server
- Through cloud functions defined in
/functions
The firebase-admin-sdk
does not handle firestore calls well through an SSR app. Thus you will notice the firebase SDK was used instead here.
We have left it up to the developer to decide the way they want to go. Below are the guidelines for handling calls that are defined as API routes in /server
folder.
The code is identical in handling it with cloud functions.
API Calls
Any file in /server/{handler}.ts in the
/
folder are API endpoints.
Protected API calls
Firebase handles verification of tokens through their SDK. Just make a normal API call on the front-end like this:
/app/services/auth/auth.service.ts
...
export class AuthService {
user: firebase.User | null;
constructor(private afAuth: AngularFireAuth) {}
async getIdToken() {
const user = await this.afAuth.currentUser;
if (user) {
return user.getIdToken();
} else {
throw new Error('User not authenticated');
}
}
...
/app/services/api/api.service.ts
...
@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
async createBillingPortal(data: { returnUrl: string }) {
const idToken = await this.authService.getIdToken();
return this.http
.post<{
url: string;
}>(`/api/stripe/create-billing-portal`, data, {
headers: { authorization: `Bearer ${idToken}` },
})
.toPromise();
}
...
/app/pages/home.component.html
<button class="btn btn-primary" (click)="handleBilling()">
<span *ngIf="isLoading" class="loading loading-spinner loading-sm"></span>
Purchase
</button>
/app/pages/home.component.ts
export class HomeComponent {
constructor(private apiService: ApiService) {
}
handleBilling() {
try {
await this.apiService.createBillingPortal({returnUrl: 'https://shipangular.com'})
}catch(err){
// handle error
}
}
The API file should look like this:
/server/stripe.ts
router.post('/create-checkout-session', bodyParser.json(), async (req, res) => {
const token = req.headers.authorization?.split('Bearer ')[1];
if (!token) {
return res.status(401).send('Unauthorized');
}
let decodedToken;
try {
decodedToken = await admin.auth().verifyIdToken(idToken);
} catch (error) {
console.log('Failed to decode the ID token: ', error);
res.status(401).send({ message: 'TOKEN_EXPIRED' });
return;
}
try {
const userId = decodedToken.uid;
const { priceId, successUrl, cancelUrl } = req.body;
if (!priceId) {
res.status(400).send({ message: 'Price ID is required' });
return;
} else if (!successUrl || !cancelUrl) {
res.status(400).send({ message: 'Success and cancel URLs are required' });
return;
}
const stripeSession = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [
{
price: priceId,
quantity: 1,
},
],
discounts: couponId
? [
{
coupon: couponId,
},
]
: [],
customer_creation: 'always',
client_reference_id: userId,
success_url: successUrl,
cancel_url: cancelUrl,
tax_id_collection: { enabled: true },
});
return res.status(200).send({ url: stripeSession.url });
});
Going to production?
- Navigate to the
/functions
folder - run
npm run deploy
in your firebase project - Update the endpoints in your Stripe Webhooks
- Update the
apiBaseUrl
in your/environments/environment.ts