From 665e247cf6f182fe50e1685e27b7aba1d386813a Mon Sep 17 00:00:00 2001 From: Darren Ackers Date: Tue, 20 Aug 2024 17:41:00 +0100 Subject: [PATCH 1/2] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bf8f1440..e14b68dd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # UPDATE 2023-10-08: -This project has now being officailly transferred to [Invertase](https://github.com/invertase), who will maintain this extension going forward. Please see [this issue](https://github.com/stripe/stripe-firebase-extensions/issues/524) for more details. -It is now reccomended to uninstall the `stripe/firestore-stripe-payments` extension and install `invertase/firestore-stripe-payments` from the Firebase Extension Hub. +This project has now being officially transferred to [Invertase](https://github.com/invertase), who will maintain this extension going forward. Please see [this issue](https://github.com/stripe/stripe-firebase-extensions/issues/524) for more details. +It is now recommended to uninstall the `stripe/firestore-stripe-payments` extension and install `invertase/firestore-stripe-payments` from the Firebase Extension Hub. -Alternativley, you can also use the following link to convert your current installation to the Invertase version +Alternatively, you can also use the following link to convert your current installation to the Invertase version `https://console.firebase.google.com/project/_/extensions/install?instanceId=STRIPE_EXTENSION_INSTANCE_ID&ref=invertase%2Ffirestore-stripe-payments@0.3.5` From 055cf57f5f7b437039a7dfefbe99b12b64614c1d Mon Sep 17 00:00:00 2001 From: Chetan Padia Date: Thu, 9 Jan 2025 12:51:58 +0000 Subject: [PATCH 2/2] feat(firestore-stripe-payments): Update payments collection for refunds Requires adding the `charge.refunded` webhook handler event --- firestore-stripe-payments/POSTINSTALL.md | 3 ++ firestore-stripe-payments/extension.yaml | 2 + .../functions/__tests__/helpers/webhooks.ts | 1 + .../functions/src/index.ts | 50 +++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/firestore-stripe-payments/POSTINSTALL.md b/firestore-stripe-payments/POSTINSTALL.md index 186e0387..2ae1010b 100644 --- a/firestore-stripe-payments/POSTINSTALL.md +++ b/firestore-stripe-payments/POSTINSTALL.md @@ -82,6 +82,7 @@ Here's how to set up the webhook and configure your extension to use it: - `payment_intent.succeeded` - `payment_intent.canceled` - `payment_intent.payment_failed` + - `charge.refunded` (optional, will sync refunded payments to Cloud Firestore) - `tax_rate.created` (optional) - `tax_rate.updated` (optional) - `invoice.paid` (optional, will sync invoices to Cloud Firestore) @@ -193,6 +194,8 @@ db.collection("${param:PRODUCTS_COLLECTION}") You can create Checkout Sessions for one-time payments when referencing a one-time price ID. One-time payments will be synced to Cloud Firestore into a payments collection for the relevant customer doc if you update your webhook handler in the Stripe dashboard to include the following events: `payment_intent.succeeded`, `payment_intent.payment_failed`, `payment_intent.canceled`, `payment_intent.processing`. +If a payment is refunded in Stripe the associated payment in the payments collection can be updated. To update the payments collection for refunds add the following events to your webhook handler in the Stripe dashboard: `charge.refunded`. + To create a Checkout Session ID for a one-time payment, pass `mode: 'payment` to the Checkout Session doc creation: ```js diff --git a/firestore-stripe-payments/extension.yaml b/firestore-stripe-payments/extension.yaml index bbccfd0e..accc2ab2 100644 --- a/firestore-stripe-payments/extension.yaml +++ b/firestore-stripe-payments/extension.yaml @@ -349,3 +349,5 @@ events: description: Occurs when a PaymentIntent has failed the attempt to create a payment method or a payment. + - type: com.stripe.v1.charge.refunded + description: Occurs whenever a charge is refunded. diff --git a/firestore-stripe-payments/functions/__tests__/helpers/webhooks.ts b/firestore-stripe-payments/functions/__tests__/helpers/webhooks.ts index 1c77bd11..9c5c5cf9 100644 --- a/firestore-stripe-payments/functions/__tests__/helpers/webhooks.ts +++ b/firestore-stripe-payments/functions/__tests__/helpers/webhooks.ts @@ -18,6 +18,7 @@ export const setupWebhooks = async (url) => { 'payment_intent.succeeded', 'payment_intent.canceled', 'payment_intent.payment_failed', + 'charge.refunded', 'tax_rate.created', 'tax_rate.updated', 'invoice.paid', diff --git a/firestore-stripe-payments/functions/src/index.ts b/firestore-stripe-payments/functions/src/index.ts index 74a72472..542b480d 100644 --- a/firestore-stripe-payments/functions/src/index.ts +++ b/firestore-stripe-payments/functions/src/index.ts @@ -727,6 +727,51 @@ const insertPaymentRecord = async ( logs.firestoreDocCreated('payments', payment.id); }; +/** + * Update the charges for a payment intent. + */ +const updateChargeForPaymentIntent = async (charge: Stripe.Charge) => { + const customersSnap = await admin + .firestore() + .collection(config.customersCollectionPath) + .where('stripeId', '==', charge.customer) + .get(); + if (customersSnap.size !== 1) { + throw new Error('User not found!'); + } + + const paymentId = + typeof charge.payment_intent === 'string' + ? charge.payment_intent + : charge.payment_intent.id; + + const paymentRef = customersSnap.docs[0].ref + .collection('payments') + .doc(paymentId); + + const charges: Stripe.ApiList = ( + await paymentRef.get() + ).data().charges ?? { + data: [], + has_more: false, + object: 'list', + url: '/v1/charges', + }; + + const chargeIndex = charges.data.findIndex(({ id }) => id === charge.id); + if (chargeIndex !== -1) { + charges.data[chargeIndex] = charge; + } else { + charges.data.push(charge); + } + + await customersSnap.docs[0].ref + .collection('payments') + .doc(paymentId) + .set({ charges }, { merge: true }); + logs.firestoreDocCreated('payments', paymentId); +}; + /** * A webhook handler function for the relevant Stripe events. */ @@ -757,6 +802,7 @@ export const handleWebhookEvents = functions.handler.https.onRequest( 'payment_intent.succeeded', 'payment_intent.canceled', 'payment_intent.payment_failed', + 'charge.refunded', ]); let event: Stripe.Event; @@ -857,6 +903,10 @@ export const handleWebhookEvents = functions.handler.https.onRequest( const paymentIntent = event.data.object as Stripe.PaymentIntent; await insertPaymentRecord(paymentIntent); break; + case 'charge.refunded': + const charge = event.data.object as Stripe.Charge; + await updateChargeForPaymentIntent(charge); + break; default: logs.webhookHandlerError( new Error('Unhandled relevant event!'),