This repository has been archived by the owner on Jun 3, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
webhook.js
130 lines (121 loc) · 4.33 KB
/
webhook.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const config = require('../config');
const {orders} = require('../stripe/inventory');
const stripe = require('stripe')(config.stripe.secretKey);
stripe.setApiVersion(config.stripe.apiVersion);
/**
* Webhook handler to process payments for sources asynchronously.
* @returns {any}
*/
module.exports = async context => {
let data = context.params.data;
if (config.stripe.webhookSecret) {
// Retrieve the event by verifying the signature using the raw body and secret.
let event;
let signature = context.http.headers['stripe-signature'];
try {
event = stripe.webhooks.constructEvent(
context.http.body,
signature,
config.stripe.webhookSecret
);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`);
return {
body: 'Bad request',
statusCode: 400,
};
}
// Extract the object from the event.
data = event.data;
}
const object = data.object;
// Monitor `source.chargeable` events.
if (
object.object === 'source' &&
object.status === 'chargeable' &&
object.metadata.order
) {
const source = object;
console.log(`🔔 Webhook received! The source ${source.id} is chargeable.`);
// Find the corresponding order this source is for by looking in its metadata.
const order = await orders.retrieve(source.metadata.order);
// Verify that this order actually needs to be paid.
if (
order.metadata.status === 'pending' ||
order.metadata.status === 'paid' ||
order.metadata.status === 'failed'
) {
return {
body: 'Bad request',
statusCode: 400,
};
}
// Note: We're setting an idempotency key below on the charge creation to
// prevent any race conditions. It's set to the order ID, which protects us from
// 2 different sources becoming `chargeable` simultaneously for the same order ID.
// Depending on your use cases and your idempotency keys, you might need an extra
// lock surrounding your webhook code to prevent other race conditions.
// Read more on Stripe's best practices here for asynchronous charge creation:
// https://stripe.com/docs/sources/best-practices#charge-creation
// Pay the order using the source we just received.
let charge, status;
try {
charge = await stripe.charges.create(
{
source: source.id,
amount: order.amount,
currency: order.currency,
receipt_email: order.email,
},
{
// Set a unique idempotency key based on the order ID.
// This is to avoid any race conditions with your webhook handler.
idempotency_key: order.id,
}
);
} catch (err) {
// This is where you handle declines and errors.
// For the demo, we simply set the status to mark the order as failed.
status = 'failed';
}
if (charge && charge.status === 'succeeded') {
status = 'paid';
} else if (charge) {
status = charge.status;
} else {
status = 'failed';
}
// Update the order status based on the charge status.
await orders.update(order.id, {metadata: {status}});
}
if (
object.object === 'charge' &&
object.status === 'succeeded' &&
object.source.metadata.order
) {
const charge = object;
console.log(`🔔 Webhook received! The charge ${charge.id} succeeded.`);
// Find the corresponding order this source is for by looking in its metadata.
const order = await orders.retrieve(charge.source.metadata.order);
// Update the order status to mark it as paid.
await orders.update(order.id, {metadata: {status: 'paid'}});
}
// Monitor `source.failed`, `source.canceled`, and `charge.failed` events.
if (
(object.object === 'source' || object.object === 'charge') &&
(object.status === 'failed' || object.status === 'canceled')
) {
const source = object.source ? object.source : object;
console.log(`🔔 Webhook received! Failure for ${object.id}.`);
if (source.metadata.order) {
// Find the corresponding order this source is for by looking in its metadata.
const order = await orders.retrieve(source.metadata.order);
// Update the order status to mark it as failed.
await orders.update(order.id, {metadata: {status: 'failed'}});
}
}
return {
body: '',
statusCode: 200,
};
};