Skip to content

Commit b4c6ca5

Browse files
committed
feat(firestore-stripe-payments): Update payments collection for refunds
Requires adding the `charge.refunded` webhook handler event
1 parent 665e247 commit b4c6ca5

File tree

4 files changed

+58
-0
lines changed

4 files changed

+58
-0
lines changed

firestore-stripe-payments/POSTINSTALL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Here's how to set up the webhook and configure your extension to use it:
8282
- `payment_intent.succeeded`
8383
- `payment_intent.canceled`
8484
- `payment_intent.payment_failed`
85+
- `charge.refunded` (optional, will sync refunded payments to Cloud Firestore)
8586
- `tax_rate.created` (optional)
8687
- `tax_rate.updated` (optional)
8788
- `invoice.paid` (optional, will sync invoices to Cloud Firestore)
@@ -193,6 +194,8 @@ db.collection("${param:PRODUCTS_COLLECTION}")
193194

194195
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`.
195196

197+
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`.
198+
196199
To create a Checkout Session ID for a one-time payment, pass `mode: 'payment` to the Checkout Session doc creation:
197200

198201
```js

firestore-stripe-payments/extension.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,5 @@ events:
349349
description:
350350
Occurs when a PaymentIntent has failed the attempt to create a payment
351351
method or a payment.
352+
- type: com.stripe.v1.charge.refunded
353+
description: Occurs whenever a charge is refunded.

firestore-stripe-payments/functions/__tests__/helpers/webhooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const setupWebhooks = async (url) => {
1818
'payment_intent.succeeded',
1919
'payment_intent.canceled',
2020
'payment_intent.payment_failed',
21+
'charge.refunded',
2122
'tax_rate.created',
2223
'tax_rate.updated',
2324
'invoice.paid',

firestore-stripe-payments/functions/src/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,53 @@ const insertPaymentRecord = async (
727727
logs.firestoreDocCreated('payments', payment.id);
728728
};
729729

730+
/**
731+
* Update the charges for a payment intent.
732+
*/
733+
const updateChargeForPaymentIntent = async (charge: Stripe.Charge) => {
734+
const customersSnap = await admin
735+
.firestore()
736+
.collection(config.customersCollectionPath)
737+
.where('stripeId', '==', charge.customer)
738+
.get();
739+
if (customersSnap.size !== 1) {
740+
throw new Error('User not found!');
741+
}
742+
743+
const paymentId =
744+
typeof charge.payment_intent === 'string'
745+
? charge.payment_intent
746+
: charge.payment_intent.id;
747+
748+
const paymentRef = customersSnap.docs[0].ref
749+
.collection('payments')
750+
.doc(paymentId);
751+
752+
const charges: Stripe.ApiList<Stripe.Charge> & { total_count: number } = (
753+
await paymentRef.get()
754+
).data().charges ?? {
755+
data: [],
756+
has_more: false,
757+
object: 'list',
758+
url: '/v1/charges',
759+
total_count: 0,
760+
};
761+
762+
const chargeIndex = charges.data.findIndex(({ id }) => id === charge.id);
763+
if (chargeIndex !== -1) {
764+
charges.data[chargeIndex] = charge;
765+
} else {
766+
charges.data.push(charge);
767+
charges.total_count++;
768+
}
769+
770+
await customersSnap.docs[0].ref
771+
.collection('payments')
772+
.doc(paymentId)
773+
.set({ charges }, { merge: true });
774+
logs.firestoreDocCreated('payments', paymentId);
775+
};
776+
730777
/**
731778
* A webhook handler function for the relevant Stripe events.
732779
*/
@@ -757,6 +804,7 @@ export const handleWebhookEvents = functions.handler.https.onRequest(
757804
'payment_intent.succeeded',
758805
'payment_intent.canceled',
759806
'payment_intent.payment_failed',
807+
'charge.refunded',
760808
]);
761809
let event: Stripe.Event;
762810

@@ -857,6 +905,10 @@ export const handleWebhookEvents = functions.handler.https.onRequest(
857905
const paymentIntent = event.data.object as Stripe.PaymentIntent;
858906
await insertPaymentRecord(paymentIntent);
859907
break;
908+
case 'charge.refunded':
909+
const charge = event.data.object as Stripe.Charge;
910+
await updateChargeForPaymentIntent(charge);
911+
break;
860912
default:
861913
logs.webhookHandlerError(
862914
new Error('Unhandled relevant event!'),

0 commit comments

Comments
 (0)