Fix plugin purchase 500 when subscription catalog columns are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-10 10:33:48 +03:30
co-authored by Cursor
parent 6cc10e3333
commit cf92635c0e
2 changed files with 66 additions and 31 deletions
@@ -0,0 +1,13 @@
-- Catalog linkage on plugin_subscriptions was in schema.prisma without a
-- CREATE COLUMN migration. Remote store GET swallows the missing column;
-- POST create did not, so purchase requests returned HTTP 500.
ALTER TABLE "plugin_subscriptions"
ADD COLUMN IF NOT EXISTS "setting_key" VARCHAR(50);
ALTER TABLE "plugin_subscriptions"
ADD COLUMN IF NOT EXISTS "auto_cut_on_expiry" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "plugin_subscriptions"
ADD COLUMN IF NOT EXISTS "alert_marks" JSONB;
CREATE INDEX IF NOT EXISTS "plugin_subscriptions_setting_key_idx"
ON "plugin_subscriptions"("setting_key");
+53 -31
View File
@@ -3,6 +3,7 @@ import {
ConflictException,
ForbiddenException,
Injectable,
InternalServerErrorException,
Logger,
NotFoundException,
OnModuleDestroy,
@@ -722,10 +723,16 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
throw new ConflictException('درخواست در انتظار برای این افزونه وجود دارد');
}
const sub = await this.prisma.pluginSubscription.findFirst({
where: { tenantId, settingKey: item.settingKey, isActive: true },
orderBy: { expiresAt: 'desc' },
});
let sub: { isActive: boolean; expiresAt: Date | null } | null = null;
try {
sub = await this.prisma.pluginSubscription.findFirst({
where: { tenantId, settingKey: item.settingKey, isActive: true },
orderBy: { expiresAt: 'desc' },
select: { isActive: true, expiresAt: true },
});
} catch (error: any) {
this.logger.warn(`remoteCreateRequest sub skipped: ${error?.message || error}`);
}
if (sub?.isActive && !sub.expiresAt) {
throw new ConflictException('این افزونه یکبار پرداخت شده و فعال است');
}
@@ -736,35 +743,50 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
}
}
const created = await this.prisma.pluginPurchaseRequest.create({
data: {
tenantId,
catalogItemId: item.id,
durationDays: dto.durationDays,
originalPriceRials: price.originalPriceRials,
finalPriceRials: price.finalPriceRials,
status: 'pending',
},
include: {
catalogItem: { select: { titleFa: true, settingKey: true } },
tenant: { select: { nameFa: true, domain: true } },
},
});
try {
const created = await this.prisma.pluginPurchaseRequest.create({
data: {
tenantId,
catalogItemId: item.id,
durationDays: dto.durationDays,
originalPriceRials: price.originalPriceRials,
finalPriceRials: price.finalPriceRials,
status: 'pending',
},
include: {
catalogItem: { select: { titleFa: true, settingKey: true } },
tenant: { select: { nameFa: true, domain: true } },
},
});
this.notify.notifyAdmins(
`درخواست افزونه: «${created.catalogItem.titleFa}» ${pluginStoreDurationLabel(dto.durationDays)} برای «${created.tenant.nameFa}» (${created.tenant.domain}) — ${price.finalPriceRials.toLocaleString('fa-IR')} ریال`,
);
this.notify.notifyAdmins(
`درخواست افزونه: «${created.catalogItem.titleFa}» ${pluginStoreDurationLabel(dto.durationDays)} برای «${created.tenant.nameFa}» (${created.tenant.domain}) — ${price.finalPriceRials.toLocaleString('fa-IR')} ریال`,
);
return this.toRequestRow({
...created,
tenant: {
id: tenantId,
nameFa: created.tenant.nameFa,
domain: created.tenant.domain,
slug: '',
product: ProductType.khanegi,
},
} as any);
return this.toRequestRow({
...created,
tenant: {
id: tenantId,
nameFa: created.tenant.nameFa,
domain: created.tenant.domain,
slug: '',
product: ProductType.khanegi,
},
} as any);
} catch (error: any) {
if (
error instanceof BadRequestException ||
error instanceof ConflictException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
this.logger.error(`remoteCreateRequest failed: ${error?.message || error}`);
throw new InternalServerErrorException(
error?.message || 'ثبت درخواست افزونه ناموفق بود',
);
}
}
async remoteCancelRequest(tenantId: string, product: string, requestId: string) {