Skip to content

Commit 95a4c68

Browse files
committed
feat: handle refunds for cloud credits
1 parent 717d570 commit 95a4c68

File tree

17 files changed

+756
-36
lines changed

17 files changed

+756
-36
lines changed

bifrost/lib/clients/jawnTypes/private.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,25 @@ export interface components {
706706
completion_token: number;
707707
};
708708
};
709+
PaymentIntentRecord: {
710+
id: string;
711+
/** Format: double */
712+
amount: number;
713+
/** Format: double */
714+
created: number;
715+
status: string;
716+
isRefunded?: boolean;
717+
/** Format: double */
718+
refundedAmount?: number;
719+
refundIds?: string[];
720+
};
721+
StripePaymentIntentsResponse: {
722+
data: components["schemas"]["PaymentIntentRecord"][];
723+
has_more: boolean;
724+
next_page: string | null;
725+
/** Format: double */
726+
count: number;
727+
};
709728
Json: JsonObject;
710729
"ResultSuccess__40_Database-at-public_91_Tables_93_-at-organization_91_Row_93_-and-_role-string__41_-Array_": {
711730
data: (({
@@ -16348,7 +16367,7 @@ export interface operations {
1634816367
/** @description Ok */
1634916368
200: {
1635016369
content: {
16351-
"application/json": unknown;
16370+
"application/json": components["schemas"]["StripePaymentIntentsResponse"];
1635216371
};
1635316372
};
1635416373
};

bifrost/lib/clients/jawnTypes/public.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,25 @@ Json: JsonObject;
21852185
completion_token: number;
21862186
};
21872187
};
2188+
PaymentIntentRecord: {
2189+
id: string;
2190+
/** Format: double */
2191+
amount: number;
2192+
/** Format: double */
2193+
created: number;
2194+
status: string;
2195+
isRefunded?: boolean;
2196+
/** Format: double */
2197+
refundedAmount?: number;
2198+
refundIds?: string[];
2199+
};
2200+
StripePaymentIntentsResponse: {
2201+
data: components["schemas"]["PaymentIntentRecord"][];
2202+
has_more: boolean;
2203+
next_page: string | null;
2204+
/** Format: double */
2205+
count: number;
2206+
};
21882207
ValidationError: {
21892208
field: string;
21902209
message: string;
@@ -5878,7 +5897,7 @@ export interface operations {
58785897
/** @description Ok */
58795898
200: {
58805899
content: {
5881-
"application/json": unknown;
5900+
"application/json": components["schemas"]["StripePaymentIntentsResponse"];
58825901
};
58835902
};
58845903
};

docs/swagger.json

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5809,6 +5809,74 @@
58095809
"type": "object",
58105810
"additionalProperties": false
58115811
},
5812+
"PaymentIntentRecord": {
5813+
"properties": {
5814+
"id": {
5815+
"type": "string"
5816+
},
5817+
"amount": {
5818+
"type": "number",
5819+
"format": "double"
5820+
},
5821+
"created": {
5822+
"type": "number",
5823+
"format": "double"
5824+
},
5825+
"status": {
5826+
"type": "string"
5827+
},
5828+
"isRefunded": {
5829+
"type": "boolean"
5830+
},
5831+
"refundedAmount": {
5832+
"type": "number",
5833+
"format": "double"
5834+
},
5835+
"refundIds": {
5836+
"items": {
5837+
"type": "string"
5838+
},
5839+
"type": "array"
5840+
}
5841+
},
5842+
"required": [
5843+
"id",
5844+
"amount",
5845+
"created",
5846+
"status"
5847+
],
5848+
"type": "object",
5849+
"additionalProperties": false
5850+
},
5851+
"StripePaymentIntentsResponse": {
5852+
"properties": {
5853+
"data": {
5854+
"items": {
5855+
"$ref": "#/components/schemas/PaymentIntentRecord"
5856+
},
5857+
"type": "array"
5858+
},
5859+
"has_more": {
5860+
"type": "boolean"
5861+
},
5862+
"next_page": {
5863+
"type": "string",
5864+
"nullable": true
5865+
},
5866+
"count": {
5867+
"type": "number",
5868+
"format": "double"
5869+
}
5870+
},
5871+
"required": [
5872+
"data",
5873+
"has_more",
5874+
"next_page",
5875+
"count"
5876+
],
5877+
"type": "object",
5878+
"additionalProperties": false
5879+
},
58125880
"ValidationError": {
58135881
"properties": {
58145882
"field": {
@@ -16990,7 +17058,9 @@
1699017058
"description": "Ok",
1699117059
"content": {
1699217060
"application/json": {
16993-
"schema": {}
17061+
"schema": {
17062+
"$ref": "#/components/schemas/StripePaymentIntentsResponse"
17063+
}
1699417064
}
1699517065
}
1699617066
}

valhalla/jawn/src/controllers/public/stripeController.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,18 @@ export interface SearchPaymentIntentsRequest {
4747
page?: string;
4848
}
4949

50+
export interface PaymentIntentRecord {
51+
id: string; // Always the payment intent ID
52+
amount: number;
53+
created: number;
54+
status: string;
55+
isRefunded?: boolean;
56+
refundedAmount?: number;
57+
refundIds?: string[];
58+
}
59+
5060
export interface StripePaymentIntentsResponse {
51-
data: Stripe.PaymentIntent[];
61+
data: PaymentIntentRecord[];
5262
has_more: boolean;
5363
next_page: string | null;
5464
count: number;
@@ -387,7 +397,7 @@ export class StripeController extends Controller {
387397
@Query() search_kind: string,
388398
@Query() limit?: number,
389399
@Query() page?: string
390-
): Promise<any> {
400+
): Promise<StripePaymentIntentsResponse> {
391401
// Check if search_kind is valid
392402
if (!Object.values(PaymentIntentSearchKind).includes(search_kind as PaymentIntentSearchKind)) {
393403
this.setStatus(400);

valhalla/jawn/src/managers/inputs/InputsManager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ export class InputsManager extends BaseManager {
291291
`,
292292
[this.authParams.organizationId, promptVersion, datasetId]
293293
);
294-
console.log("result", result);
295294
const bodyStore = new RequestResponseBodyStore(
296295
this.authParams.organizationId
297296
);

valhalla/jawn/src/managers/stripe/StripeManager.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
UpgradeToTeamBundleRequest,
66
StripePaymentIntentsResponse,
77
PaymentIntentSearchKind,
8+
PaymentIntentRecord,
89
} from "../../controllers/public/stripeController";
910
import { clickhouseDb } from "../../lib/db/ClickhouseWrapper";
1011
import { Database } from "../../lib/db/database.types";
@@ -1389,11 +1390,81 @@ WHERE (${builtFilter.filter})`,
13891390

13901391
const paymentIntents = await this.stripe.paymentIntents.search(searchParams);
13911392

1393+
// Map Stripe PaymentIntent to our custom PaymentIntentRecord type
1394+
const mappedData: PaymentIntentRecord[] = [];
1395+
1396+
// Process each payment intent and fetch its refunds
1397+
for (const intent of paymentIntents.data) {
1398+
let totalRefunded = 0;
1399+
let isFullyRefunded = false;
1400+
let latestRefundDate = intent.created;
1401+
let refundIds: string[] = [];
1402+
1403+
// Fetch refunds for this payment intent
1404+
try {
1405+
const refunds = await this.stripe.refunds.list({
1406+
payment_intent: intent.id,
1407+
limit: 100, // Get all refunds for this payment intent
1408+
});
1409+
1410+
if (refunds.data.length > 0) {
1411+
totalRefunded = refunds.data.reduce((sum, refund) => sum + refund.amount, 0);
1412+
isFullyRefunded = totalRefunded >= intent.amount;
1413+
refundIds = refunds.data.map(refund => refund.id);
1414+
1415+
// Use the latest refund date for sorting if fully refunded
1416+
if (isFullyRefunded) {
1417+
latestRefundDate = Math.max(...refunds.data.map(r => r.created), intent.created);
1418+
}
1419+
}
1420+
} catch (refundError) {
1421+
console.error(`Error fetching refunds for payment intent ${intent.id}:`, refundError);
1422+
// Continue processing other payment intents even if one fails
1423+
}
1424+
1425+
// Add consolidated record
1426+
if (isFullyRefunded) {
1427+
// Show as fully refunded transaction
1428+
mappedData.push({
1429+
id: intent.id, // Always use payment intent ID
1430+
amount: intent.amount,
1431+
created: latestRefundDate,
1432+
status: "refunded",
1433+
isRefunded: true,
1434+
refundedAmount: totalRefunded,
1435+
refundIds: refundIds,
1436+
});
1437+
} else if (totalRefunded > 0) {
1438+
// Show as partially refunded transaction
1439+
mappedData.push({
1440+
id: intent.id, // Always use payment intent ID
1441+
amount: intent.amount,
1442+
created: intent.created,
1443+
status: intent.status,
1444+
isRefunded: true,
1445+
refundedAmount: totalRefunded,
1446+
refundIds: refundIds,
1447+
});
1448+
} else {
1449+
// Show as normal transaction
1450+
mappedData.push({
1451+
id: intent.id, // Always use payment intent ID
1452+
amount: intent.amount,
1453+
created: intent.created,
1454+
status: intent.status,
1455+
isRefunded: false,
1456+
});
1457+
}
1458+
}
1459+
1460+
// Sort all records by created date (newest first)
1461+
mappedData.sort((a, b) => b.created - a.created);
1462+
13921463
return ok({
1393-
data: paymentIntents.data,
1464+
data: mappedData,
13941465
has_more: paymentIntents.has_more,
13951466
next_page: paymentIntents.next_page || null,
1396-
count: paymentIntents.data.length,
1467+
count: mappedData.length,
13971468
});
13981469
} catch (error: any) {
13991470
console.error("Error searching payment intents:", error);

valhalla/jawn/src/tsoa-build/private/routes.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,31 @@ const models: TsoaRoute.Models = {
298298
"additionalProperties": false,
299299
},
300300
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
301+
"PaymentIntentRecord": {
302+
"dataType": "refObject",
303+
"properties": {
304+
"id": {"dataType":"string","required":true},
305+
"amount": {"dataType":"double","required":true},
306+
"created": {"dataType":"double","required":true},
307+
"status": {"dataType":"string","required":true},
308+
"isRefunded": {"dataType":"boolean"},
309+
"refundedAmount": {"dataType":"double"},
310+
"refundIds": {"dataType":"array","array":{"dataType":"string"}},
311+
},
312+
"additionalProperties": false,
313+
},
314+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
315+
"StripePaymentIntentsResponse": {
316+
"dataType": "refObject",
317+
"properties": {
318+
"data": {"dataType":"array","array":{"dataType":"refObject","ref":"PaymentIntentRecord"},"required":true},
319+
"has_more": {"dataType":"boolean","required":true},
320+
"next_page": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
321+
"count": {"dataType":"double","required":true},
322+
},
323+
"additionalProperties": false,
324+
},
325+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
301326
"Json": {
302327
"dataType": "refAlias",
303328
"type": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"double"},{"dataType":"boolean"},{"dataType":"enum","enums":[null]},{"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"union","subSchemas":[{"ref":"Json"},{"dataType":"undefined"}]}},{"dataType":"array","array":{"dataType":"refAlias","ref":"Json"}}],"validators":{}},

0 commit comments

Comments
 (0)