Skip to content

Commit afa8617

Browse files
committed
add more Paddle webhook event handlers
1 parent 0a7b8a1 commit afa8617

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

backendApi.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { Plan, PlanName } from './Plan'
2828
import {
2929
handlePaddleSubscriptionActivated,
3030
handlePaddleSubscriptionCanceled,
31+
handlePaddleTransactionCompleted,
32+
handlePaddleTransactionPastDue,
3133
handleStripeCheckoutSessionCompleted,
3234
handleStripeCustomerSubscriptionDeleted,
3335
handleStripeInvoicePaymentFailed,
@@ -242,6 +244,12 @@ app.post('/paddle_webhook', async function (req, res) {
242244
case PaddleEventName.SubscriptionCanceled:
243245
await handlePaddleSubscriptionCanceled(eventData)
244246
break
247+
case PaddleEventName.TransactionPastDue:
248+
await handlePaddleTransactionPastDue(eventData)
249+
break
250+
case PaddleEventName.TransactionCompleted:
251+
await handlePaddleTransactionCompleted(eventData)
252+
break
245253
default:
246254
log.warn(
247255
`Unhandled Paddle event type ${eventData.eventType}: %j`,

subscription.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
Paddle,
1111
SubscriptionActivatedEvent,
1212
SubscriptionCanceledEvent,
13+
TransactionCompletedEvent,
14+
TransactionPastDueEvent,
1315
} from '@paddle/paddle-node-sdk'
1416
import { ok } from 'node:assert/strict'
1517

@@ -75,6 +77,8 @@ export async function handleStripeCheckoutSessionCompleted({
7577
export async function handlePaddleSubscriptionActivated(
7678
event: SubscriptionActivatedEvent
7779
) {
80+
log.info('handlePaddleSubscriptionActivated: %j', event)
81+
7882
const { jwt: token } = event.data.customData as {
7983
jwt: string
8084
}
@@ -96,6 +100,7 @@ export async function handlePaddleSubscriptionActivated(
96100
export async function handlePaddleSubscriptionCanceled(
97101
event: SubscriptionCanceledEvent
98102
) {
103+
log.info('handlePaddleSubscriptionCanceled: %j', event)
99104
const userId = (event.data.customData as { userId: string }).userId
100105

101106
ok(userId, 'userId is missing in customData')
@@ -109,6 +114,38 @@ export async function handlePaddleSubscriptionCanceled(
109114
}
110115
}
111116

117+
export async function handlePaddleTransactionPastDue(
118+
event: TransactionPastDueEvent
119+
) {
120+
log.info('handlePaddleTransactionPastDue: %j', event)
121+
122+
const subscription = await paddle.subscriptions.get(event.data.subscriptionId)
123+
124+
const userId = (subscription.customData as { userId: string }).userId
125+
126+
ok(userId, 'userId is missing in customData')
127+
128+
await switchToPlan(userId, PlanName.FREE)
129+
}
130+
131+
export async function handlePaddleTransactionCompleted(
132+
event: TransactionCompletedEvent
133+
) {
134+
log.info('handlePaddleTransactionCompleted: %j', event)
135+
136+
const subscription = await paddle.subscriptions.get(event.data.subscriptionId)
137+
138+
const userId = (subscription.customData as { userId: string }).userId
139+
140+
// userId might be missing if this is the first transaction of a new customer
141+
// and there is a race condition as we haven't set the customData yet.
142+
// In this case, the user should get PRO access via handlePaddleSubscriptionActivated()
143+
// a few moments later. Since we don't know the userId yet, we can't set the plan here.
144+
if (userId) {
145+
await switchToPlan(userId, PlanName.PRO)
146+
}
147+
}
148+
112149
export async function handleStripeCustomerSubscriptionDeleted({
113150
metadata,
114151
customer: stripeCustomerId,

0 commit comments

Comments
 (0)