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.

Revisions

  1. Comurule created this gist Jul 27, 2025.
    137 changes: 137 additions & 0 deletions cross-border.service.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,137 @@


    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();
    }
    }
    46 changes: 46 additions & 0 deletions pin.service.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@


    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 });
    }