pay 2
This commit is contained in:
parent
a61623b0aa
commit
75a537aab4
@ -6,11 +6,12 @@ import { OriginCheckMiddleware } from './middleware/OriginCheck.middleware';
|
|||||||
import { ReqBodyKeysToUppercaseMiddleware } from './middleware/reqBodyKeysToUppercase.middleware';
|
import { ReqBodyKeysToUppercaseMiddleware } from './middleware/reqBodyKeysToUppercase.middleware';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { MailModule } from './mail/mail.module';
|
import { MailModule } from './mail/mail.module';
|
||||||
|
import { PaypalModule } from './paypal/paypal.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
AuthModule, DbModule, OracleModule, MailModule
|
AuthModule, DbModule, OracleModule, MailModule, PaypalModule
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@ -39,6 +39,13 @@ export class CARNETNO_DTO {
|
|||||||
P_CARNETNO: string;
|
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 {
|
export class HEADERID_DTO {
|
||||||
@ApiProperty({ required: true })
|
@ApiProperty({ required: true })
|
||||||
@Max(999999999, {
|
@Max(999999999, {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
APPLICATIONNAME_DTO, AUTHREP_DTO, AUTO_FLAG_DTO, CARNETNO_DTO, COMMERCIAL_SAMPLE_FLAG_DTO,
|
APPLICATIONNAME_DTO, AUTHREP_DTO, AUTO_FLAG_DTO, CARNETNO_DTO, COMMERCIAL_SAMPLE_FLAG_DTO,
|
||||||
COUNTRYTABLE_DTO, CUSTCOURIERNO_DTO, DELIVERYMETHOD_DTO, DELIVERYTYPE_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,
|
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,
|
PRINTGL_DTO,
|
||||||
PROF_EQUIPMENT_FLAG_DTO, REASON_CODE_DTO, REFNO_DTO, SHIPADDRID_DTO, SHIPCONTACTID_DTO, SHIPNAME_DTO, SHIPTOTYPE_DTO, USSETS_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";
|
} 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 PrintCarnetDTO extends (IntersectionType(SPID_DTO, HEADERID_DTO, PRINTGL_DTO)) { }
|
||||||
export class PrintGLDTO extends (IntersectionType(SPID_DTO, HEADERID_DTO)) { }
|
export class PrintGLDTO extends (IntersectionType(SPID_DTO, HEADERID_DTO)) { }
|
||||||
|
export class CapturePaymentDTO extends (IntersectionType(ORDERID_DTO)) { }
|
||||||
|
|
||||||
export class CreateApplicationDTO extends IntersectionType(
|
export class CreateApplicationDTO extends IntersectionType(
|
||||||
SPID_DTO, CLIENTID_DTO, LOCATIONID_DTO, USERID_DTO, APPLICATIONNAME_DTO,
|
SPID_DTO, CLIENTID_DTO, LOCATIONID_DTO, USERID_DTO, APPLICATIONNAME_DTO,
|
||||||
|
|||||||
19
src/exceptions/notFound.exception.ts
Normal file
19
src/exceptions/notFound.exception.ts
Normal file
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
AddCountriesDTO,
|
AddCountriesDTO,
|
||||||
AddGenerallistItemsDTO,
|
AddGenerallistItemsDTO,
|
||||||
CA_UpdateHolderDTO,
|
CA_UpdateHolderDTO,
|
||||||
|
CapturePaymentDTO,
|
||||||
CarnetProcessingCenterDTO, CarnetProcessingCenterDTO2, CopyCarnetDTO, CreateApplicationDTO, DeleteGenerallistItemsDTO, EditGenerallistItemsDTO, GetCarnetControlCenterDTO, GetExtendedSectionDTO, PrintCarnetDTO, PrintGLDTO, SaveCarnetApplicationDTO, SaveExtensionApplicationDTO, TransmitApplicationtoProcessDTO,
|
CarnetProcessingCenterDTO, CarnetProcessingCenterDTO2, CopyCarnetDTO, CreateApplicationDTO, DeleteGenerallistItemsDTO, EditGenerallistItemsDTO, GetCarnetControlCenterDTO, GetExtendedSectionDTO, PrintCarnetDTO, PrintGLDTO, SaveCarnetApplicationDTO, SaveExtensionApplicationDTO, TransmitApplicationtoProcessDTO,
|
||||||
UpdateExpGoodsAuthRepDTO,
|
UpdateExpGoodsAuthRepDTO,
|
||||||
UpdateShippingDetailsDTO
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,14 @@ import {
|
|||||||
CopyCarnetDTO,
|
CopyCarnetDTO,
|
||||||
GetExtendedSectionDTO,
|
GetExtendedSectionDTO,
|
||||||
SaveExtensionApplicationDTO,
|
SaveExtensionApplicationDTO,
|
||||||
CarnetProcessingCenterDTO2
|
CarnetProcessingCenterDTO2,
|
||||||
|
CapturePaymentDTO
|
||||||
} from 'src/dto/property.dto';
|
} from 'src/dto/property.dto';
|
||||||
import { OracleService } from '../oracle.service';
|
import { OracleService } from '../oracle.service';
|
||||||
import { BadRequestException } from 'src/exceptions/badRequest.exception';
|
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()
|
@Injectable()
|
||||||
export class CarnetApplicationService {
|
export class CarnetApplicationService {
|
||||||
@ -32,7 +36,8 @@ export class CarnetApplicationService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly oracleDBService: OracleDBService,
|
private readonly oracleDBService: OracleDBService,
|
||||||
private readonly oracleService: OracleService
|
private readonly oracleService: OracleService,
|
||||||
|
private readonly payPalService: PaypalService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
// [ CARNETAPPLICATION_PKG ]
|
// [ 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,13 @@ import { UserMaintenanceModule } from './user-maintenance/user-maintenance.modul
|
|||||||
import { CarnetApplicationModule } from './carnet-application/carnet-application.module';
|
import { CarnetApplicationModule } from './carnet-application/carnet-application.module';
|
||||||
import { OracleService } from './oracle.service';
|
import { OracleService } from './oracle.service';
|
||||||
import { AuthModule } from 'src/auth/auth.module';
|
import { AuthModule } from 'src/auth/auth.module';
|
||||||
|
import { PaypalModule } from 'src/paypal/paypal.module';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
DbModule,
|
DbModule,
|
||||||
|
PaypalModule,
|
||||||
CarnetApplicationModule,
|
CarnetApplicationModule,
|
||||||
UserMaintenanceModule,
|
UserMaintenanceModule,
|
||||||
HomePageModule,
|
HomePageModule,
|
||||||
|
|||||||
4
src/paypal/paypal.controller.ts
Normal file
4
src/paypal/paypal.controller.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Controller } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('paypal')
|
||||||
|
export class PaypalController {}
|
||||||
10
src/paypal/paypal.module.ts
Normal file
10
src/paypal/paypal.module.ts
Normal file
@ -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 { }
|
||||||
69
src/paypal/paypal.service.ts
Normal file
69
src/paypal/paypal.service.ts
Normal file
@ -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<any> {
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user