@windrun-huaiin/backend-core 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/app/api/stripe/checkout/route.d.ts +19 -0
- package/dist/app/api/stripe/checkout/route.d.ts.map +1 -0
- package/dist/app/api/stripe/checkout/route.js +120 -0
- package/dist/app/api/stripe/checkout/route.mjs +118 -0
- package/dist/app/api/stripe/customer-portal/route.d.ts +11 -0
- package/dist/app/api/stripe/customer-portal/route.d.ts.map +1 -0
- package/dist/app/api/stripe/customer-portal/route.js +73 -0
- package/dist/app/api/stripe/customer-portal/route.mjs +71 -0
- package/dist/app/api/user/anonymous/init/route.d.ts +7 -0
- package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -0
- package/dist/app/api/user/anonymous/init/route.js +210 -0
- package/dist/app/api/user/anonymous/init/route.mjs +208 -0
- package/dist/app/api/webhook/clerk/user/route.d.ts +7 -0
- package/dist/app/api/webhook/clerk/user/route.d.ts.map +1 -0
- package/dist/app/api/webhook/clerk/user/route.js +202 -0
- package/dist/app/api/webhook/clerk/user/route.mjs +200 -0
- package/dist/app/api/webhook/stripe/route.d.ts +8 -0
- package/dist/app/api/webhook/stripe/route.d.ts.map +1 -0
- package/dist/app/api/webhook/stripe/route.js +70 -0
- package/dist/app/api/webhook/stripe/route.mjs +67 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.mjs +18 -0
- package/dist/lib/auth-utils.d.ts +46 -0
- package/dist/lib/auth-utils.d.ts.map +1 -0
- package/dist/lib/auth-utils.js +107 -0
- package/dist/lib/auth-utils.mjs +102 -0
- package/dist/lib/credit-init.d.ts +8 -0
- package/dist/lib/credit-init.d.ts.map +1 -0
- package/dist/lib/credit-init.js +16 -0
- package/dist/lib/credit-init.mjs +10 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +31 -0
- package/dist/lib/index.mjs +4 -0
- package/dist/lib/money-price-config.d.ts +51 -0
- package/dist/lib/money-price-config.d.ts.map +1 -0
- package/dist/lib/money-price-config.js +156 -0
- package/dist/lib/money-price-config.mjs +151 -0
- package/dist/lib/stripe-config.d.ts +31 -0
- package/dist/lib/stripe-config.d.ts.map +1 -0
- package/dist/lib/stripe-config.js +278 -0
- package/dist/lib/stripe-config.mjs +268 -0
- package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +48 -0
- package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs +45 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.js +54 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.mjs +51 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.js +44 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.mjs +35 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js +31 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.mjs +18 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.js +587 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.mjs +527 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.js +447 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.mjs +399 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.js +245 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.mjs +232 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.js +68 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.mjs +62 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.js +39 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.mjs +37 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.js +80 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.mjs +75 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.js +101 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.mjs +86 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.js +102 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.mjs +76 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js +56 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.mjs +52 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.js +1205 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.mjs +1157 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.js +407 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.mjs +374 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.js +9 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.mjs +7 -0
- package/dist/prisma/client.d.ts +2 -0
- package/dist/prisma/client.d.ts.map +1 -0
- package/dist/prisma/client.js +12 -0
- package/dist/prisma/client.mjs +1 -0
- package/dist/prisma/index.d.ts +4 -0
- package/dist/prisma/index.d.ts.map +1 -0
- package/dist/prisma/index.js +10 -0
- package/dist/prisma/index.mjs +2 -0
- package/dist/prisma/prisma-transaction-util.d.ts +3 -0
- package/dist/prisma/prisma-transaction-util.d.ts.map +1 -0
- package/dist/prisma/prisma-transaction-util.js +29 -0
- package/dist/prisma/prisma-transaction-util.mjs +27 -0
- package/dist/prisma/prisma.d.ts +4 -0
- package/dist/prisma/prisma.d.ts.map +1 -0
- package/dist/prisma/prisma.js +109 -0
- package/dist/prisma/prisma.mjs +106 -0
- package/dist/services/aggregate/billing.aggregate.service.d.ts +83 -0
- package/dist/services/aggregate/billing.aggregate.service.d.ts.map +1 -0
- package/dist/services/aggregate/billing.aggregate.service.js +308 -0
- package/dist/services/aggregate/billing.aggregate.service.mjs +306 -0
- package/dist/services/aggregate/index.d.ts +3 -0
- package/dist/services/aggregate/index.d.ts.map +1 -0
- package/dist/services/aggregate/index.js +9 -0
- package/dist/services/aggregate/index.mjs +2 -0
- package/dist/services/aggregate/user.aggregate.service.d.ts +34 -0
- package/dist/services/aggregate/user.aggregate.service.d.ts.map +1 -0
- package/dist/services/aggregate/user.aggregate.service.js +136 -0
- package/dist/services/aggregate/user.aggregate.service.mjs +133 -0
- package/dist/services/context/index.d.ts +2 -0
- package/dist/services/context/index.d.ts.map +1 -0
- package/dist/services/context/index.js +13 -0
- package/dist/services/context/index.mjs +1 -0
- package/dist/services/context/user-context-service.d.ts +30 -0
- package/dist/services/context/user-context-service.d.ts.map +1 -0
- package/dist/services/context/user-context-service.js +170 -0
- package/dist/services/context/user-context-service.mjs +162 -0
- package/dist/services/database/apilog.service.d.ts +39 -0
- package/dist/services/database/apilog.service.d.ts.map +1 -0
- package/dist/services/database/apilog.service.js +174 -0
- package/dist/services/database/apilog.service.mjs +170 -0
- package/dist/services/database/constants.d.ts +73 -0
- package/dist/services/database/constants.d.ts.map +1 -0
- package/dist/services/database/constants.js +135 -0
- package/dist/services/database/constants.mjs +117 -0
- package/dist/services/database/credit.service.d.ts +107 -0
- package/dist/services/database/credit.service.d.ts.map +1 -0
- package/dist/services/database/credit.service.js +515 -0
- package/dist/services/database/credit.service.mjs +512 -0
- package/dist/services/database/creditAuditLog.service.d.ts +73 -0
- package/dist/services/database/creditAuditLog.service.d.ts.map +1 -0
- package/dist/services/database/creditAuditLog.service.js +305 -0
- package/dist/services/database/creditAuditLog.service.mjs +302 -0
- package/dist/services/database/index.d.ts +10 -0
- package/dist/services/database/index.d.ts.map +1 -0
- package/dist/services/database/index.js +38 -0
- package/dist/services/database/index.mjs +8 -0
- package/dist/services/database/prisma-model-type.d.ts +3 -0
- package/dist/services/database/prisma-model-type.d.ts.map +1 -0
- package/dist/services/database/subscription.service.d.ts +48 -0
- package/dist/services/database/subscription.service.d.ts.map +1 -0
- package/dist/services/database/subscription.service.js +267 -0
- package/dist/services/database/subscription.service.mjs +264 -0
- package/dist/services/database/transaction.service.d.ts +92 -0
- package/dist/services/database/transaction.service.d.ts.map +1 -0
- package/dist/services/database/transaction.service.js +326 -0
- package/dist/services/database/transaction.service.mjs +323 -0
- package/dist/services/database/user.service.d.ts +45 -0
- package/dist/services/database/user.service.d.ts.map +1 -0
- package/dist/services/database/user.service.js +180 -0
- package/dist/services/database/user.service.mjs +177 -0
- package/dist/services/database/userBackup.service.d.ts +45 -0
- package/dist/services/database/userBackup.service.d.ts.map +1 -0
- package/dist/services/database/userBackup.service.js +249 -0
- package/dist/services/database/userBackup.service.mjs +246 -0
- package/dist/services/stripe/index.d.ts +2 -0
- package/dist/services/stripe/index.d.ts.map +1 -0
- package/dist/services/stripe/index.js +7 -0
- package/dist/services/stripe/index.mjs +1 -0
- package/dist/services/stripe/webhook-handler.d.ts +6 -0
- package/dist/services/stripe/webhook-handler.d.ts.map +1 -0
- package/dist/services/stripe/webhook-handler.js +537 -0
- package/dist/services/stripe/webhook-handler.mjs +535 -0
- package/migrations/create.sql +176 -0
- package/migrations/db.init.sql +13 -0
- package/migrations/init-schema.sql +19 -0
- package/migrations/purge.sql +27 -0
- package/migrations/test-check.sql +167 -0
- package/package.json +123 -0
- package/prisma/schema.prisma +191 -0
- package/src/app/api/stripe/checkout/route.ts +145 -0
- package/src/app/api/stripe/customer-portal/route.ts +83 -0
- package/src/app/api/user/anonymous/init/route.ts +284 -0
- package/src/app/api/webhook/clerk/user/route.ts +249 -0
- package/src/app/api/webhook/stripe/route.ts +93 -0
- package/src/index.ts +6 -0
- package/src/lib/auth-utils.ts +101 -0
- package/src/lib/credit-init.ts +9 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/money-price-config.ts +168 -0
- package/src/lib/stripe-config.ts +333 -0
- package/src/prisma/client.ts +2 -0
- package/src/prisma/index.ts +3 -0
- package/src/prisma/prisma-transaction-util.ts +24 -0
- package/src/prisma/prisma.ts +122 -0
- package/src/services/aggregate/billing.aggregate.service.ts +498 -0
- package/src/services/aggregate/index.ts +2 -0
- package/src/services/aggregate/user.aggregate.service.ts +168 -0
- package/src/services/context/index.ts +1 -0
- package/src/services/context/user-context-service.ts +200 -0
- package/src/services/database/apilog.service.ts +185 -0
- package/src/services/database/constants.ts +148 -0
- package/src/services/database/credit.service.ts +747 -0
- package/src/services/database/creditAuditLog.service.ts +402 -0
- package/src/services/database/index.ts +41 -0
- package/src/services/database/prisma-model-type.ts +13 -0
- package/src/services/database/subscription.service.ts +319 -0
- package/src/services/database/transaction.service.ts +447 -0
- package/src/services/database/user.service.ts +218 -0
- package/src/services/database/userBackup.service.ts +290 -0
- package/src/services/stripe/index.ts +1 -0
- package/src/services/stripe/webhook-handler.ts +648 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { billingAggregateService } from '../aggregate/index';
|
|
4
|
+
import {
|
|
5
|
+
Apilogger,
|
|
6
|
+
BillingReason,
|
|
7
|
+
OrderStatus,
|
|
8
|
+
PaymentStatus,
|
|
9
|
+
subscriptionService,
|
|
10
|
+
transactionService,
|
|
11
|
+
TransactionType
|
|
12
|
+
} from '../database/index';
|
|
13
|
+
import { Transaction } from '../database/prisma-model-type';
|
|
14
|
+
import { oneTimeExpiredDays } from '../../lib/credit-init';
|
|
15
|
+
import { getCreditsFromPriceId } from '../../lib/money-price-config';
|
|
16
|
+
import { fetchPaymentId, stripe } from '../../lib/stripe-config';
|
|
17
|
+
import Stripe from 'stripe';
|
|
18
|
+
import { viewLocalTime } from '@windrun-huaiin/lib/utils';
|
|
19
|
+
|
|
20
|
+
const mapPaymentStatus = (
|
|
21
|
+
status?: Stripe.Checkout.Session.PaymentStatus | null
|
|
22
|
+
): PaymentStatus => {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case 'paid':
|
|
25
|
+
return PaymentStatus.PAID;
|
|
26
|
+
case 'no_payment_required':
|
|
27
|
+
return PaymentStatus.NO_PAYMENT_REQUIRED;
|
|
28
|
+
case 'unpaid':
|
|
29
|
+
default:
|
|
30
|
+
return PaymentStatus.UN_PAID;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isPaymentSettled = (paymentStatus: PaymentStatus) =>
|
|
35
|
+
paymentStatus === PaymentStatus.PAID || paymentStatus === PaymentStatus.NO_PAYMENT_REQUIRED;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Main event handler - routes events to specific handlers
|
|
39
|
+
*/
|
|
40
|
+
export async function handleStripeEvent(event: Stripe.Event) {
|
|
41
|
+
console.log(`Processing Stripe event: ${event.type}`);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
switch (event.type) {
|
|
45
|
+
// ===== Checkout Events =====
|
|
46
|
+
case 'checkout.session.completed':
|
|
47
|
+
return await handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session);
|
|
48
|
+
|
|
49
|
+
case 'checkout.session.async_payment_succeeded':
|
|
50
|
+
return await handleAsyncPaymentSucceeded(event.data.object as Stripe.Checkout.Session);
|
|
51
|
+
|
|
52
|
+
case 'checkout.session.async_payment_failed':
|
|
53
|
+
return await handleAsyncPaymentFailed(event.data.object as Stripe.Checkout.Session);
|
|
54
|
+
|
|
55
|
+
// ===== Invoice Events (Subscription renewals) =====
|
|
56
|
+
case 'invoice.paid':
|
|
57
|
+
return await handleInvoicePaid(event.data.object as Stripe.Invoice);
|
|
58
|
+
|
|
59
|
+
case 'invoice.payment_failed':
|
|
60
|
+
return await handleInvoicePaymentFailed(event.data.object as Stripe.Invoice);
|
|
61
|
+
|
|
62
|
+
// ===== Subscription Events =====
|
|
63
|
+
case 'customer.subscription.created':
|
|
64
|
+
return await handleSubscriptionCreated(event.data.object as Stripe.Subscription);
|
|
65
|
+
|
|
66
|
+
case 'customer.subscription.updated':
|
|
67
|
+
return await handleSubscriptionUpdated(event.data.object as Stripe.Subscription);
|
|
68
|
+
|
|
69
|
+
case 'customer.subscription.deleted':
|
|
70
|
+
return await handleSubscriptionDeleted(event.data.object as Stripe.Subscription);
|
|
71
|
+
|
|
72
|
+
// ===== Payment Intent Events (One-time payments) =====
|
|
73
|
+
case 'payment_intent.succeeded':
|
|
74
|
+
console.log(`Payment Intent succeeded: ${(event.data.object as Stripe.PaymentIntent).id}`);
|
|
75
|
+
// Usually handled by checkout.session.completed
|
|
76
|
+
return;
|
|
77
|
+
|
|
78
|
+
case 'payment_intent.payment_failed':
|
|
79
|
+
console.log(`Payment Intent failed: ${(event.data.object as Stripe.PaymentIntent).id}`);
|
|
80
|
+
return;
|
|
81
|
+
|
|
82
|
+
// ===== Refund Events =====
|
|
83
|
+
case 'charge.refunded':
|
|
84
|
+
return await handleChargeRefunded(event.data.object as Stripe.Charge);
|
|
85
|
+
|
|
86
|
+
default:
|
|
87
|
+
console.log(`Unhandled event type: ${event.type}`);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`Error processing event ${event.type}:`, error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
|
|
96
|
+
console.log(`Checkout session completed: ${session.id}`);
|
|
97
|
+
|
|
98
|
+
// 1. Get transaction by session ID or order ID
|
|
99
|
+
const orderId = session.metadata?.order_id;
|
|
100
|
+
if (!orderId) {
|
|
101
|
+
throw new Error('Missing order_id in session metadata');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const transaction = await transactionService.findByOrderId(orderId);
|
|
105
|
+
if (!transaction) {
|
|
106
|
+
throw new Error(`Transaction not found: ${orderId}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (transaction.orderStatus === OrderStatus.SUCCESS) {
|
|
110
|
+
throw new Error(`Transaction already processed successfully: ${transaction.orderId}, skipping.`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Stripe docs: checkout.session.completed fires even when payment is pending for async methods
|
|
114
|
+
// https://stripe.com/docs/payments/checkout/one-time#webhooks
|
|
115
|
+
const paymentStatus = mapPaymentStatus(session.payment_status);
|
|
116
|
+
|
|
117
|
+
if (!isPaymentSettled(paymentStatus)) {
|
|
118
|
+
console.log( `Checkout session ${session.id} payment incomplete (status=${session.payment_status}), awaiting async confirmation.`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
transaction.orderStatus === OrderStatus.CREATED ||
|
|
123
|
+
transaction.orderStatus === OrderStatus.PENDING_UNPAID
|
|
124
|
+
) {
|
|
125
|
+
await transactionService.updateStatus(orderId, OrderStatus.PENDING_UNPAID, {
|
|
126
|
+
payUpdatedAt: new Date(),
|
|
127
|
+
paymentStatus,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 2. Route based on transaction type
|
|
134
|
+
if (transaction.type === TransactionType.SUBSCRIPTION) {
|
|
135
|
+
// For subscriptions, store session info and wait for invoice.paid
|
|
136
|
+
return await handleSubscriptionCheckoutInit(session, transaction);
|
|
137
|
+
}
|
|
138
|
+
return await handleOneTimeCheckout(session, transaction, paymentStatus);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function handleSubscriptionCheckoutInit(
|
|
142
|
+
session: Stripe.Checkout.Session,
|
|
143
|
+
transaction: Transaction,
|
|
144
|
+
) {
|
|
145
|
+
console.log(`Processing subscription checkout: ${session.id}`);
|
|
146
|
+
|
|
147
|
+
// 1. Get subscription ID from session
|
|
148
|
+
if (!session.subscription) {
|
|
149
|
+
throw new Error('No subscription ID in checkout session');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const subscriptionId = session.subscription as string;
|
|
153
|
+
|
|
154
|
+
// ===== STEP 1: FETCH EXTERNAL API DATA (BEFORE TRANSACTION) =====
|
|
155
|
+
// 2. Get COMPLETE Stripe subscription details including billing period
|
|
156
|
+
const stripeSubscription = await stripe.subscriptions.retrieve(subscriptionId);
|
|
157
|
+
|
|
158
|
+
// Extract billing period from subscription items (NOT from top-level subscription object)
|
|
159
|
+
// The current_period_start/end are on SubscriptionItem, not on Subscription
|
|
160
|
+
const subscriptionItem = stripeSubscription.items?.data?.[0];
|
|
161
|
+
if (!subscriptionItem) {
|
|
162
|
+
throw new Error( `No subscription items found for subscription ${subscriptionId}` );
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentPeriodStart = subscriptionItem.current_period_start;
|
|
166
|
+
const currentPeriodEnd = subscriptionItem.current_period_end;
|
|
167
|
+
|
|
168
|
+
if (!currentPeriodStart || !currentPeriodEnd) {
|
|
169
|
+
throw new Error( `Invalid subscription period from Stripe API: start=${currentPeriodStart}, end=${currentPeriodEnd}` );
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const subPeriodStart = new Date(currentPeriodStart * 1000);
|
|
173
|
+
const subPeriodEnd = new Date(currentPeriodEnd * 1000);
|
|
174
|
+
|
|
175
|
+
// Log the Stripe API response with correct data structure
|
|
176
|
+
const logId = await Apilogger.logStripeOutgoing(
|
|
177
|
+
'stripe.subscriptions.retrieve',
|
|
178
|
+
{ subscriptionId },
|
|
179
|
+
{
|
|
180
|
+
id: stripeSubscription.id,
|
|
181
|
+
status: stripeSubscription.status,
|
|
182
|
+
subPeriodStart: subPeriodStart,
|
|
183
|
+
subPeriodEnd: subPeriodEnd,
|
|
184
|
+
subscriptionItemCount: stripeSubscription.items?.data?.length || 0,
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
Apilogger.updateResponse(logId, stripeSubscription);
|
|
188
|
+
|
|
189
|
+
console.log('Subscription checkout completed, just log:', {
|
|
190
|
+
id: subscriptionId,
|
|
191
|
+
orderId: transaction.orderId,
|
|
192
|
+
status: stripeSubscription.status,
|
|
193
|
+
periodStart: viewLocalTime(subPeriodStart),
|
|
194
|
+
periodEnd: viewLocalTime(subPeriodEnd),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function handleOneTimeCheckout(
|
|
199
|
+
session: Stripe.Checkout.Session,
|
|
200
|
+
transaction: Transaction,
|
|
201
|
+
paymentStatus: PaymentStatus
|
|
202
|
+
) {
|
|
203
|
+
console.log(`Processing one-time payment checkout: ${session.id}`);
|
|
204
|
+
// 1. Calculate one-time credit expiration
|
|
205
|
+
const now = new Date();
|
|
206
|
+
const oneTimePaidStart = now;
|
|
207
|
+
const oneTimePaidEnd = new Date(now);
|
|
208
|
+
oneTimePaidEnd.setDate(oneTimePaidEnd.getDate() + oneTimeExpiredDays);
|
|
209
|
+
oneTimePaidEnd.setHours(23, 59, 59, 999);
|
|
210
|
+
|
|
211
|
+
await billingAggregateService.completeOneTimeCheckout(
|
|
212
|
+
{
|
|
213
|
+
userId: transaction.userId,
|
|
214
|
+
orderId: transaction.orderId,
|
|
215
|
+
creditsGranted: transaction.creditsGranted || 0,
|
|
216
|
+
paymentStatus,
|
|
217
|
+
payTransactionId: session.payment_intent as string,
|
|
218
|
+
paidEmail: session.customer_details?.email,
|
|
219
|
+
paidAt: oneTimePaidStart,
|
|
220
|
+
oneTimePaidStart,
|
|
221
|
+
oneTimePaidEnd,
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
console.log(`One-time payment completed: ${transaction.orderId}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function handleInvoicePaid(invoice: Stripe.Invoice) {
|
|
229
|
+
console.log(`Invoice paid: ${invoice.id}`);
|
|
230
|
+
|
|
231
|
+
// ===== STEP 1: EXTRACT AND VALIDATE DATA FROM INVOICE (BEFORE TRANSACTION) =====
|
|
232
|
+
// 1. Get subscription details from invoice parent
|
|
233
|
+
const parentDetails = (invoice as any).parent?.subscription_details;
|
|
234
|
+
if (!parentDetails?.subscription) {
|
|
235
|
+
throw new Error('Invoice not associated with subscription, skipping');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 2. Check billing reason to determine payment type
|
|
239
|
+
const isInitialPayment = invoice.billing_reason === BillingReason.SUBSCRIPTION_CREATE;
|
|
240
|
+
const isRenewal = invoice.billing_reason === BillingReason.SUBSCRIPTION_CYCLE;
|
|
241
|
+
|
|
242
|
+
// Only handle initial payments and renewals
|
|
243
|
+
if (!isInitialPayment && !isRenewal) {
|
|
244
|
+
throw new Error(`Unhandled invoice billing_reason: ${invoice.billing_reason}, skipping`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 3. Extract subscription period from invoice line items
|
|
248
|
+
const lineItem = invoice.lines?.data?.[0];
|
|
249
|
+
if (!lineItem) {
|
|
250
|
+
throw new Error(`No line items found in invoice ${invoice.id}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const periodStart = (lineItem as any).period?.start;
|
|
254
|
+
const periodEnd = (lineItem as any).period?.end;
|
|
255
|
+
if (!periodStart || !periodEnd) {
|
|
256
|
+
throw new Error( `Invalid period in invoice line: start=${periodStart}, end=${periodEnd}. Invoice ID: ${invoice.id}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const subPeriodStart = new Date(periodStart * 1000);
|
|
260
|
+
const subPeriodEnd = new Date(periodEnd * 1000);
|
|
261
|
+
|
|
262
|
+
const subscriptionMetadata = parentDetails.metadata || {};
|
|
263
|
+
const orderId = subscriptionMetadata.order_id;
|
|
264
|
+
if (!orderId) {
|
|
265
|
+
throw new Error( `No order_id in subscription metadata for initial invoice ${invoice.id}. ` + `Skipping invoice URL update.` );
|
|
266
|
+
}
|
|
267
|
+
const transaction = await transactionService.findByOrderId(orderId);
|
|
268
|
+
if (!transaction) {
|
|
269
|
+
throw new Error(`Transaction not found for order_id: ${orderId}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const subscriptionId = parentDetails.subscription;
|
|
273
|
+
|
|
274
|
+
const userId = transaction.userId;
|
|
275
|
+
const paymentIntentId = await fetchPaymentId(invoice.id)
|
|
276
|
+
|
|
277
|
+
const invoicePaidAt = invoice.status_transitions?.paid_at;
|
|
278
|
+
const paidAt = invoicePaidAt ? new Date(invoicePaidAt * 1000) : new Date();
|
|
279
|
+
const paidEmail = invoice.customer_email;
|
|
280
|
+
|
|
281
|
+
console.log('Invoice paid event key-info:', {
|
|
282
|
+
invoiceId: invoice.id,
|
|
283
|
+
subscriptionId,
|
|
284
|
+
paymentIntentId,
|
|
285
|
+
billingReason: invoice.billing_reason,
|
|
286
|
+
isInitialPayment,
|
|
287
|
+
paidEmail,
|
|
288
|
+
paidAt: viewLocalTime(paidAt),
|
|
289
|
+
periodStart: viewLocalTime(subPeriodStart),
|
|
290
|
+
periodEnd: viewLocalTime(subPeriodEnd),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (isInitialPayment) {
|
|
294
|
+
// 首次订阅校验
|
|
295
|
+
const nonActiveSubscription = await subscriptionService.getNonActiveSubscription(userId);
|
|
296
|
+
if (!nonActiveSubscription) {
|
|
297
|
+
throw new Error(`Subscription status is ACTIVE for user ${userId}, forbidden to re-active!`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await billingAggregateService.recordSubscriptionInitPayment(
|
|
301
|
+
{
|
|
302
|
+
userId,
|
|
303
|
+
subIdKey: nonActiveSubscription.id,
|
|
304
|
+
orderId,
|
|
305
|
+
paySubscriptionId: subscriptionId,
|
|
306
|
+
creditsGranted: transaction.creditsGranted || 0,
|
|
307
|
+
priceId: transaction.priceId,
|
|
308
|
+
priceName: transaction.priceName,
|
|
309
|
+
periodStart: subPeriodStart,
|
|
310
|
+
periodEnd: subPeriodEnd,
|
|
311
|
+
invoiceId: invoice.id,
|
|
312
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
|
313
|
+
invoicePdf: invoice.invoice_pdf,
|
|
314
|
+
billingReason: invoice.billing_reason,
|
|
315
|
+
paymentIntentId,
|
|
316
|
+
paidAt,
|
|
317
|
+
paidEmail
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
console.log(`Initial invoice recorded for transaction: ${orderId}`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (isRenewal) {
|
|
326
|
+
// 续订时,一定要查到订阅记录
|
|
327
|
+
const subscription = await subscriptionService.findByPaySubscriptionId(subscriptionId);
|
|
328
|
+
if (!subscription) {
|
|
329
|
+
throw new Error(`Subscription not found for renewal: ${subscriptionId}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const renewalOrderId = `order_renew_${invoice.id}`;
|
|
333
|
+
const existingOrder = await transactionService.findByOrderId(renewalOrderId);
|
|
334
|
+
if (existingOrder) {
|
|
335
|
+
throw new Error(`Renewal invoice ${invoice.id} already processed as ${existingOrder.orderId}, skipping.`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Get credits from current price configuration (handles plan upgrades/downgrades)
|
|
339
|
+
// 优先从配置中取,取不到就以上个周期的为准作为Fallback,后续有问题再人工补偿,优先保证能用
|
|
340
|
+
// 只要配置正确,这里就不会出错!
|
|
341
|
+
const creditsForRenewal = subscription.priceId
|
|
342
|
+
? getCreditsFromPriceId(subscription.priceId)
|
|
343
|
+
: subscription.creditsAllocated;
|
|
344
|
+
|
|
345
|
+
const renewalCredits = creditsForRenewal || subscription.creditsAllocated;
|
|
346
|
+
|
|
347
|
+
await billingAggregateService.recordSubscriptionRenewalPayment(
|
|
348
|
+
{
|
|
349
|
+
userId,
|
|
350
|
+
subIdKey: subscription.id,
|
|
351
|
+
orderId: renewalOrderId,
|
|
352
|
+
paySubscriptionId: subscriptionId,
|
|
353
|
+
creditsGranted: renewalCredits,
|
|
354
|
+
priceId: subscription.priceId,
|
|
355
|
+
priceName: subscription.priceName,
|
|
356
|
+
periodStart: subPeriodStart,
|
|
357
|
+
periodEnd: subPeriodEnd,
|
|
358
|
+
invoiceId: invoice.id,
|
|
359
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
|
360
|
+
invoicePdf: invoice.invoice_pdf,
|
|
361
|
+
billingReason: invoice.billing_reason,
|
|
362
|
+
paymentIntentId,
|
|
363
|
+
paidAt: paidAt,
|
|
364
|
+
paidEmail,
|
|
365
|
+
amountPaidCents: invoice.amount_paid,
|
|
366
|
+
currency: invoice.currency,
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
console.log(`Invoice renewal paid event completed, and invoiceId: ${invoice.id}, subscriptionId: ${subscription.id}, orderId: ${renewalOrderId}`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Handle customer.subscription.deleted
|
|
377
|
+
*/
|
|
378
|
+
async function handleSubscriptionDeleted(stripeSubscription: Stripe.Subscription) {
|
|
379
|
+
const subscriptionId = stripeSubscription.id;
|
|
380
|
+
console.log(`Subscription deleted: ${subscriptionId}`);
|
|
381
|
+
|
|
382
|
+
const userCanceledAt = stripeSubscription.canceled_at;
|
|
383
|
+
if (!userCanceledAt) {
|
|
384
|
+
throw new Error( `Invalid period in invoice line: canceldAt=${userCanceledAt}, subscriptionId=${subscriptionId}` );
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const subscription = await subscriptionService.findByPaySubscriptionId(subscriptionId);
|
|
388
|
+
if (!subscription) {
|
|
389
|
+
throw new Error(`Subscription id invalid: ${subscriptionId}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const orderId = subscription.orderId;
|
|
393
|
+
if (!orderId) {
|
|
394
|
+
throw new Error(`Subscription's orderId is NULL: ${subscriptionId}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const transaction = await transactionService.findByOrderId(orderId);
|
|
398
|
+
if (!transaction) {
|
|
399
|
+
throw new Error(`Subscription's orderId is illegal: subscriptionId=${subscriptionId}, orderId=${orderId}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const canceledAt = new Date(userCanceledAt * 1000);
|
|
403
|
+
const cancellationDetail = stripeSubscription.cancellation_details ? JSON.stringify(stripeSubscription.cancellation_details) : undefined;
|
|
404
|
+
await billingAggregateService.processSubscriptionCancel(
|
|
405
|
+
{
|
|
406
|
+
userId: subscription.userId,
|
|
407
|
+
subIdKey: subscription.id,
|
|
408
|
+
orderId,
|
|
409
|
+
canceledAt,
|
|
410
|
+
cancellationDetail
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
console.log(`Subscription status updated to canceled: ${subscription.id}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function handleAsyncPaymentSucceeded(session: Stripe.Checkout.Session) {
|
|
418
|
+
console.log(`Async payment succeeded: ${session.id}`);
|
|
419
|
+
|
|
420
|
+
// Retrieve the latest session state to ensure payment_status is up to date
|
|
421
|
+
const latestSession = await stripe.checkout.sessions.retrieve(session.id);
|
|
422
|
+
|
|
423
|
+
return await handleCheckoutCompleted(latestSession);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function handleAsyncPaymentFailed(session: Stripe.Checkout.Session) {
|
|
427
|
+
console.log(`Async payment failed: ${session.id}`);
|
|
428
|
+
|
|
429
|
+
const orderId = session.metadata?.order_id;
|
|
430
|
+
if (!orderId) {
|
|
431
|
+
throw new Error(`Transaction orderId is NULL for async payment failure`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const transaction = await transactionService.findByOrderId(orderId);
|
|
435
|
+
if (!transaction) {
|
|
436
|
+
throw new Error(`Transaction not found for async payment failure, orderId=${orderId}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (transaction.orderStatus === OrderStatus.SUCCESS) {
|
|
440
|
+
throw new Error( `Received async payment failed for already successful order ${orderId}, skipping.` );
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
await transactionService.updateStatus(orderId, OrderStatus.FAILED, {
|
|
444
|
+
payUpdatedAt: new Date(),
|
|
445
|
+
paymentStatus: PaymentStatus.UN_PAID,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
|
|
450
|
+
console.log(`Invoice payment-failed event: ${invoice.id}`);
|
|
451
|
+
|
|
452
|
+
const parentDetails = (invoice as any).parent?.subscription_details;
|
|
453
|
+
if (!parentDetails?.subscription) {
|
|
454
|
+
throw new Error('Invoice not associated with subscription, skipping');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const isInitialPayment = invoice.billing_reason === BillingReason.SUBSCRIPTION_CREATE;
|
|
458
|
+
const isRenewal = invoice.billing_reason === BillingReason.SUBSCRIPTION_CYCLE;
|
|
459
|
+
|
|
460
|
+
// Only handle initial payments and renewals
|
|
461
|
+
if (!isInitialPayment && !isRenewal) {
|
|
462
|
+
throw new Error(`Unhandled invoice billing_reason: ${invoice.billing_reason}, skipping`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const subscriptionId = parentDetails.subscription;
|
|
466
|
+
const subscriptionMetadata = parentDetails.metadata || {};
|
|
467
|
+
|
|
468
|
+
// 支付ID
|
|
469
|
+
const paymentIntentId = await fetchPaymentId(invoice.id)
|
|
470
|
+
|
|
471
|
+
console.log('Invoice payment failed event key-info:', {
|
|
472
|
+
invoiceId: invoice.id,
|
|
473
|
+
subscriptionId,
|
|
474
|
+
paymentIntentId,
|
|
475
|
+
billingReason: invoice.billing_reason,
|
|
476
|
+
isInitialPayment
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const paidEmail = invoice.customer_email;
|
|
480
|
+
|
|
481
|
+
const orderId = subscriptionMetadata.order_id;
|
|
482
|
+
if (!orderId) {
|
|
483
|
+
throw new Error( `No order_id in subscription metadata for failed initial invoice ${invoice.id}. ` + `Skipping payment failure update.` );
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const transaction = await transactionService.findByOrderId(orderId);
|
|
487
|
+
if (!transaction) {
|
|
488
|
+
throw new Error(`Transaction not found for order_id: ${orderId}`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ===== CASE 1: Initial subscription payment failed =====
|
|
492
|
+
if (isInitialPayment) {
|
|
493
|
+
await billingAggregateService.recordInitialPaymentFailure(
|
|
494
|
+
{
|
|
495
|
+
orderId: transaction.orderId,
|
|
496
|
+
invoiceId: invoice.id,
|
|
497
|
+
paymentIntentId: paymentIntentId,
|
|
498
|
+
detail: 'Initial subscription payment failed',
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
console.log(`Initial subscription payment-failed event updated for order: ${orderId}`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ===== CASE 2: Subscription renewal payment failed =====
|
|
506
|
+
if (isRenewal) {
|
|
507
|
+
// For renewals, we need the subscription to get user info
|
|
508
|
+
const subscription = await subscriptionService.findByPaySubscriptionId(subscriptionId);
|
|
509
|
+
if (!subscription) {
|
|
510
|
+
throw new Error(`Subscription not found for renewal payment-failed event, and invoice ${invoice.id}`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const failedOrderId = `order_renew_failed_${invoice.id}`;
|
|
514
|
+
const existingFailureOrder = await transactionService.findByOrderId(failedOrderId);
|
|
515
|
+
if (existingFailureOrder) {
|
|
516
|
+
throw new Error(`Renewal payment-failure event for invoice ${invoice.id} already recorded as ${failedOrderId}, skipping.`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
await billingAggregateService.recordRenewalPaymentFailure(
|
|
520
|
+
{
|
|
521
|
+
userId: subscription.userId,
|
|
522
|
+
subIdKey: subscription.id,
|
|
523
|
+
orderId: failedOrderId,
|
|
524
|
+
paySubscriptionId: subscriptionId,
|
|
525
|
+
creditsGranted: 0,
|
|
526
|
+
priceId: subscription.priceId,
|
|
527
|
+
priceName: subscription.priceName,
|
|
528
|
+
periodStart: null,
|
|
529
|
+
periodEnd: null,
|
|
530
|
+
invoiceId: invoice.id,
|
|
531
|
+
billingReason: invoice.billing_reason,
|
|
532
|
+
paymentIntentId,
|
|
533
|
+
amountPaidCents: invoice.amount_due,
|
|
534
|
+
currency: invoice.currency,
|
|
535
|
+
paidAt: null,
|
|
536
|
+
paidEmail
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
console.log(`Invoice renewal payment-failed event completed, and invoiceId: ${invoice.id}, recorded: ${subscription.id}, orderId: ${failedOrderId}`);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function handleSubscriptionCreated(stripeSubscription: Stripe.Subscription) {
|
|
547
|
+
console.log(`Subscription created: ${stripeSubscription.id}`);
|
|
548
|
+
// Usually handled by checkout.session.completed
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Handle subscription updated TODO
|
|
553
|
+
*/
|
|
554
|
+
async function handleSubscriptionUpdated(stripeSubscription: Stripe.Subscription) {
|
|
555
|
+
console.log(`Subscription updated: ${stripeSubscription.id}`);
|
|
556
|
+
const orderId = stripeSubscription.metadata?.order_id
|
|
557
|
+
if (!orderId) {
|
|
558
|
+
throw new Error('Missing order_id in session metadata');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Extract period timestamps from subscription items (NOT from top-level subscription object)
|
|
562
|
+
const subscriptionItem = stripeSubscription.items?.data?.[0];
|
|
563
|
+
|
|
564
|
+
if (!subscriptionItem) {
|
|
565
|
+
throw new Error(`No subscription items found for ${stripeSubscription.id}, reject!`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const subscription = await subscriptionService.findByPaySubscriptionId(stripeSubscription.id);
|
|
569
|
+
if (!subscription) {
|
|
570
|
+
throw new Error(`Subscription not found in DB: ${stripeSubscription.id}`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const isUserCancel = stripeSubscription.cancellation_details?.reason === 'cancellation_requested'
|
|
574
|
+
|
|
575
|
+
// Use period from subscription item if available
|
|
576
|
+
const currentPeriodStart = subscriptionItem.current_period_start;
|
|
577
|
+
const currentPeriodEnd = subscriptionItem.current_period_end;
|
|
578
|
+
|
|
579
|
+
await billingAggregateService.syncSubscriptionFromStripe(
|
|
580
|
+
{
|
|
581
|
+
subscription,
|
|
582
|
+
status: stripeSubscription.status,
|
|
583
|
+
periodStart: new Date(currentPeriodStart * 1000),
|
|
584
|
+
periodEnd: new Date(currentPeriodEnd * 1000),
|
|
585
|
+
orderId,
|
|
586
|
+
isUserCancel
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
console.log(`Subscription updated in DB: ${subscription.id}`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
async function handleChargeRefunded(charge: Stripe.Charge) {
|
|
595
|
+
console.log(`Charge refunded: ${charge.id}`);
|
|
596
|
+
|
|
597
|
+
// Find transaction by payment intent
|
|
598
|
+
const paymentIntentId = typeof charge.payment_intent === 'string'
|
|
599
|
+
? charge.payment_intent
|
|
600
|
+
: charge.payment_intent?.id;
|
|
601
|
+
|
|
602
|
+
if (!paymentIntentId) {
|
|
603
|
+
throw new Error("PaymentId is illegal NULL");
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const transaction = await transactionService.findByPayTransactionId(paymentIntentId);
|
|
607
|
+
if (!transaction) {
|
|
608
|
+
throw new Error(`Transaction not found for paymentId: ${paymentIntentId}`);
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
if (transaction.orderStatus === OrderStatus.REFUNDED) {
|
|
612
|
+
throw new Error(`Transaction already marked refunded: ${transaction.orderId}, skipping.`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (transaction.type === TransactionType.SUBSCRIPTION) {
|
|
616
|
+
const subscription = transaction.paySubscriptionId
|
|
617
|
+
? await subscriptionService.findByPaySubscriptionId(transaction.paySubscriptionId)
|
|
618
|
+
: null;
|
|
619
|
+
|
|
620
|
+
await billingAggregateService.processSubscriptionRefund(
|
|
621
|
+
{
|
|
622
|
+
transaction,
|
|
623
|
+
subscription,
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
console.log(`Subscription refund processed for transaction: ${transaction.orderId}`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (transaction.type === TransactionType.ONE_TIME) {
|
|
632
|
+
await billingAggregateService.processOneTimeRefund({ transaction });
|
|
633
|
+
|
|
634
|
+
console.log(`One-time refund processed for transaction: ${transaction.orderId}`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
// for other type, not available
|
|
638
|
+
await transactionService.update(
|
|
639
|
+
transaction.orderId,
|
|
640
|
+
{
|
|
641
|
+
orderStatus: OrderStatus.REFUNDED,
|
|
642
|
+
paymentStatus: PaymentStatus.UN_PAID,
|
|
643
|
+
payUpdatedAt: new Date(),
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
console.log(`Refund processed for transaction without credit adjustments: ${transaction.orderId}`);
|
|
648
|
+
}
|