Repository navigation
Expand file tree
/
Copy pathserver.js
More file actions
83 lines (70 loc) · 2.95 KB
/
Copy pathserver.js
File metadata and controls
83 lines (70 loc) · 2.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import express from 'express';
import Stripe from 'stripe';
// Node 20.12+ can read .env natively — no dotenv dependency needed.
try {
process.loadEnvFile('.env');
} catch {
// No .env file; fall back to whatever is already in the environment.
}
const { STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY } = process.env;
if (!STRIPE_SECRET_KEY || !STRIPE_PUBLISHABLE_KEY) {
console.error('Missing STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY. Copy .env.example to .env first.');
process.exit(1);
}
const stripe = new Stripe(STRIPE_SECRET_KEY);
const app = express();
const PORT = process.env.PORT || 4242;
// The amount lives on the server so the browser can never change what it pays.
// The client sends the same amount to Elements purely so the wallet sheet shows
// the right number; Stripe rejects the confirm if the two disagree.
const AMOUNT = 4200; // $42.00
const CURRENCY = 'usd';
app.use(express.json());
app.use(express.static('public'));
app.get('/api/config', (_req, res) => {
res.json({ publishableKey: STRIPE_PUBLISHABLE_KEY, amount: AMOUNT, currency: CURRENCY });
});
// Asks Stripe whether the domain you're currently browsing from is registered
// for Apple Pay. An unregistered domain is the #1 reason the button silently
// never renders — Stripe just quietly drops it, with no console error.
app.get('/api/domain-status', async (req, res) => {
const host = String(req.query.host || '').split(':')[0];
try {
const { data } = await stripe.paymentMethodDomains.list({ limit: 100 });
const match = data.find((d) => d.domain_name === host);
res.json({
host,
registered: Boolean(match),
enabled: match?.enabled ?? false,
applePayStatus: match?.apple_pay?.status ?? null,
livemode: match?.livemode ?? false,
registeredDomains: data.map((d) => d.domain_name),
});
} catch (err) {
res.status(500).json({ host, error: err.message });
}
});
app.post('/api/create-intent', async (req, res) => {
try {
const intent = await stripe.paymentIntents.create({
amount: AMOUNT,
currency: CURRENCY,
// Lets you turn payment methods on and off from the Stripe Dashboard
// instead of redeploying.
automatic_payment_methods: { enabled: true },
// Apple Pay and Google Pay don't share the customer's email unless you ask
// for it (emailRequired on the Element). This is also where a real
// integration would look up an existing customer by email rather than
// creating a new one on every transaction.
receipt_email: req.body?.email || undefined,
});
res.json({ client_secret: intent.client_secret });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
app.listen(PORT, () => {
console.log(`\n Apple Pay demo running at http://localhost:${PORT}`);
console.log(' Apple Pay needs a public HTTPS domain registered with Stripe.');
console.log(` Run: ngrok http ${PORT} then: npm run register-domain -- <your-ngrok-host>\n`);
});