diff --git a/fluxer_api/src/Tables.ts b/fluxer_api/src/Tables.ts index 6a67cb08..17f30b52 100644 --- a/fluxer_api/src/Tables.ts +++ b/fluxer_api/src/Tables.ts @@ -560,10 +560,15 @@ export const GiftCodesByCreator = defineTable({ +export const GiftCodesByPaymentIntent = defineTable< + GiftCodeByPaymentIntentRow, + 'stripe_payment_intent_id' | 'code', + 'stripe_payment_intent_id' +>({ name: 'gift_codes_by_payment_intent', columns: GIFT_CODE_BY_PAYMENT_INTENT_COLUMNS, - primaryKey: ['stripe_payment_intent_id'], + primaryKey: ['stripe_payment_intent_id', 'code'], + partitionKey: ['stripe_payment_intent_id'], }); export const GiftCodesByRedeemer = defineTable({ diff --git a/fluxer_api/src/stripe/services/StripeGiftService.ts b/fluxer_api/src/stripe/services/StripeGiftService.ts index 91f95817..36e6e6d9 100644 --- a/fluxer_api/src/stripe/services/StripeGiftService.ts +++ b/fluxer_api/src/stripe/services/StripeGiftService.ts @@ -154,6 +154,21 @@ export class StripeGiftService { Logger.debug({checkoutSessionId, code: payment.giftCode}, 'Gift code already exists for checkout session'); return; } + if (paymentIntentId) { + const existingGift = await this.userRepository.findGiftCodeByPaymentIntent(paymentIntentId); + if (existingGift) { + await this.userRepository.linkGiftCodeToCheckoutSession(existingGift.code, checkoutSessionId); + await this.userRepository.updatePayment({ + ...payment.toRow(), + gift_code: existingGift.code, + }); + Logger.warn( + {checkoutSessionId, paymentIntentId, code: existingGift.code}, + 'Recovered existing gift code for checkout session', + ); + return; + } + } const code = await this.generateUniqueGiftCode(); let visionarySequenceNumber: number | null = null; diff --git a/fluxer_api/src/stripe/services/StripeWebhookService.ts b/fluxer_api/src/stripe/services/StripeWebhookService.ts index cd3a63e3..a2bd1ade 100644 --- a/fluxer_api/src/stripe/services/StripeWebhookService.ts +++ b/fluxer_api/src/stripe/services/StripeWebhookService.ts @@ -114,10 +114,14 @@ export class StripeWebhookService { return; } - if (payment.status !== 'pending') { + const recoverGiftWithoutCode = payment.isGift && payment.status === 'completed' && !payment.giftCode; + if (payment.status !== 'pending' && !recoverGiftWithoutCode) { Logger.debug({sessionId: session.id, status: payment.status}, 'Payment already processed'); return; } + if (recoverGiftWithoutCode) { + Logger.warn({sessionId: session.id}, 'Recovering gift checkout with missing gift code'); + } const productInfo = this.productRegistry.getProduct(payment.priceId!); if (!productInfo) { @@ -140,7 +144,7 @@ export class StripeWebhookService { amount_cents: session.amount_total || 0, currency: session.currency || 'usd', status: 'completed', - completed_at: new Date(), + completed_at: payment.completedAt ?? new Date(), }); const customerId = extractId(session.customer);