production secret added
This commit is contained in:
27
.firebase/hosting.ZGlzdA.cache
Normal file
27
.firebase/hosting.ZGlzdA.cache
Normal 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
|
||||
@@ -50,6 +50,12 @@ service cloud.firestore {
|
||||
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
|
||||
match /subscribers/{subscriberId} {
|
||||
allow create: if true;
|
||||
|
||||
@@ -7,14 +7,14 @@ initializeApp();
|
||||
const db = getFirestore();
|
||||
|
||||
/**
|
||||
* Shopdm Pay Webhook Handler
|
||||
* Shopdm Pay Webhook Handler (v2)
|
||||
*
|
||||
* Receives payment.successful events from Shopdm Pay.
|
||||
* Matches the invoice_id to a pending donation and creates
|
||||
* a confirmed donor record.
|
||||
*/
|
||||
exports.shopdmPayWebhook = onRequest(
|
||||
{ secrets: ["SHOPDM_MERCHANT_SECRET_DEV"] },
|
||||
{ secrets: ["SHOPDM_MERCHANT_SECRET", "SHOPDM_MERCHANT_SECRET_DEV"], invoker: "public" },
|
||||
async (req, res) => {
|
||||
// Only accept POST requests
|
||||
if (req.method !== "POST") {
|
||||
@@ -24,8 +24,7 @@ exports.shopdmPayWebhook = onRequest(
|
||||
|
||||
const payload = req.body;
|
||||
|
||||
// Verify webhook signature
|
||||
// Supports both dev and prod secrets
|
||||
// Verify webhook signature using sorted keys + HMAC-SHA256 (per Shopdm Pay docs)
|
||||
const secrets = [
|
||||
process.env.SHOPDM_MERCHANT_SECRET,
|
||||
process.env.SHOPDM_MERCHANT_SECRET_DEV,
|
||||
@@ -39,10 +38,7 @@ exports.shopdmPayWebhook = onRequest(
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedPayload = JSON.stringify(
|
||||
payload,
|
||||
Object.keys(payload).sort()
|
||||
);
|
||||
const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
|
||||
|
||||
const isValid = secrets.some((secret) => {
|
||||
const expected = crypto
|
||||
@@ -57,14 +53,21 @@ exports.shopdmPayWebhook = onRequest(
|
||||
res.status(401).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Webhook signature verified successfully");
|
||||
}
|
||||
|
||||
// Only process successful payments
|
||||
if (payload.event !== "payment.successful") {
|
||||
if (payload.event !== "payment.successful" && payload.event !== "payment.success") {
|
||||
res.status(200).send("Event ignored");
|
||||
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;
|
||||
if (!invoiceId) {
|
||||
console.log("No invoice_id in webhook payload, skipping donor creation");
|
||||
@@ -85,6 +88,16 @@ exports.shopdmPayWebhook = onRequest(
|
||||
|
||||
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)
|
||||
if (pending.status === "confirmed") {
|
||||
console.log(`Donation ${invoiceId} already confirmed, skipping`);
|
||||
@@ -103,6 +116,7 @@ exports.shopdmPayWebhook = onRequest(
|
||||
message: pending.message || "",
|
||||
anonymous: !!pending.anonymous,
|
||||
status: "confirmed",
|
||||
env: webhookEnv,
|
||||
pendingDonationId: invoiceId,
|
||||
paymentId: payload.data?.object_id || "",
|
||||
date: FieldValue.serverTimestamp(),
|
||||
|
||||
@@ -24,7 +24,9 @@ export function subscribeToDonors(callback) {
|
||||
(snapshot) => {
|
||||
const donors = [];
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ export function buildPaymentUrl(pendingId, amount, donorName) {
|
||||
reason: reason,
|
||||
invoice_id: pendingId,
|
||||
redirect: 'true',
|
||||
webhook: 'true',
|
||||
});
|
||||
return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user