Skip to content

Commit

Permalink
feat(firestore-stripe-payments): Update payments collection for refunds
Browse files Browse the repository at this point in the history
Requires adding the `charge.refunded` webhook handler event
  • Loading branch information
chetbox committed Jan 9, 2025
1 parent 665e247 commit b4c6ca5
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 0 deletions.
3 changes: 3 additions & 0 deletions firestore-stripe-payments/POSTINSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions firestore-stripe-payments/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
52 changes: 52 additions & 0 deletions firestore-stripe-payments/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,53 @@ 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<Stripe.Charge> & { total_count: number } = (
await paymentRef.get()
).data().charges ?? {
data: [],
has_more: false,
object: 'list',
url: '/v1/charges',
total_count: 0,
};

const chargeIndex = charges.data.findIndex(({ id }) => id === charge.id);
if (chargeIndex !== -1) {
charges.data[chargeIndex] = charge;
} else {
charges.data.push(charge);
charges.total_count++;
}

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.
*/
Expand Down Expand Up @@ -757,6 +804,7 @@ export const handleWebhookEvents = functions.handler.https.onRequest(
'payment_intent.succeeded',
'payment_intent.canceled',
'payment_intent.payment_failed',
'charge.refunded',
]);
let event: Stripe.Event;

Expand Down Expand Up @@ -857,6 +905,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!'),
Expand Down

0 comments on commit b4c6ca5

Please sign in to comment.