production secret added

This commit is contained in:
2026-03-24 14:16:27 -04:00
parent d772b7ec9c
commit 1300084481
5 changed files with 60 additions and 10 deletions

View File

@@ -0,0 +1,27 @@
thank-you.html,1774294010867,a98548e027b70a2458181b1f196d304ba6b6fab91579d9beca30bdbdadc7d0d4
images/whs-logo.svg,1774294010745,f2f19a1d66481b40716d2fae51d3d95dbf0b9617838ff17a881872a3a828cd73
admin.html,1774294010867,8af127db93bb249aa05e8d1161f60cbac31895135f496507a0dbd2eac501dff7
index.html,1774294010867,7a8dd6e44319df434aa173ca63eab80a8ff1a47299dde4496e0b228acfab66bc
images/image-8.jpeg,1774294010737,1fec63c572754a3ad0e9c5a15cfcc805c53379ae76e37d21f69a800616a6d358
images/image-5.jpeg,1774294010736,4cc8d453cd23f99f35a1e586ccc2df3b41ec0174a86d0a96a31157d4d0eb27ca
images/image-4.jpeg,1774294010736,843a3e70184b50e1c011092b35abc51c7b8484f32f6f07ae1cd469ba37a551fc
images/image-3.jpeg,1774294010735,8d9ca0c0bc4a905bade23eda94e41fda805c1f00234f8194bdfef0350f99966c
images/favicon.svg,1774294010733,bbc0b0e1127a983a4959994990de60d262fabc2841eaafd1da90f3722e2f4316
images/image-2.jpeg,1774294010735,24aeeaf130a7b960d996192827b9b6142ab6d41f1d87ff49418ac484b60f2887
images/image-10.jpeg,1774294010734,af37c6807cadfd5c5d39848fbacb96022038eeca29a5f7aafe05aa48ea001fcf
assets/style-B2qcWQAK.css,1774294010867,8d9acb18fb8a1d37e887273bce7fd56a36a9c2268383c842e52df6abfac65c56
assets/admin-r6G2dek0.css,1774294010867,6ac9a2ac211285672f35c9a7c38ef92debe018923b2ae8936382173be7bb8a6c
assets/main-CCpTsy09.js,1774294010867,9666c47573d9b382b8924a05258ccf2fba644594f050b7fcaca3e6a03490d31b
assets/admin-CsJ8T6K2.js,1774294010867,8a6602998b0e3569ca2f07d245806f04057dd221cbe2ef910b72bb1325a7ba7a
images/logo-white.png,1774294010744,2323f3f0fb9e21f903d72a49a2986d1fa0b54a2504ecd9b0292537fb7991d8fc
images/image-7.jpeg,1774294010737,cb7578c686fd0944fff610a639e99dc16333db0d293bb8ba9a2a233d35faeb8a
images/image-9.jpeg,1774294010738,f90c90cacc15bb5a0cdbcbaf3f7d04188578d5f416c86b5a6926da5c19638b78
images/logo-white.jpeg,1774294010744,d9da6cd236f2da999142179a51130ae2ce16ab62498aeffde5708a26ef1bbf5d
images/image-1.jpeg,1774294010733,ecd0ecf9188d492a91f3eefaf11aa34a8767beafcc205e4e3976f06370a2d16f
images/image-6.jpeg,1774294010736,c1b308cfc1cd79523c04b0d77901a1025c95d7526bb446dbc064cd75d80c549e
images/logo-orange.jpeg,1774294010743,27bb4f3c065919ff52cf6bd1a493cc2fd7cd14451316307105f18768f724491a
images/logo-black.jpeg,1774294010738,30b9860ce62ac0f5e47f6b5a0e36674f67ae358ffa368099264c84b2ee82db7d
images/logo-blue.jpeg,1774294010740,7263a76d431ec3327052ea46e43d8fc126f5e50353fff32d2b27fe729ff72f26
images/image-11.jpeg,1774294010734,ff55cff55378fddf547dbb8a9d4f46be690417117ceacd3dc422fb051b462c75
assets/donors-BfBdprwo.js,1774294010867,25ba97a96b4b6acf626544695794c0be01d25aa62ef6c3b4d647c862d1eb8c13
images/logo-blue.png,1774294010742,1164ff2f709525030b5f4be9794880c6374177516ad22a4789bf4a3b5ecd9160

View File

@@ -50,6 +50,12 @@ service cloud.firestore {
allow update: if false; allow update: if false;
} }
// Pending donations (created before payment, confirmed via webhook)
match /pendingDonations/{donationId} {
allow create: if true;
allow read, update, delete: if false;
}
// Newsletter subscribers // Newsletter subscribers
match /subscribers/{subscriberId} { match /subscribers/{subscriberId} {
allow create: if true; allow create: if true;

View File

@@ -7,14 +7,14 @@ initializeApp();
const db = getFirestore(); const db = getFirestore();
/** /**
* Shopdm Pay Webhook Handler * Shopdm Pay Webhook Handler (v2)
* *
* Receives payment.successful events from Shopdm Pay. * Receives payment.successful events from Shopdm Pay.
* Matches the invoice_id to a pending donation and creates * Matches the invoice_id to a pending donation and creates
* a confirmed donor record. * a confirmed donor record.
*/ */
exports.shopdmPayWebhook = onRequest( exports.shopdmPayWebhook = onRequest(
{ secrets: ["SHOPDM_MERCHANT_SECRET_DEV"] }, { secrets: ["SHOPDM_MERCHANT_SECRET", "SHOPDM_MERCHANT_SECRET_DEV"], invoker: "public" },
async (req, res) => { async (req, res) => {
// Only accept POST requests // Only accept POST requests
if (req.method !== "POST") { if (req.method !== "POST") {
@@ -24,8 +24,7 @@ exports.shopdmPayWebhook = onRequest(
const payload = req.body; const payload = req.body;
// Verify webhook signature // Verify webhook signature using sorted keys + HMAC-SHA256 (per Shopdm Pay docs)
// Supports both dev and prod secrets
const secrets = [ const secrets = [
process.env.SHOPDM_MERCHANT_SECRET, process.env.SHOPDM_MERCHANT_SECRET,
process.env.SHOPDM_MERCHANT_SECRET_DEV, process.env.SHOPDM_MERCHANT_SECRET_DEV,
@@ -39,10 +38,7 @@ exports.shopdmPayWebhook = onRequest(
return; return;
} }
const sortedPayload = JSON.stringify( const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
payload,
Object.keys(payload).sort()
);
const isValid = secrets.some((secret) => { const isValid = secrets.some((secret) => {
const expected = crypto const expected = crypto
@@ -57,14 +53,21 @@ exports.shopdmPayWebhook = onRequest(
res.status(401).send("Unauthorized"); res.status(401).send("Unauthorized");
return; return;
} }
console.log("Webhook signature verified successfully");
} }
// Only process successful payments // Only process successful payments
if (payload.event !== "payment.successful") { if (payload.event !== "payment.successful" && payload.event !== "payment.success") {
res.status(200).send("Event ignored"); res.status(200).send("Event ignored");
return; return;
} }
// Determine environment from the live flag in the payload
const isLive = payload.live === true;
const webhookEnv = isLive ? "production" : "dev";
console.log(`Webhook environment: ${webhookEnv} (live=${payload.live})`);
const invoiceId = payload.metadata?.invoice_id; const invoiceId = payload.metadata?.invoice_id;
if (!invoiceId) { if (!invoiceId) {
console.log("No invoice_id in webhook payload, skipping donor creation"); console.log("No invoice_id in webhook payload, skipping donor creation");
@@ -85,6 +88,16 @@ exports.shopdmPayWebhook = onRequest(
const pending = pendingDoc.data(); const pending = pendingDoc.data();
// Ensure the webhook environment matches the pending donation environment
const pendingEnv = pending.env || "production";
if (pendingEnv !== webhookEnv) {
console.warn(
`Environment mismatch: pending donation is ${pendingEnv} but webhook is ${webhookEnv}. Skipping.`
);
res.status(200).send("OK - environment mismatch");
return;
}
// Check if already processed (idempotency) // Check if already processed (idempotency)
if (pending.status === "confirmed") { if (pending.status === "confirmed") {
console.log(`Donation ${invoiceId} already confirmed, skipping`); console.log(`Donation ${invoiceId} already confirmed, skipping`);
@@ -103,6 +116,7 @@ exports.shopdmPayWebhook = onRequest(
message: pending.message || "", message: pending.message || "",
anonymous: !!pending.anonymous, anonymous: !!pending.anonymous,
status: "confirmed", status: "confirmed",
env: webhookEnv,
pendingDonationId: invoiceId, pendingDonationId: invoiceId,
paymentId: payload.data?.object_id || "", paymentId: payload.data?.object_id || "",
date: FieldValue.serverTimestamp(), date: FieldValue.serverTimestamp(),

View File

@@ -24,7 +24,9 @@ export function subscribeToDonors(callback) {
(snapshot) => { (snapshot) => {
const donors = []; const donors = [];
snapshot.forEach((doc) => { snapshot.forEach((doc) => {
donors.push({ id: doc.id, ...doc.data() }); const data = doc.data();
if (data.env === 'dev') return; // skip test donations
donors.push({ id: doc.id, ...data });
}); });
callback(donors); callback(donors);
}, },

View File

@@ -35,6 +35,7 @@ export function buildPaymentUrl(pendingId, amount, donorName) {
reason: reason, reason: reason,
invoice_id: pendingId, invoice_id: pendingId,
redirect: 'true', redirect: 'true',
webhook: 'true',
}); });
return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`; return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`;
} }