fix(payments): curate /payments/me response — stop leaking internal columns, join plan name
getMyPayments returned the raw payment row, exposing internal fields (adminNote, iyzicoPaymentId, bankAccountId, session/intent ids) to the end user. Replace with an explicit projection that returns only what the billing UI needs, joins planName from the subscription's plan (was always "-"), and surfaces Stripe receipt availability as a hasStripeReceipt boolean instead of the raw payment intent id. Frontend reads the boolean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { DATABASE, type Database } from "../database/database.provider";
|
||||
import { payments } from "../database/schema/core";
|
||||
import { payments, plans, userSubscriptions } from "../database/schema/core";
|
||||
import { StripeService } from "./stripe/stripe.service";
|
||||
|
||||
@Injectable()
|
||||
@@ -13,12 +13,42 @@ export class PaymentsService {
|
||||
private stripeService: StripeService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Payment history for the billing page. Returns a curated projection — never
|
||||
* the raw row — so internal columns (adminNote, iyzicoPaymentId,
|
||||
* bankAccountId, session/intent ids) are not leaked to the client. planName
|
||||
* is joined from the subscription's plan; Stripe receipt availability is
|
||||
* surfaced as a boolean rather than exposing the payment intent id.
|
||||
*/
|
||||
async getMyPayments(userId: string) {
|
||||
return this.db
|
||||
.select()
|
||||
const rows = await this.db
|
||||
.select({
|
||||
id: payments.id,
|
||||
amount: payments.amount,
|
||||
method: payments.method,
|
||||
status: payments.status,
|
||||
createdAt: payments.createdAt,
|
||||
planName: plans.name,
|
||||
eftReceiptUrl: payments.eftReceiptUrl,
|
||||
stripePaymentIntentId: payments.stripePaymentIntentId,
|
||||
})
|
||||
.from(payments)
|
||||
.leftJoin(userSubscriptions, eq(payments.subscriptionId, userSubscriptions.id))
|
||||
.leftJoin(plans, eq(userSubscriptions.planId, plans.id))
|
||||
.where(eq(payments.userId, userId))
|
||||
.orderBy(desc(payments.createdAt));
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
amount: row.amount,
|
||||
method: row.method,
|
||||
status: row.status,
|
||||
createdAt: row.createdAt,
|
||||
planName: row.planName ?? null,
|
||||
eftReceiptUrl: row.eftReceiptUrl ?? null,
|
||||
hasStripeReceipt:
|
||||
row.method === "stripe" && row.status === "completed" && !!row.stripePaymentIntentId,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user