Skip to main content

Financial Payout Services

This document provides detailed information about the financial payout services integrated into the Rahat Anticipatory Action platform, including offramp services, payment providers, and token-to-fiat conversion workflows.

Overview​

The financial payout system enables the conversion of blockchain tokens to local currency and distribution to beneficiaries through various payment methods. The system supports multiple payment providers and offramp services for seamless cash distribution.

Architecture​

Core Components​

1. Offramp Services​

  • Purpose: Token-to-fiat conversion and local payment processing
  • Integration: REST API with authentication
  • Features: Instant conversion, bank transfers, VPA payments

2. Payment Providers​

  • Purpose: Local financial service provider integration
  • Features: Multiple provider support, transaction tracking
  • Methods: Bank transfers, mobile money, VPA

3. Payout Management​

  • Purpose: Orchestrate the entire payout process
  • Features: Batch processing, status tracking, error handling

Payout Flow​

1. Payout Initiation​

Payout Service Structure​

// apps/aa/src/payouts/payouts.service.ts
@Injectable()
export class PayoutsService {
constructor(
private readonly offrampService: OfframpService,
private readonly stellarService: StellarService,
private readonly prisma: PrismaService
) {}

async triggerPayout(uuid: string): Promise<any> {
const payoutDetails = await this.findOne(uuid);

if (payoutDetails.isPayoutTriggered) {
throw new RpcException(
`Payout with UUID '${uuid}' has already been triggered`
);
}

const BeneficiaryPayoutDetails = await this.fetchBeneficiaryPayoutDetails(uuid);
const offrampWalletAddress = await this.offrampService.getOfframpWalletAddress();

const stellerOfframpQueuePayload: FSPPayoutDetails[] =
BeneficiaryPayoutDetails.map((beneficiary) => ({
amount: beneficiary.amount,
beneficiaryWalletAddress: beneficiary.walletAddress,
beneficiaryBankDetails: beneficiary.bankDetails,
payoutUUID: uuid,
payoutProcessorId: payoutDetails.payoutProcessorId,
beneficiaryPhoneNumber: beneficiary.phoneNumber,
offrampWalletAddress,
offrampType: payoutExtras.paymentProviderType,
}));

await this.stellarService.addBulkToTokenTransferQueue(
stellerOfframpQueuePayload
);

return 'Payout Initiated Successfully';
}
}

Beneficiary Payout Details​

interface BeneficiaryPayoutDetails {
amount: number;
walletAddress: string;
bankDetails: {
bankName: string;
accountNumber: string;
accountName: string;
ifscCode?: string;
};
phoneNumber: string;
beneficiaryId: string;
}

2. Token Transfer Process​

Stellar Token Transfer​

// apps/aa/src/processors/stellar.processor.ts
@Processor(BQUEUE.STELLAR)
export class StellarProcessor {
@Process(JOBS.STELLAR.TRANSFER_TO_OFFRAMP)
async processTransferToOfframp(job: Job) {
const payload: FSPPayoutDetails = job.data;

try {
// Transfer tokens from beneficiary to offramp wallet
const transactionResult = await this.stellarService.transferTokens({
fromAddress: payload.beneficiaryWalletAddress,
toAddress: payload.offrampWalletAddress,
amount: payload.amount,
assetCode: 'RAHAT',
assetIssuer: process.env.STELLAR_ASSET_ISSUER
});

// Add to offramp queue for cash conversion
await this.offrampService.addBulkToOfframpQueue([payload]);

return transactionResult;
} catch (error) {
this.logger.error(`Token transfer failed: ${error.message}`);
throw error;
}
}
}

3. Offramp Processing​

Offramp Service Integration​

// apps/aa/src/payouts/offramp.service.ts
@Injectable()
export class OfframpService {
async fetchOfframpSettings(): Promise<{
url: string;
appId: string;
accessToken: string;
}> {
// Fetch settings from core service
const settings = await this.coreClient.send({
cmd: 'SETTINGS.GET',
data: { name: 'OFFRAMP_SETTINGS' }
});

return {
url: settings.value.URL,
appId: settings.value.APP_ID,
accessToken: settings.value.ACCESS_TOKEN
};
}

async getOfframpWalletAddress(): Promise<string> {
const offrampSettings = await this.fetchOfframpSettings();

const response = await this.httpService.axiosRef.get(
`${offrampSettings.url}/app/${offrampSettings.appId}`,
{
headers: { 'APP_ID': offrampSettings.appId }
}
);

return response.data.data.wallet;
}

async instantOfframp(offrampPayload: any): Promise<CipsResponseData> {
const offrampSettings = await this.fetchOfframpSettings();

const response = await this.httpService.axiosRef.post(
`${offrampSettings.url}/offramp-request/instant`,
offrampPayload,
{
headers: { 'APP_ID': offrampSettings.appId }
}
);

return response.data.data;
}
}

Offramp Payload Generation​

// apps/aa/src/processors/offramp.processor.ts
private async generateOfframpPayload(
offrampType: string,
fspOfframpDetails: FSPOfframpDetails
): Promise<any> {
let offrampRequest: any = {
tokenAmount: fspOfframpDetails.amount,
paymentProviderId: fspOfframpDetails.payoutProcessorId,
transactionHash: fspOfframpDetails.transactionHash,
senderAddress: fspOfframpDetails.beneficiaryWalletAddress,
xref: fspOfframpDetails.payoutUUID,
paymentDetails: {
creditorAgent: getBankId(fspOfframpDetails.beneficiaryBankDetails.bankName),
creditorAccount: fspOfframpDetails.beneficiaryBankDetails.accountNumber,
creditorName: fspOfframpDetails.beneficiaryBankDetails.accountName,
},
};

if (offrampType.toLowerCase() === 'vpa') {
offrampRequest.paymentDetails = {
vpa: fspOfframpDetails.beneficiaryPhoneNumber,
};
}

return offrampRequest;
}

4. Payment Provider Integration​

Payment Provider Types​

interface IPaymentProvider {
id: string;
name: string;
type: 'BANK' | 'MOBILE_MONEY' | 'VPA' | 'CASH';
country: string;
currency: string;
supportedMethods: string[];
processingTime: string;
fees: {
percentage: number;
fixed: number;
};
}

Payment Provider API​

async getPaymentProvider(): Promise<IPaymentProvider[]> {
const offrampSettings = await this.fetchOfframpSettings();

const response = await this.httpService.axiosRef.get(
`${offrampSettings.url}/payment-providers`,
{
headers: { 'APP_ID': offrampSettings.appId }
}
);

return response.data.data;
}

Status Tracking​

Payout Status​

enum PayoutStatus {
PENDING = 'PENDING',
PROCESSING = 'PROCESSING',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
CANCELLED = 'CANCELLED'
}

Beneficiary Redeem Status​

interface BeneficiaryRedeem {
uuid: string;
beneficiaryId: string;
payoutUUID: string;
amount: number;
status: PayoutStatus;
transactionHash?: string;
offrampResponse?: any;
errorMessage?: string;
numberOfAttempts: number;
createdAt: Date;
updatedAt: Date;
}

Monitoring and Analytics​

Payout Metrics​

interface PayoutMetrics {
totalPayouts: number;
totalAmount: number;
successfulPayouts: number;
failedPayouts: number;
averageProcessingTime: number;
successRate: number;
}

Transaction Tracking​

async getPayoutDetails(uuid: string) {
return {
payout: await this.prisma.payouts.findUnique({ where: { uuid } }),
beneficiaryRedeems: await this.prisma.beneficiaryRedeem.findMany({
where: { payoutUUID: uuid }
}),
transactionHistory: await this.getTransactionHistory(uuid)
};
}

Configuration Management​

Settings Management​

// Offramp settings stored in database
interface OfframpSettings {
URL: string;
APP_ID: string;
ACCESS_TOKEN: string;
DEFAULT_PAYMENT_PROVIDER: string;
TRANSACTION_LIMITS: {
MIN_AMOUNT: number;
MAX_AMOUNT: number;
DAILY_LIMIT: number;
};
}