Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 13x 13x 13x 23x 1x 2x 2x 1x 1x 2x 1x 1x 1x 3x 3x 3x 1x 1x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 3x 2x 2x 1x 1x | import { Injectable } from '@nestjs/common';
import { PrismaService } from '@app/modules/prisma/prisma.service';
@Injectable()
export class BillingService {
constructor(private readonly prisma: PrismaService) {}
listPlans() {
return this.prisma.client.plan.findMany({ orderBy: [{ scope: 'asc' }, { period: 'asc' }] as any });
}
async subscribe(userId: number, dto: { planCode: string; period?: string; paymentMethodId?: string }) {
const plan = await this.prisma.client.plan.findUnique({ where: { code: dto.planCode } });
if (!plan) throw new Error('Unknown plan');
// Placeholder: create subscription in provider (Stripe later). For now, mark active until +30d.
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
return this.prisma.client.subscription.create({
data: { userId, planId: plan.id, status: 'ACTIVE', currentPeriodEnd, provider: 'stripe' },
});
}
listInvoices(userId: number) {
// Future: real invoices table; for now, map payments
return this.prisma.client.payment.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } });
}
async getInvoice(id: number, userId: number) {
// @ts-ignore
const p = await this.prisma.client.payment.findUnique({ where: { id } });
Iif (!p || p.userId !== userId) throw new Error('Invoice not found');
return p;
}
handleStripeWebhook(_req: any, _sig: string) {
// Placeholder: verify signature and update subscriptions/payments
return { received: true };
}
async createPaymentIntent(userId: number, body: { amountCents: number; currency?: string; purpose: string; subjectRef?: string }) {
const merchantTransId = `p_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
// @ts-ignore
const payment = await this.prisma.client.payment.create({
data: {
userId,
amountCents: body.amountCents,
currency: body.currency || 'UZS',
purpose: body.purpose,
status: 'NEW',
provider: 'click',
merchantTransId,
meta: body.subjectRef ? ({ subjectRef: body.subjectRef } as any) : undefined,
},
});
return { merchantTransId: payment.merchantTransId, amountCents: payment.amountCents, purpose: payment.purpose };
}
getPaymentByMerchantTransId(tid: string) {
// @ts-ignore
return this.prisma.client.payment.findUnique({ where: { merchantTransId: tid } });
}
async requestRefund(merchantTransId: string, userId: number) {
// Simplified: mark as REFUND_REQUESTED if owned and confirmed
// @ts-ignore
const payment = await this.prisma.client.payment.findUnique({ where: { merchantTransId } });
Iif (!payment || payment.userId !== userId) throw new Error('Payment not found');
Iif (payment.status !== 'CONFIRMED') throw new Error('Only confirmed payments can be refunded');
// @ts-ignore
await this.prisma.client.payment.update({ where: { id: payment.id }, data: { status: 'REFUND_REQUESTED' } });
return { ok: true };
}
// ----------------------------
// Seat subscriptions (AGENT_SEAT)
// ----------------------------
async createSeatSubscription(agencyId: number, seatCount: number) {
if (!agencyId || !Number.isInteger(seatCount) || seatCount <= 0) {
throw new Error('Invalid parameters');
}
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
// @ts-ignore
const sub = await this.prisma.client.subscription.create({
data: {
agencyId,
type: 'AGENT_SEAT',
quantity: seatCount,
status: 'PAST_DUE',
currentPeriodEnd,
provider: 'click',
},
});
// Initiate payment intent: 50_000 UZS per seat (amountCents is in UZS cents or integer minor unit)
const amountCents = 50000 * seatCount;
const intent = await this.createPaymentIntent(0, {
amountCents,
currency: 'UZS',
purpose: 'AGENT_SEAT',
subjectRef: JSON.stringify({ agencyId, subscriptionId: sub.id }),
});
return { subscriptionId: sub.id, merchantTransId: intent.merchantTransId, amountCents: intent.amountCents };
}
// ----------------------------
// Pay-to-publish one-time fee
// ----------------------------
async createPayToPublishIntent(userId: number, listingId: number) {
if (!userId || !listingId) throw new Error('Invalid parameters');
const listing = await (this.prisma.client as any).listing.findUnique({ where: { id: listingId } });
if (!listing) throw new Error('Listing not found');
const amountCents = 25000;
return this.createPaymentIntent(userId, {
amountCents,
currency: 'UZS',
purpose: 'LISTING_FEE',
subjectRef: JSON.stringify({ listingId }),
});
}
}
|