Skip to content

Commit 340a7cf

Browse files
author
root
committed
feat(functions): import whatsanalyze-wrapped Cloud Functions with preview wildcard CORS
1 parent 674aad1 commit 340a7cf

24 files changed

Lines changed: 923 additions & 3 deletions

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ static/sw.js
77
coverage
88
dist
99
node_modules
10+
functions-wrapped/lib

.firebaserc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"projects": {
3-
"default": "whatsanalyze-80665"
3+
"default": "whatsanalyze-80665",
4+
"wrapped-dev": "whatsanalyze-wrapped",
5+
"wrapped-prod": "whatsanalyze-wrapped-prod"
46
}
57
}

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ static/sw.js
1313
.nuxt
1414
.output
1515
node_modules
16+
functions-wrapped/lib
1617
nuxt.config.js

firebase.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515
"*.local"
1616
],
1717
"runtime": "nodejs22"
18+
},
19+
{
20+
"source": "functions-wrapped",
21+
"codebase": "wrapped",
22+
"ignore": [
23+
"node_modules",
24+
".git",
25+
"firebase-debug.log",
26+
"firebase-debug.*.log",
27+
"*.local"
28+
],
29+
"runtime": "nodejs22"
1830
}
1931
],
2032
"emulators": {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Development Environment Variables
2+
# Copy this to .env.dev and fill in your development values
3+
4+
# Public Stripe Key (Test Mode)
5+
STRIPE_PUBLISHABLE_KEY=pk_test_51SVIxL74KJ57kF2wmRNVO0Q7LEu1FD1lVSHpvtdWDP4y4Hqy4UePDJWE1XnUHn4Gf5ABw2ebbl2HB6EKammxHnnG00lmCrZuNL
6+
7+
# Price IDs (Test Mode)
8+
PRO_PRICE_ID=price_1Sc6u074KJ57kF2wxb5cnIZL
9+
10+
# Allowed Origins (comma-separated)
11+
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000,https://wrapped.whatsanalyze.com,https://whatsanalyze.com,https://www.whatsanalyze.com,https://whatsanalyze-80665.web.app,https://whatsanalyze-80665.firebaseapp.com,https://whatsanalyze-80665--*.web.app,https://whatsanalyze-wrapped.web.app,https://whatsanalyze-wrapped--*.web.app
12+
# Base Url used in Email Templates
13+
EMAIL_BASE_URL=https://whatsanalyze-wrapped.web.app
14+
15+
APP_NAME=whatsanalyze-wrapped
16+
17+
# Note: Secrets (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET) should be set via:
18+
# firebase --project dev functions:secrets:set STRIPE_SECRET_KEY
19+
# firebase --project dev functions:secrets:set STRIPE_WEBHOOK_SECRET
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Production Environment Variables
2+
3+
# Public Stripe Key (Live Mode)
4+
STRIPE_PUBLISHABLE_KEY=pk_live_51SWEboL4rDqbYflo9zu46Mte4TajSTLu4q1qTgNF7ywYeqHreCr0xpjptrl4bxhQ0DCClx6D0iRQrttP4waTX5Gy009B1hstFl
5+
6+
# Price IDs (Live Mode)
7+
PRO_PRICE_ID=price_1SgOxVL4rDqbYflowSbSteJQ
8+
9+
# Allowed Origins (comma-separated)
10+
ALLOWED_ORIGINS=https://wrapped.whatsanalyze.com,https://whatsanalyze.com,https://www.whatsanalyze.com,https://whatsanalyze-80665.web.app,https://whatsanalyze-80665.firebaseapp.com,https://whatsanalyze-80665--*.web.app,http://localhost:3000,http://localhost:5173
11+
# Base Url used in Email Templates
12+
EMAIL_BASE_URL=https://wrapped.whatsanalyze.com
13+
14+
APP_NAME=whatsanalyze-wrapped-prod

functions-wrapped/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Compiled JavaScript files
2+
lib/**/*.js
3+
lib/**/*.js.map
4+
5+
# TypeScript v1 declaration files
6+
typings/
7+
8+
# Node.js dependency directory
9+
node_modules/
10+
11+
# Environment files
12+
*.local
13+
.env
14+
.env.*
15+
!.env.example
16+
!.env.dev
17+
!.env.prod
18+
!.env.whatsanalyze-wrapped
19+
!.env.whatsanalyze-wrapped-prod

functions-wrapped/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Project switching
2+
firebase use dev
3+
firebase use prod
4+
# Firebase Functions Setup (Node 22)
5+
6+
## Prerequisites
7+
8+
- Node 22 or higher
9+
- Firebase CLI: `npm install -g firebase-tools`
10+
- both are installed already with nix
11+
- install stripe cli
12+
13+
## Quick Start
14+
15+
### 1. Install Dependencies
16+
17+
```bash
18+
cd functions
19+
npm install
20+
```
21+
22+
### 2. Add Firebase Projects
23+
24+
```bash
25+
firebase use --add
26+
# Alias: dev
27+
# Project: whatsanalyze-dev (or your dev project ID)
28+
29+
firebase use --add
30+
# Alias: prod
31+
# Project: whatsanalyze-prod (or your prod project ID)
32+
```
33+
34+
### 3. Build & Test Locally
35+
36+
```bash
37+
npm run build # Compile TypeScript
38+
npm run dev # Run emulator with functions
39+
```
40+
41+
Visit `http://localhost:5001` for emulator UI.
42+
43+
### 4. Deploy to Dev
44+
45+
```bash
46+
firebase use dev
47+
npm run deploy:dev
48+
```
49+
50+
### 5. Deploy to Prod
51+
52+
```bash
53+
firebase use prod
54+
npm run deploy:prod
55+
```
56+
57+
### 6. Setup Stripe
58+
59+
If You want to test the payment/subscription flow locally, stripe relies on webhook. We use the stripe cli to make sure that we can recieve those locally. Have a look at the stripe section further down.
60+
61+
## Node Version
62+
63+
Functions target **Node 22** (latest LTS supported by Firebase Functions runtime).
64+
65+
## Environment Variables
66+
There are some values which are different between dev and prod. For that we use environment files (`.env.FIREBASE_PROJECT_NAME`). It is possible to overwrite those when running locally by creating a `.env.local` file and adding the values there. This file takes precedence over the `.env.FIREBASE_PROJECT_NAME` file. There are also some values that are sensitive, so we treat them as secrets.
67+
68+
### Secrets
69+
`STRIPE_WEBHOOK_SECRET` and `STRIPE_SECRET_KEY` are secrets which we can not simply put in the `.env.FIREBASE_PROJECT_NAME` files. For that reason we create and manage secrets via the firebase cli:
70+
71+
```bash
72+
firebase --project $PROJECT functions:secrets:set STRIPE_SECRET_KEY
73+
firebase --project $PROJECT functions:secrets:set STRIPE_WEBHOOK_SECRET
74+
```
75+
You can use the `scripts/setup-stripe.sh` file for that. Make sure that there are no whitespaces in the secrets stored. You can access the secret with `firebase --project $PROJECT functions:secrets:access STRIPE_SECRET_KEY`.
76+
77+
## Init mail templates
78+
Install the extension in the firebase console first:
79+
![firebase_email_extension.png](docs/firebase_email_extension.png)
80+
```bash
81+
firebase login
82+
# authenticate gcloud
83+
gcloud auth application-default login
84+
npm run init:templates:dev
85+
```
86+
87+
# Troubleshooting
88+
89+
If you get cors issues when trying to invoke the firebase Callable Cloud Function the most likely issue is that
90+
anonymous access is not allow and needs ot be enabled in gcp.
91+
![gcloud_function_allow_public_access.png](docs/gcloud_function_allow_public_access.png)
92+
93+
94+
# Stripe Firebase Functions
95+
96+
Firebase Cloud Functions for handling Stripe checkout and subscriptions, translated from the [Stripe checkout-single-subscription sample](https://github.com/stripe-samples/checkout-single-subscription).
97+
98+
## Quick Start
99+
100+
### 1. Stripe Local testing
101+
- stripe login
102+
- stripe listen --forward-to http://127.0.0.1:5001/whatsanalyze-wrapped/us-central1/stripeWebhook
103+
- add "Your webhook signing secret is `whsec_2f7....`" output to `.secret.local` file
104+
- `STRIPE_WEBHOOK_SECRET=`whsec_2f7....``
105+
- stripe trigger checkout.session.completed
106+
107+
### 2. Stripe dev testing
108+
- ./scripts/setup-stripe.sh to set the stripe keys (secret + api)
109+
- stripe trigger subscription.payment_succeeded --add "customer:email=stripe@whatsanalyze.com"
110+
111+
### 3. Stripe Production Webhook Testing
112+
113+
To verify webhooks work correctly in production, create a test checkout session:
114+
115+
```bash
116+
npm run stripe:test-checkout:prod
117+
```
118+
119+
This creates a checkout session with a €0.50 test product (`prod_TeTooduQWBMod7`). Open the returned `url` in your browser, complete the purchase with a real card, verify the webhook fires correctly, then refund immediately from Stripe Dashboard.
120+
121+
**Note**: This uses the `STRIPE_SECRET_KEY` from Firebase secrets, so you need Firebase project access.
122+
123+
### 4. Stripe Subscription Renewal testing
124+
In theory it should be enough to set the renewal date to now with this command:
125+
```bash
126+
stripe subscriptions update "$SUB_ID" --billing-cycle-anchor=now --proration-behavior=none
127+
```
128+
And stripe would then handle the rest in the background (automatic invoice creation and payment of that).
129+
But I was not able to make it work. The anchor is successfully set, but the subscription cycle is not happening.
130+
131+
What was working is following this tutorial:
132+
https://docs.stripe.com/billing/testing/test-clocks/simulate-subscriptions
133+
It simulates what would happen by forwarding the time at stripe BE.
134+
135+
## Stripe Web setup
136+
Create api keys and make sure to store them accordingly to the firebase function secret setup (look above).
137+
Create a subscription and add the price_id as env variable accordingly to the firebase environment setup (look above).
138+
Make sure that the webook is correctly configured. Webhook URL looks something like: `https://us-central1-whatsanalyze-wrapped.cloudfunctions.net/stripeWebhook` (This is just an example, make sure to find the correct url. It should be displayed when deploying functions).

functions-wrapped/package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "functions-wrapped",
3+
"scripts": {
4+
"build": "tsc",
5+
"build:watch": "tsc --watch",
6+
"dev": "npm run build -- --watch | firebase emulators:start --only functions",
7+
"shell": "npm run build && firebase functions:shell",
8+
"start": "npm run shell",
9+
"deploy:dev": "npm run build && firebase deploy --only functions --project dev",
10+
"deploy:prod": "npm run build && firebase deploy --only functions --project prod",
11+
"logs": "firebase functions:log",
12+
"secrets:set:dev": "firebase --project dev functions:secrets:set",
13+
"secrets:set:prod": "firebase --project prod functions:secrets:set",
14+
"init:templates:dev": "ts-node scripts/initializeTemplates.ts dev",
15+
"init:templates:prod": "ts-node scripts/initializeTemplates.ts prod",
16+
"subscribe": "stripe trigger subscription.payment_succeeded --add \"customer:email=stripe@whatsanalyze.com\"",
17+
"subscriptions:list": "stripe subscriptions list | jq \".data[] | {id: .id, status: .status, customer: .customer}\"",
18+
"stripe:test-checkout:prod": "STRIPE_API_KEY=$(firebase functions:secrets:access STRIPE_SECRET_KEY --project prod) stripe checkout sessions create --mode=subscription -d \"line_items[0][price]=price_1ShAqrL4rDqbYfloWx53VxpL\" -d \"line_items[0][quantity]=1\" --success-url=\"https://whatsanalyze.com/subscription/verify?session_id={CHECKOUT_SESSION_ID}\" --cancel-url=\"https://whatsanalyze.com\" --live"
19+
},
20+
"engines": {
21+
"node": "22"
22+
},
23+
"main": "lib/index.js",
24+
"dependencies": {
25+
"@google-cloud/functions-framework": "^4.0.1",
26+
"firebase-admin": "^12.6.0",
27+
"firebase-functions": "^6.0.1",
28+
"stripe": "^20.0.0"
29+
},
30+
"devDependencies": {
31+
"firebase-functions-test": "^3.1.0",
32+
"ts-node": "^10.9.2",
33+
"typescript": "^5.7.3"
34+
},
35+
"private": true
36+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as admin from "firebase-admin";
2+
import * as fs from "fs";
3+
4+
const project = process.argv[2] || "dev";
5+
6+
try {
7+
// Read .firebaserc to get project ID
8+
const firebaserc = JSON.parse(fs.readFileSync("../.firebaserc", "utf-8"));
9+
const projectId = firebaserc.projects[project];
10+
11+
if (!projectId) {
12+
console.error(`Project "${project}" not found in .firebaserc`);
13+
process.exit(1);
14+
}
15+
16+
// Initialize with just projectId - Firebase CLI auth is used automatically
17+
admin.initializeApp({
18+
projectId,
19+
});
20+
21+
const db = admin.firestore();
22+
23+
const subscriptionTemplate = {
24+
subject: "Subscription Confirmation - WhatsAnalyze Wrapped",
25+
html: `<html><body>
26+
<h2>Welcome, {{customerName}}!</h2>
27+
<p>Your subscription has been successfully created.</p>
28+
<p>Thank you for joining WhatsAnalyze Wrapped. You now have access to all premium features.</p>
29+
<p><a href="{{loginUrl}}">Open your account</a></p>
30+
<p><small>If the link doesn't work, copy and paste this URL into your browser:<br/>{{loginUrl}}</small></p>
31+
<p><small>Your verification code: {{subscriptionId}}</small></p>
32+
<p>If you have any questions, please reach out to our support team.</p>
33+
<p>Best regards,<br/>WhatsAnalyze Team</p>
34+
</body></html>`,
35+
text: `Welcome, {{customerName}}!\n\nYour subscription has been successfully created.\n\nThank you for joining WhatsAnalyze Wrapped. You now have access to all premium features.\n\nOpen your account: {{loginUrl}}\n\nVerification code (if needed): {{subscriptionId}}\n\nBest regards,\nWhatsAnalyze Team`,
36+
};
37+
38+
db.collection("mailTemplates")
39+
.doc("subscription-confirmation")
40+
.set(subscriptionTemplate)
41+
.then(() => {
42+
console.log(
43+
"✅ Email template 'subscription-confirmation' initialized for project:",
44+
project
45+
);
46+
process.exit(0);
47+
})
48+
.catch((error) => {
49+
console.error("❌ Error initializing email templates:", error);
50+
process.exit(1);
51+
});
52+
} catch (error: any) {
53+
console.error("❌ Error:", error.message);
54+
process.exit(1);
55+
}

0 commit comments

Comments
 (0)