From 2483c02a01c94d9bdda15c54d4ba1679c08fe3f8 Mon Sep 17 00:00:00 2001 From: "name.ZuLu0890" Date: Sun, 26 Apr 2026 13:31:26 +0100 Subject: [PATCH 1/3] Integrate reward points redemption into payment checkout flow - Add RewardsModule to PaymentsModule for dependency injection - Inject RewardsService into PaymentsService to enable points redemption - Add pointsToUse field to InitiatePaymentDto to allow customers to use points during checkout - Implement logic in initiatePayment to apply reward points discount before payment - Points are converted to discount (100 points = ) and deducted from final payment amount - Gracefully handle points redemption failures without blocking payment This completes the integration of the Tiered Loyalty & Rewards Program Engine with the checkout flow. --- src/payments/dto/payment.dto.ts | 1 + src/payments/payments.module.ts | 2 ++ src/payments/payments.service.ts | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/payments/dto/payment.dto.ts b/src/payments/dto/payment.dto.ts index f3d4b5d..7a31c2a 100644 --- a/src/payments/dto/payment.dto.ts +++ b/src/payments/dto/payment.dto.ts @@ -25,6 +25,7 @@ export class InitiatePaymentDto { readonly orderId: string; readonly currency: PaymentCurrency; readonly timeoutMinutes?: number; + readonly pointsToUse?: number; } export class PaymentWebhookDto { diff --git a/src/payments/payments.module.ts b/src/payments/payments.module.ts index 3e4524e..f64cf72 100644 --- a/src/payments/payments.module.ts +++ b/src/payments/payments.module.ts @@ -12,6 +12,7 @@ import { Wallet } from '../wallet/entities/wallet.entity'; import { OrdersModule } from '../orders/orders.module'; import { WalletModule } from '../wallet/wallet.module'; import { WebhooksModule } from '../webhooks/webhooks.module'; +import { RewardsModule } from '../rewards/rewards.module'; @Module({ imports: [ @@ -21,6 +22,7 @@ import { WebhooksModule } from '../webhooks/webhooks.module'; OrdersModule, WalletModule, WebhooksModule, + RewardsModule, ], controllers: [PaymentsController], providers: [PaymentsService, PaymentMonitorService], diff --git a/src/payments/payments.service.ts b/src/payments/payments.service.ts index 75c6392..4e891f3 100644 --- a/src/payments/payments.service.ts +++ b/src/payments/payments.service.ts @@ -29,6 +29,7 @@ import { PaymentTimeoutEvent, EventNames, } from '../common/events'; +import { RewardsService } from '../rewards/rewards.service'; @Injectable() export class PaymentsService { @@ -45,6 +46,7 @@ export class PaymentsService { private walletsRepository: Repository, private configService: ConfigService, private eventEmitter: EventEmitter2, + private readonly rewardsService: RewardsService, ) { // Initialize Stellar SDK const horizonUrl = this.configService.get( @@ -107,6 +109,32 @@ export class PaymentsService { return this.mapToResponseDto(existingPayment); } + // Apply reward points if provided + let finalAmount = parseFloat(order.totalAmount.toString()); + let pointsUsed = 0; + let pointsDiscount = 0; + + if (initiatePaymentDto.pointsToUse && initiatePaymentDto.pointsToUse > 0) { + try { + const pointsResult = await this.rewardsService.applyPointsToCheckout( + order.buyerId, + initiatePaymentDto.pointsToUse, + finalAmount, + ); + pointsUsed = pointsResult.pointsUsed; + pointsDiscount = pointsResult.discountAmount; + finalAmount = finalAmount - pointsDiscount; + this.logger.log( + `Applied ${pointsUsed} reward points for order ${initiatePaymentDto.orderId}, discount: $${pointsDiscount}`, + ); + } catch (error) { + this.logger.warn( + `Failed to apply reward points: ${error.message}. Proceeding without points.`, + ); + // Continue without points if redemption fails + } + } + // Get buyer's wallet to get payment address const wallet = await this.walletsRepository.findOne({ where: { userId: order.buyerId }, @@ -123,7 +151,7 @@ export class PaymentsService { const payment = this.paymentsRepository.create({ orderId: initiatePaymentDto.orderId, - amount: parseFloat(order.totalAmount.toString()), + amount: finalAmount, currency: initiatePaymentDto.currency, status: PaymentStatus.PENDING, destinationWalletAddress: wallet.publicKey, @@ -135,7 +163,7 @@ export class PaymentsService { const savedPayment = await this.paymentsRepository.save(payment); this.logger.log( - `Payment initiated for order ${initiatePaymentDto.orderId}: ${savedPayment.id}`, + `Payment initiated for order ${initiatePaymentDto.orderId}: ${savedPayment.id}. Original: $${order.totalAmount}, After points: $${finalAmount}`, ); // Emit event for payment monitoring From aa36768374d870b22b526c54941bf3c7ffbadf83 Mon Sep 17 00:00:00 2001 From: "name.ZuLu0890" Date: Sun, 26 Apr 2026 14:00:14 +0100 Subject: [PATCH 2/3] Fix deprecated gitleaks-action in security workflow - Update from deprecated zricethezav/gitleaks-action@v8 to gitleaks/gitleaks-action@v2 - Resolves CI/CD error: Unable to resolve action zricethezav/gitleaks-action@v8 --- .github/workflows/security.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 9bd5fa5..d4433f8 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 - name: Run secret scan with Gitleaks - uses: zricethezav/gitleaks-action@v8 + uses: gitleaks/gitleaks-action@v2 with: version: 'v8.18.0' args: '--path=./ --verbose' From 372974da396f7bd55879e210b91cee4d3d6cd0aa Mon Sep 17 00:00:00 2001 From: "name.ZuLu0890" Date: Sun, 26 Apr 2026 15:06:37 +0100 Subject: [PATCH 3/3] Add rewards and payments files to format:check script - Include src/rewards/**/*.ts in format:check - Include payments module files (payments.module.ts, payments.service.ts, payment.dto.ts) in format:check - Ensures new loyalty rewards code follows project formatting standards --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ddd001..d978248 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "format:check": "prettier --check \"src/app.module.ts\" \"src/users/users.service.ts\" \"src/job-processing/**/*.ts\" \"src/email/email.module.ts\" \"src/email/email.processor.ts\" \"src/email/email.service.ts\" \"src/media/media.controller.ts\" \"src/media/media.jobs.ts\" \"src/media/media.module.ts\" \"src/media/media.processor.ts\" \"src/media/media.service.ts\" \"src/media/services/image-processing.service.ts\" \"src/products/product-images.controller.ts\" \"src/recommendation/recommendation.controller.ts\" \"src/recommendation/recommendation.jobs.ts\" \"src/recommendation/recommendation.module.ts\" \"src/recommendation/recommendation.processor.ts\" \"src/recommendation/recommendation.scheduler.ts\" \"src/recommendation/recommendation.service.ts\" \"src/messaging/**/*.ts\" \"README.md\" \"package.json\" \"docker-compose.yml\" \".github/workflows/*.yml\"", + "format:check": "prettier --check \"src/app.module.ts\" \"src/users/users.service.ts\" \"src/job-processing/**/*.ts\" \"src/email/email.module.ts\" \"src/email/email.processor.ts\" \"src/email/email.service.ts\" \"src/media/media.controller.ts\" \"src/media/media.jobs.ts\" \"src/media/media.module.ts\" \"src/media/media.processor.ts\" \"src/media/media.service.ts\" \"src/media/services/image-processing.service.ts\" \"src/products/product-images.controller.ts\" \"src/recommendation/recommendation.controller.ts\" \"src/recommendation/recommendation.jobs.ts\" \"src/recommendation/recommendation.module.ts\" \"src/recommendation/recommendation.processor.ts\" \"src/recommendation/recommendation.scheduler.ts\" \"src/recommendation/recommendation.service.ts\" \"src/messaging/**/*.ts\" \"src/rewards/**/*.ts\" \"src/payments/payments.module.ts\" \"src/payments/payments.service.ts\" \"src/payments/dto/payment.dto.ts\" \"README.md\" \"package.json\" \"docker-compose.yml\" \".github/workflows/*.yml\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch",