From 75a537aab413c7b842db3c908a8853c650c4be07 Mon Sep 17 00:00:00 2001 From: Kallesh B S Date: Mon, 18 Aug 2025 20:34:44 +0530 Subject: [PATCH] pay 2 --- src/app.module.ts | 3 +- .../carnet-application-property.dto.ts | 7 + .../carnet-application.dto.ts | 3 +- src/exceptions/notFound.exception.ts | 19 +++ .../carnet-application.controller.ts | 16 +++ .../carnet-application.service.ts | 126 +++++++++++++++++- src/oracle/oracle.module.ts | 4 +- src/paypal/paypal.controller.ts | 4 + src/paypal/paypal.module.ts | 10 ++ src/paypal/paypal.service.ts | 69 ++++++++++ 10 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 src/exceptions/notFound.exception.ts create mode 100644 src/paypal/paypal.controller.ts create mode 100644 src/paypal/paypal.module.ts create mode 100644 src/paypal/paypal.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 21cc89b..f7caa7c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,11 +6,12 @@ import { OriginCheckMiddleware } from './middleware/OriginCheck.middleware'; import { ReqBodyKeysToUppercaseMiddleware } from './middleware/reqBodyKeysToUppercase.middleware'; import { ConfigModule } from '@nestjs/config'; import { MailModule } from './mail/mail.module'; +import { PaypalModule } from './paypal/paypal.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), - AuthModule, DbModule, OracleModule, MailModule + AuthModule, DbModule, OracleModule, MailModule, PaypalModule ], controllers: [], providers: [], diff --git a/src/dto/carnet-application/carnet-application-property.dto.ts b/src/dto/carnet-application/carnet-application-property.dto.ts index 86212f9..5bfdd0e 100644 --- a/src/dto/carnet-application/carnet-application-property.dto.ts +++ b/src/dto/carnet-application/carnet-application-property.dto.ts @@ -39,6 +39,13 @@ export class CARNETNO_DTO { P_CARNETNO: string; } +export class ORDERID_DTO { + @ApiProperty({ required: true }) + @IsString() + @IsDefined({ message: 'Property P_ORDERID is required' }) + P_ORDERID: string; +} + export class HEADERID_DTO { @ApiProperty({ required: true }) @Max(999999999, { diff --git a/src/dto/carnet-application/carnet-application.dto.ts b/src/dto/carnet-application/carnet-application.dto.ts index ba0bcb9..f69ffbf 100644 --- a/src/dto/carnet-application/carnet-application.dto.ts +++ b/src/dto/carnet-application/carnet-application.dto.ts @@ -4,7 +4,7 @@ import { APPLICATIONNAME_DTO, AUTHREP_DTO, AUTO_FLAG_DTO, CARNETNO_DTO, COMMERCIAL_SAMPLE_FLAG_DTO, COUNTRYTABLE_DTO, CUSTCOURIERNO_DTO, DELIVERYMETHOD_DTO, DELIVERYTYPE_DTO, EXIBITIONS_FAIR_FLAG_DTO, EXTENSION_PERIOD_DTO, FORMOFSECURITY_DTO, GLTABLE_DTO, GLTABLE_ITEMNO_OPTIONAL_DTO, GOODS_COUNTRY_DTO, GOODS_PORT_DTO, HEADERID_DTO, - HORSE_FLAG_DTO, INSPROTECTION_DTO, ITEMNO_DTO, LDIPROTECTION_DTO, ORDERTYPE_DTO, PAYMENTMETHOD_DTO, + HORSE_FLAG_DTO, INSPROTECTION_DTO, ITEMNO_DTO, LDIPROTECTION_DTO, ORDERID_DTO, ORDERTYPE_DTO, PAYMENTMETHOD_DTO, PRINTGL_DTO, PROF_EQUIPMENT_FLAG_DTO, REASON_CODE_DTO, REFNO_DTO, SHIPADDRID_DTO, SHIPCONTACTID_DTO, SHIPNAME_DTO, SHIPTOTYPE_DTO, USSETS_DTO } from "./carnet-application-property.dto"; @@ -68,6 +68,7 @@ export class SaveExtensionApplicationDTO extends (IntersectionType( export class PrintCarnetDTO extends (IntersectionType(SPID_DTO, HEADERID_DTO, PRINTGL_DTO)) { } export class PrintGLDTO extends (IntersectionType(SPID_DTO, HEADERID_DTO)) { } +export class CapturePaymentDTO extends (IntersectionType(ORDERID_DTO)) { } export class CreateApplicationDTO extends IntersectionType( SPID_DTO, CLIENTID_DTO, LOCATIONID_DTO, USERID_DTO, APPLICATIONNAME_DTO, diff --git a/src/exceptions/notFound.exception.ts b/src/exceptions/notFound.exception.ts new file mode 100644 index 0000000..00f388b --- /dev/null +++ b/src/exceptions/notFound.exception.ts @@ -0,0 +1,19 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; + +export class NotFoundException extends HttpException { + constructor( + message = 'Not Found', + // errorCode = 'INTERNAL_ERROR', + // data: any = null, + ) { + super( + { + statusCode: HttpStatus.NOT_FOUND , + message, + // errorCode, + // data, + }, + HttpStatus.NOT_FOUND, + ); + } +} diff --git a/src/oracle/carnet-application/carnet-application.controller.ts b/src/oracle/carnet-application/carnet-application.controller.ts index c9c3451..7633343 100644 --- a/src/oracle/carnet-application/carnet-application.controller.ts +++ b/src/oracle/carnet-application/carnet-application.controller.ts @@ -7,6 +7,7 @@ import { AddCountriesDTO, AddGenerallistItemsDTO, CA_UpdateHolderDTO, + CapturePaymentDTO, CarnetProcessingCenterDTO, CarnetProcessingCenterDTO2, CopyCarnetDTO, CreateApplicationDTO, DeleteGenerallistItemsDTO, EditGenerallistItemsDTO, GetCarnetControlCenterDTO, GetExtendedSectionDTO, PrintCarnetDTO, PrintGLDTO, SaveCarnetApplicationDTO, SaveExtensionApplicationDTO, TransmitApplicationtoProcessDTO, UpdateExpGoodsAuthRepDTO, UpdateShippingDetailsDTO @@ -271,4 +272,19 @@ export class CarnetApplicationController { } } + //[payment] + + @Post('InitiatePayment') + @UseGuards() + async InitiatePayment() { + return this.carnetApplicationService.InitiatePayment(); + } + + @Post('CompletePayment') + @HttpCode(200) + @UseGuards() + async CapturePayment(@Body() body: CapturePaymentDTO) { + return this.carnetApplicationService.CapturePayment(body); + } + } diff --git a/src/oracle/carnet-application/carnet-application.service.ts b/src/oracle/carnet-application/carnet-application.service.ts index 499d3e4..d4d390e 100644 --- a/src/oracle/carnet-application/carnet-application.service.ts +++ b/src/oracle/carnet-application/carnet-application.service.ts @@ -20,10 +20,14 @@ import { CopyCarnetDTO, GetExtendedSectionDTO, SaveExtensionApplicationDTO, - CarnetProcessingCenterDTO2 + CarnetProcessingCenterDTO2, + CapturePaymentDTO } from 'src/dto/property.dto'; import { OracleService } from '../oracle.service'; import { BadRequestException } from 'src/exceptions/badRequest.exception'; +import axios, { AxiosError, AxiosRequestConfig } from 'axios'; +import { PaypalService } from 'src/paypal/paypal.service'; +import { NotFoundException } from 'src/exceptions/notFound.exception'; @Injectable() export class CarnetApplicationService { @@ -32,7 +36,8 @@ export class CarnetApplicationService { constructor( private readonly oracleDBService: OracleDBService, - private readonly oracleService: OracleService + private readonly oracleService: OracleService, + private readonly payPalService: PaypalService ) { } // [ CARNETAPPLICATION_PKG ] @@ -1582,4 +1587,121 @@ export class CarnetApplicationService { } } + // [payment] + + + async InitiatePayment() { + try { + const accessToken = await this.payPalService.generateAccessToken(); + + // return { accessToken, orderid: "1abc" } + + const response = await axios({ + url: process.env.PAYPAL_BASE_URL + '/v2/checkout/orders', + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + accessToken + }, + data: JSON.stringify({ + intent: 'CAPTURE', + purchase_units: [ + { + items: [ + { + name: 'carnet', + description: 'carnet', + quantity: 1, + unit_amount: { + currency_code: 'USD', + value: '100.00' + } + } + ], + amount: { + currency_code: 'USD', + value: '100.00', + breakdown: { + item_total: { + currency_code: 'USD', + value: '100.00' + } + } + } + } + ], + application_context: { + return_url: process.env.BASE_URL + '/complete-order', + cancel_url: process.env.BASE_URL + '/cancel-order', + shipping_preference: 'NO_SHIPPING', + user_action: 'PAY_NOW', + brand_name: 'test sample', + disable_funding: "card" + } + }) + }); + + // console.log('PayPal create order response headers:', response.headers); + // console.log('PayPal create order response data:', response.data); + + return { + id: response.data.id, href: response?.data?.links?.find(obj => obj.rel === 'approve')?.href + }; + + // return { id: response.data.id }; + + } catch (error) { + console.error('Error creating PayPal order:', error.response?.data || error.message || error); + + // Return a structured error object or throw to be handled by caller + + throw new InternalServerException(); + // return { + // error: true, + // message: 'Failed to create PayPal order', + // details: error.response?.data || error.message || error + // }; + } + } + + async CapturePayment(body: CapturePaymentDTO) { + try { + const accessToken = await this.payPalService.generateAccessToken(); + + const response = await axios({ + url: `${process.env.PAYPAL_BASE_URL}/v2/checkout/orders/${body.P_ORDERID}/capture`, + method: 'post', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}`, + }, + }); + + const capture = response?.data?.purchase_units?.[0]?.payments?.captures?.[0]; + + return { + statusCode: 200, + message: "Payment successful", + amount: `${capture?.amount?.value} ${capture?.amount?.currency_code}`, + }; + + } catch (error: any) { + const status = error.response?.status || 500; + const message = error.response?.data?.message || "Payment failed due to an unexpected error."; + + // Optional: log full error for debugging + console.error("PayPal Capture Error:", { + status, + message, + details: error.response?.data, + }); + + if (status === 404) { + throw new NotFoundException(error.response?.data?.message || error?.message || "Resource Not Found") + } + + throw new InternalServerException(); + } + } + } diff --git a/src/oracle/oracle.module.ts b/src/oracle/oracle.module.ts index d325bdc..28b3f22 100644 --- a/src/oracle/oracle.module.ts +++ b/src/oracle/oracle.module.ts @@ -10,11 +10,13 @@ import { UserMaintenanceModule } from './user-maintenance/user-maintenance.modul import { CarnetApplicationModule } from './carnet-application/carnet-application.module'; import { OracleService } from './oracle.service'; import { AuthModule } from 'src/auth/auth.module'; +import { PaypalModule } from 'src/paypal/paypal.module'; @Global() @Module({ imports: [ DbModule, + PaypalModule, CarnetApplicationModule, UserMaintenanceModule, HomePageModule, @@ -28,4 +30,4 @@ import { AuthModule } from 'src/auth/auth.module'; controllers: [], exports: [OracleService, UserMaintenanceModule], }) -export class OracleModule {} +export class OracleModule { } diff --git a/src/paypal/paypal.controller.ts b/src/paypal/paypal.controller.ts new file mode 100644 index 0000000..1ce196d --- /dev/null +++ b/src/paypal/paypal.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('paypal') +export class PaypalController {} diff --git a/src/paypal/paypal.module.ts b/src/paypal/paypal.module.ts new file mode 100644 index 0000000..0ffdd6a --- /dev/null +++ b/src/paypal/paypal.module.ts @@ -0,0 +1,10 @@ +import { Global, Module } from '@nestjs/common'; +import { PaypalController } from './paypal.controller'; +import { PaypalService } from './paypal.service'; + +@Global() +@Module({ + providers: [PaypalService], + exports: [PaypalService] +}) +export class PaypalModule { } diff --git a/src/paypal/paypal.service.ts b/src/paypal/paypal.service.ts new file mode 100644 index 0000000..e5d4c04 --- /dev/null +++ b/src/paypal/paypal.service.ts @@ -0,0 +1,69 @@ +// paypal.service.ts +import { Injectable, Logger } from '@nestjs/common'; +import axios from 'axios'; +import { InternalServerException } from 'src/exceptions/internalServerError.exception'; + +@Injectable() +export class PaypalService { + private accessToken: string | null = null; + private tokenExpiresAt: number = 0; // Unix timestamp in milliseconds + + private readonly clientId = process.env.PAYPAL_CLIENT_ID; + private readonly clientSecret = process.env.PAYPAL_CLIENT_SECRET; + private readonly baseUrl = process.env.PAYPAL_BASE_URL; // or live URL + + private readonly logger = new Logger(PaypalService.name); + + async generateAccessToken(): Promise { + + // return process.env.PAYPAL_AT + const now = Date.now(); + + // Check if token is still valid + if (this.accessToken && now < this.tokenExpiresAt - 60_000) { + return this.accessToken; + } + + // Fetch new token + const auth = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64'); + + try { + const tokenUrl = process.env.PAYPAL_BASE_URL + '/v1/oauth2/token'; + + const params = new URLSearchParams(); + params.append('grant_type', 'client_credentials'); + + const response = await axios.post( + tokenUrl, + params.toString(), // or use `params` directly + { + auth: { + username: process.env.PAYPAL_CLIENT_ID!, + password: process.env.PAYPAL_SECRET!, + }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + } + ); + + const { access_token, expires_in } = response.data; + + this.accessToken = access_token; + this.tokenExpiresAt = now + expires_in * 1000; // convert seconds to ms + + // this.logger.warn(`Fetched new PayPal access token, expires in ${access_token} seconds`); + // console.log(access_token); + + + if (this.accessToken) { + return this.accessToken; + } + throw new InternalServerException("Error while getting paypal access token") + } catch (error) { + this.logger.error('Failed to fetch PayPal access token', error); + throw new InternalServerException('PayPal token fetch failed'); + } + } + +}