From f21395779553f7a4c3413a0bf9f133a02a46a203 Mon Sep 17 00:00:00 2001 From: Igor Zynov Date: Tue, 29 Jul 2025 01:50:05 +0400 Subject: [PATCH 1/3] feat(payment-stripe): add PWYW minimum price validation and metadata support --- .../1753736525000-add-metadata-to-price.ts | 19 ++++++++++++ .../payment-stripe/src/entities/price.ts | 6 ++++ .../src/methods/stripe/create-checkout.ts | 3 +- .../src/services/payment-gateway/abstract.ts | 3 +- .../src/services/payment-gateway/stripe.ts | 30 ++++++++++++++++--- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 microservices/payment-stripe/migrations/1753736525000-add-metadata-to-price.ts diff --git a/microservices/payment-stripe/migrations/1753736525000-add-metadata-to-price.ts b/microservices/payment-stripe/migrations/1753736525000-add-metadata-to-price.ts new file mode 100644 index 00000000..0566f940 --- /dev/null +++ b/microservices/payment-stripe/migrations/1753736525000-add-metadata-to-price.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export default class AddMetadataToPrice1753736525000 implements MigrationInterface { + name = 'AddMetadataToPrice1753736525000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "price" + ADD COLUMN "metadata" jsonb DEFAULT null + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "price" + DROP COLUMN "metadata" + `); + } +} diff --git a/microservices/payment-stripe/src/entities/price.ts b/microservices/payment-stripe/src/entities/price.ts index 758ba38a..abdeb199 100644 --- a/microservices/payment-stripe/src/entities/price.ts +++ b/microservices/payment-stripe/src/entities/price.ts @@ -41,6 +41,12 @@ class Price { @IsNumber() unitAmount: number; + @JSONSchema({ + description: 'Metadata for storing custom data like PWYW information', + }) + @Column({ type: 'jsonb', default: null }) + metadata: Record | null; + @IsTypeormDate() @CreateDateColumn() createdAt: Date; diff --git a/microservices/payment-stripe/src/methods/stripe/create-checkout.ts b/microservices/payment-stripe/src/methods/stripe/create-checkout.ts index 9f9ceac2..a4dce7f7 100644 --- a/microservices/payment-stripe/src/methods/stripe/create-checkout.ts +++ b/microservices/payment-stripe/src/methods/stripe/create-checkout.ts @@ -1,5 +1,5 @@ import { Endpoint, IsNullable, IsUndefinable } from '@lomray/microservice-helpers'; -import { IsBoolean, IsNumber, IsString, Length } from 'class-validator'; +import { IsBoolean, IsNumber, IsString, Length, Min } from 'class-validator'; import Stripe from '@services/payment-gateway/stripe'; class CreateCheckoutInput { @@ -21,6 +21,7 @@ class CreateCheckoutInput { isAllowPromoCode?: boolean; @IsNumber() + @Min(0) @IsUndefinable() customAmount?: number; } diff --git a/microservices/payment-stripe/src/services/payment-gateway/abstract.ts b/microservices/payment-stripe/src/services/payment-gateway/abstract.ts index 7014f46b..ca1090af 100644 --- a/microservices/payment-stripe/src/services/payment-gateway/abstract.ts +++ b/microservices/payment-stripe/src/services/payment-gateway/abstract.ts @@ -245,7 +245,7 @@ abstract class Abstract { * Create new price */ public async createPrice(params: IPriceParams, priceId: string = uuid()): Promise { - const { productId, currency, unitAmount, userId } = params; + const { productId, currency, unitAmount, userId, metadata } = params; const price = this.priceRepository.create({ priceId, @@ -253,6 +253,7 @@ abstract class Abstract { userId, currency, unitAmount, + ...(metadata ? { metadata } : {}), }); await this.priceRepository.save(price); diff --git a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts index 39978fc2..2b3426c1 100644 --- a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts +++ b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts @@ -389,11 +389,28 @@ class Stripe extends Abstract { productId, currency, unitAmount, + metadata, }, id, ); } + /** + * Validate PWYW custom amount against minimum price + */ + private validatePWYWAmount(customAmount: number, price: Price): void { + if (price.metadata?.isPWYW === 'true') { + const minimumPrice = price.metadata?.minimumPrice ? Number(price.metadata.minimumPrice) : 0; + + if (customAmount < minimumPrice) { + Log.error( + `Custom amount ${customAmount} is below minimum price ${minimumPrice} for PWYW show`, + ); + throw new Error(`Amount must be at least $${(minimumPrice / 100).toFixed(2)}`); + } + } + } + /** * Create checkout session and return url to redirect user for payment */ @@ -409,6 +426,11 @@ class Stripe extends Abstract { return null; } + // Validate PWYW minimum price if custom amount is provided + if (customAmount) { + this.validatePWYWAmount(customAmount, price); + } + /* eslint-disable camelcase */ const sessionParams: StripeSdk.Checkout.SessionCreateParams = { mode: 'payment', @@ -423,9 +445,9 @@ class Stripe extends Abstract { sessionParams.line_items = [ { price_data: { - currency: 'usd', + currency: price.currency, product: price.productId, - unit_amount: customAmount * 100, // Convert to cents + unit_amount: customAmount, }, quantity: 1, }, @@ -446,12 +468,12 @@ class Stripe extends Abstract { await this.createTransaction( { type: TransactionType.CREDIT, - amount: customAmount ? customAmount * 100 : price.unitAmount, // Store in cents + amount: customAmount ? customAmount : price.unitAmount, userId, productId: price.productId, entityId: price.product.entityId, status: TransactionStatus.INITIAL, - customAmount: customAmount ? customAmount * 100 : undefined, // Store custom amount in cents + customAmount: customAmount ? customAmount : undefined, // Store custom amount }, id, ); From f1b2c3dfea961c0abcabf0c3e2d3761fe7285798 Mon Sep 17 00:00:00 2001 From: Igor Zynov Date: Tue, 29 Jul 2025 01:57:55 +0400 Subject: [PATCH 2/3] feat(payment-stripe): add PWYW minimum price validation and metadata support --- .../payment-stripe/src/services/payment-gateway/stripe.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts index 2b3426c1..78a89062 100644 --- a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts +++ b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts @@ -406,7 +406,10 @@ class Stripe extends Abstract { Log.error( `Custom amount ${customAmount} is below minimum price ${minimumPrice} for PWYW show`, ); - throw new Error(`Amount must be at least $${(minimumPrice / 100).toFixed(2)}`); + throw new BaseException({ + status: 400, + message: `Amount must be at least $${(minimumPrice / 100).toFixed(2)}`, + }); } } } From 84887865675e3e58cc3d0a4c83fa4fe0064e2ba7 Mon Sep 17 00:00:00 2001 From: Igor Zynov Date: Tue, 29 Jul 2025 02:28:34 +0400 Subject: [PATCH 3/3] feat(payment-stripe): add metadata permissions for user access --- .../permissions/list/models/payment-stripe.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/microservices/authorization/migrations/permissions/list/models/payment-stripe.json b/microservices/authorization/migrations/permissions/list/models/payment-stripe.json index 3b579586..50bbb66b 100644 --- a/microservices/authorization/migrations/permissions/list/models/payment-stripe.json +++ b/microservices/authorization/migrations/permissions/list/models/payment-stripe.json @@ -1829,6 +1829,14 @@ "out": { "user": "allow" } + }, + "metadata": { + "in": { + "user": "allow" + }, + "out": { + "user": "allow" + } } }, "createdAt": "2023-05-26T13:01:39.186Z"