Skip to content

Commit 148d82e

Browse files
committed
feat: Add cancellation_details
1 parent 51cfd11 commit 148d82e

File tree

2 files changed

+45
-55
lines changed

2 files changed

+45
-55
lines changed

apps/api/src/stripe/events/stripe-payment.service.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,21 +317,20 @@ export class StripePaymentService {
317317
const subscription: Stripe.Subscription = event.data.object as Stripe.Subscription
318318
Logger.log('[ handleSubscriptionDeleted ]', subscription)
319319

320-
const metadata = subscription.metadata as StripeMetadata & {
321-
cancelReason?: string
322-
currencyConvertedTo?: string
323-
}
324-
325-
// Skip processing if this subscription was canceled for currency conversion
320+
// Check if this subscription was canceled for currency conversion
326321
// The recurring donation will be updated with the new subscription ID, not marked as canceled
327-
if (metadata.cancelReason === 'currency_conversion') {
322+
const cancellationComment = subscription.cancellation_details?.comment
323+
if (cancellationComment?.startsWith('currency_conversion:')) {
324+
const targetCurrency = cancellationComment.split(':')[1]
328325
Logger.log(
329326
`[ handleSubscriptionDeleted ] Subscription ${subscription.id} was canceled for currency ` +
330-
`conversion to ${metadata.currencyConvertedTo}. Skipping status update.`,
327+
`conversion to ${targetCurrency}. Skipping status update.`,
331328
)
332329
return
333330
}
334331

332+
const metadata = subscription.metadata as StripeMetadata
333+
335334
if (!metadata.campaignId) {
336335
throw new BadRequestException(
337336
'Subscription metadata does not contain target campaignId. Subscription is: ' +

apps/api/src/stripe/stripe.service.ts

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -931,31 +931,15 @@ export class StripeService {
931931
originalPeriodEnd: new Date(periodEnd * 1000).toISOString(),
932932
}
933933

934-
// Step 1: Try to mark the old subscription for currency conversion
935-
// This helps the webhook handler skip status updates for this subscription
936-
// If this fails (e.g., subscription already canceled or currency not supported), we continue anyway
937-
try {
938-
await this.stripeClient.subscriptions.update(subscription.id, {
939-
metadata: {
940-
...subscription.metadata,
941-
cancelReason: 'currency_conversion',
942-
currencyConvertedTo: newCurrency.toUpperCase(),
943-
currencyConvertedAt: new Date().toISOString(),
944-
},
945-
})
946-
} catch (error) {
947-
Logger.warn(
948-
`[Stripe] Could not update subscription metadata for ${subscription.id}: ${error.message}. ` +
949-
`Continuing with conversion anyway.`,
950-
)
951-
}
952-
953-
// Step 2: Cancel the old subscription immediately
934+
// Cancel the old subscription immediately
954935
// We need to cancel immediately because Stripe doesn't allow customers to have
955936
// active subscriptions in multiple currencies at the same time
956937
try {
957938
await this.stripeClient.subscriptions.cancel(subscription.id, {
958939
prorate: false, // Don't create prorated credit, we'll use trial instead
940+
cancellation_details: {
941+
comment: `currency_conversion:${newCurrency.toUpperCase()}`,
942+
},
959943
})
960944
} catch (error) {
961945
// If the subscription is already canceled, that's fine - continue with creating the new one
@@ -1035,6 +1019,33 @@ export class StripeService {
10351019
newCurrency: Currency,
10361020
newAmount: number,
10371021
): Promise<void> {
1022+
// First check for the 'converting:' prefix since that's what markRecurringDonationForConversion sets
1023+
const convertingRecord = await this.prisma.recurringDonation.findFirst({
1024+
where: { extSubscriptionId: `converting:${oldSubscriptionId}` },
1025+
})
1026+
1027+
if (convertingRecord) {
1028+
await this.prisma.recurringDonation.update({
1029+
where: { id: convertingRecord.id },
1030+
data: {
1031+
extSubscriptionId: newSubscriptionId,
1032+
currency: newCurrency,
1033+
amount: newAmount,
1034+
// Set status to active - the Stripe "trial" is just a technical mechanism
1035+
// to preserve the billing cycle, not an actual trial period
1036+
status: RecurringDonationStatus.active,
1037+
},
1038+
})
1039+
Logger.debug(
1040+
`[Stripe] Updated recurring donation ${convertingRecord.id}: ` +
1041+
`subscription converting:${oldSubscriptionId} -> ${newSubscriptionId}, ` +
1042+
`currency -> ${newCurrency}, amount -> ${newAmount}, status -> active`,
1043+
)
1044+
return
1045+
}
1046+
1047+
// Fallback: check for old subscription ID directly (in case markRecurringDonationForConversion
1048+
// was skipped or failed)
10381049
const recurringDonation = await this.prisma.recurringDonation.findFirst({
10391050
where: { extSubscriptionId: oldSubscriptionId },
10401051
})
@@ -1046,41 +1057,21 @@ export class StripeService {
10461057
extSubscriptionId: newSubscriptionId,
10471058
currency: newCurrency,
10481059
amount: newAmount,
1049-
// Set status to active - the Stripe "trial" is just a technical mechanism
1050-
// to preserve the billing cycle, not an actual trial period
10511060
status: RecurringDonationStatus.active,
10521061
},
10531062
})
10541063
Logger.debug(
1055-
`[Stripe] Updated local recurring donation ${recurringDonation.id}: ` +
1064+
`[Stripe] Updated recurring donation ${recurringDonation.id}: ` +
10561065
`subscription ${oldSubscriptionId} -> ${newSubscriptionId}, ` +
10571066
`currency -> ${newCurrency}, amount -> ${newAmount}, status -> active`,
10581067
)
1059-
} else {
1060-
// Check if it was marked for conversion (extSubscriptionId prefixed with 'converting:')
1061-
const convertingRecord = await this.prisma.recurringDonation.findFirst({
1062-
where: { extSubscriptionId: `converting:${oldSubscriptionId}` },
1063-
})
1064-
if (convertingRecord) {
1065-
await this.prisma.recurringDonation.update({
1066-
where: { id: convertingRecord.id },
1067-
data: {
1068-
extSubscriptionId: newSubscriptionId,
1069-
currency: newCurrency,
1070-
amount: newAmount,
1071-
status: RecurringDonationStatus.active,
1072-
},
1073-
})
1074-
Logger.debug(
1075-
`[Stripe] Updated converting recurring donation ${convertingRecord.id}: ` +
1076-
`subscription converting:${oldSubscriptionId} -> ${newSubscriptionId}`,
1077-
)
1078-
} else {
1079-
Logger.warn(
1080-
`[Stripe] No local recurring donation found for subscription ${oldSubscriptionId}`,
1081-
)
1082-
}
1068+
return
10831069
}
1070+
1071+
Logger.warn(
1072+
`[Stripe] No local recurring donation found for subscription ${oldSubscriptionId} ` +
1073+
`(checked both 'converting:${oldSubscriptionId}' and '${oldSubscriptionId}')`,
1074+
)
10841075
}
10851076

10861077
/**

0 commit comments

Comments
 (0)