All files / src/platform/payments payment-routing.service.ts

0% Statements 0/37
0% Branches 0/29
0% Functions 0/4
0% Lines 0/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                                                                                                                                                                                                                             
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import {
  PaymentProviderRegion,
  PaymentRoutingRule,
  PaymentRoutingDecision,
} from './schemas/payment-routing.schema';
import { BillingService } from '../billing/billing.service';
 
@Injectable()
export class PaymentRoutingService {
  private readonly logger = new Logger(PaymentRoutingService.name);
 
  constructor(
    @InjectModel(PaymentProviderRegion.name)
    private readonly regionModel: Model<PaymentProviderRegion>,
    @InjectModel(PaymentRoutingRule.name)
    private readonly ruleModel: Model<PaymentRoutingRule>,
    @InjectModel(PaymentRoutingDecision.name)
    private readonly decisionModel: Model<PaymentRoutingDecision>,
    private readonly billingService: BillingService,
  ) {}
 
  async getRouteProvider(
    tenantId: string,
    billingCountry: string,
    amount: number,
    currency: string,
  ): Promise<string> {
    const cleanCountry = billingCountry.trim().toUpperCase();
 
    // 1. Check custom tenant rules
    const customRule = await this.ruleModel
      .findOne({
        tenantId,
        countryCode: { $in: [cleanCountry, '*'] },
        isActive: true,
      })
      .sort({ priority: -1 })
      .exec();
 
    if (customRule) {
      await this.decisionModel.create({
        tenantId,
        billingCountry: cleanCountry,
        amount,
        currency,
        routedProvider: customRule.providerName,
        routingRuleId: (customRule as any)._id.toString(),
        reason: 'Custom tenant routing rule matches country',
      });
      return customRule.providerName;
    }
 
    // 2. Check system region settings
    const systemRegion = await this.regionModel
      .findOne({ countryCode: cleanCountry, isActive: true })
      .exec();
    if (systemRegion) {
      await this.decisionModel.create({
        tenantId,
        billingCountry: cleanCountry,
        amount,
        currency,
        routedProvider: systemRegion.primaryProvider,
        reason: 'System regional defaults matches country',
      });
      return systemRegion.primaryProvider;
    }
 
    // 3. System hardcoded default fallback:
    // If country is India (IN), use Easebuzz; otherwise use Stripe.
    const routedProvider = cleanCountry === 'IN' ? 'Easebuzz' : 'Stripe';
    await this.decisionModel.create({
      tenantId,
      billingCountry: cleanCountry,
      amount,
      currency,
      routedProvider,
      reason: 'Hardcoded country rules default routing fallback',
    });
 
    return routedProvider;
  }
 
  async configureCustomRule(
    tenantId: string,
    name: string,
    countryCode: string,
    providerName: string,
    priority = 0,
  ): Promise<PaymentRoutingRule> {
    if (!['Stripe', 'Easebuzz'].includes(providerName)) {
      throw new BadRequestException(`Unsupported gateway: ${providerName}`);
    }
 
    return this.ruleModel.create({
      tenantId,
      name,
      countryCode: countryCode.toUpperCase(),
      providerName,
      priority,
      isActive: true,
    });
  }
 
  async configureSystemRegion(
    countryCode: string,
    primaryProvider: string,
    fallbackProvider?: string,
  ): Promise<PaymentProviderRegion> {
    const cleanCountry = countryCode.toUpperCase();
    const existing = await this.regionModel
      .findOne({ countryCode: cleanCountry })
      .exec();
    const doc = existing ?? new this.regionModel({ countryCode: cleanCountry });
 
    doc.primaryProvider = primaryProvider;
    if (fallbackProvider) doc.fallbackProvider = fallbackProvider;
    doc.isActive = true;
 
    await doc.save();
    return doc;
  }
}