Skip to content

Instantly share code, notes, and snippets.

@Comurule
Created July 27, 2025 09:25
Show Gist options
  • Select an option

  • Save Comurule/08228c3e34ef2a4744f3cc6728ec5a91 to your computer and use it in GitHub Desktop.

Select an option

Save Comurule/08228c3e34ef2a4744f3cc6728ec5a91 to your computer and use it in GitHub Desktop.
async intiate(userId: Types.ObjectId, payload: InitiateCrossBorderTrxDTO) {
const session = await this.connection.startSession();
try {
await session.withTransaction(
async (session: ClientSession) => {
const isVerified = await this.pinService.verifyUser({
userId,
pin: payload.pin,
biometric: payload.biometric,
});
if (!isVerified) {
throw new BadRequestException({
statusCode: HttpStatus.BAD_REQUEST,
message: 'User pin/biometrics is not verified.',
});
}
const paymentProviders = this.getPaymentProviders(payload);
const payerWalletProfile = await this.walletService.getWalletProfile(
{ userId, currency: payload.from },
session,
);
const { user: payerUserInfo, ...payerWallet } = payerWalletProfile;
if (!payerWallet) {
throw new BadRequestException({
statusCode: HttpStatus.BAD_REQUEST,
message: 'Insufficient Balance',
});
}
const chargedAmount = await this.chargeService.verifyChargedAmount(
payload.amount,
payerUserInfo.phoneNumber,
TransactionType.CROSS_BORDER,
0,
false,
);
await this.walletService.checkTransactionLimit(
chargedAmount.totalAmount,
userId.toString(),
);
const deductUserAvailableBalance = await this.walletService.updateBalance(
{
$expr: {
$gte: [
{
$toDouble: '$availableBalance',
},
chargedAmount.totalAmount,
],
},
userId,
currency: payerWalletProfile.currency,
},
-NumberToDecimal128(chargedAmount.totalAmount),
session,
);
if (!deductUserAvailableBalance) {
throw new BadRequestException({
statusCode: HttpStatus.BAD_REQUEST,
message: 'Insufficient Balance',
});
}
const transactionRef = randomUUID();
const transactionPayload: Partial<Transaction> = {
transactionRef,
externalReference: `PAYRIT-${transactionRef}`,
transactionType: TransactionType.CROSS_BORDER,
initialAmount: NumberToDecimal128(chargedAmount.totalAmount),
amount: NumberToDecimal128(chargedAmount.totalAmount - chargedAmount.charge),
status: TransactionStatus.PENDING,
walletId: payerWallet,
payerId: payerWallet,
charges: NumberToDecimal128(chargedAmount.charge),
accountingType: AccountingType.DEBIT,
balanceHistory: {
initialBalance: payerWallet.availableBalance,
currentBalance: deductUserAvailableBalance.availableBalance,
},
metaData: {
sourceCurrency: payload.from,
targetCurrency: payload.to,
},
description: `Payrit payment from ${payerUserInfo.firstName} ${payerUserInfo.lastName}`,
category: await this.categoryModel.findOne({ slug: 'Transfers' }, '_id'),
history: [
{
status: TransactionStatus.PENDING,
date: new Date().toISOString(),
},
],
latitude: payload.latitude,
longitude: payload.longitude,
};
await Promise.all([
this.transactionService.create([transactionPayload], session),
this.crossBorderRepo.create(
{
transactionRef,
amount: NumberToDecimal128(payload.amount),
sourceCurrency: payload.from,
targetCurrency: payload.to,
onRampProvider: paymentProviders.onRamp,
offRampProvider: paymentProviders.offRamp,
charges: NumberToDecimal128(chargedAmount.charge),
amountReceived: null,
usdcAmount: null,
paymentChannel: payload.paymentChannel,
recipientAccount: payload.paymentChannelInput,
providerReceipts: [],
},
session,
),
]);
await session.commitTransaction();
// Send to a queue for handling
this.handleCrossBorderTransactionByStatus(transactionRef);
},
{ readConcern: 'majority', writeConcern: { w: 'majority', j: true }, retryWrites: true },
);
} catch (error) {
await session.abortTransaction();
this.logger.error(error);
errorHandler(error);
} finally {
session.endSession();
}
}
async verifyUserPin({ user, ...payload }: PinDto & Pick<TokenData, 'user'>) {
let pin = await this.getCachedPin(user.toString());
if (!pin) {
const { pin: existingPin = null } =
(await this.pinModel.findOne({ user: user }).exec()) || {};
if (!existingPin) {
return false;
}
pin = existingPin;
await this.cachePin(user.toString(), pin);
}
await this.decryptPin(payload.pin, pin);
return true;
}
async verifyPin({ user, ...payload }: PinDto & Pick<TokenData, 'user'>): Promise<IResponse> {
try {
const verified = await this.verifyUserPin({ user, pin: payload.pin });
if (!verified) {
throw new BadRequestException({
statusCode: HttpStatus.BAD_REQUEST,
message: 'User pin does not exist',
});
}
return {
status: 'success',
statusCode: HttpStatus.OK,
message: 'Pin verified successfully',
data: null,
error: null,
};
} catch (error) {
this.logger.error(error);
errorHandler(error);
}
}
async verifyUser({ userId, pin, biometric }) {
if (!pin) {
return biometric === this.configService.get(BIOMETRICSKEY.BIOMETRIC_ENCRYPTION_KEY);
}
return this.verifyUserPin({ user: userId, pin });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment