import { Events, type IPortalApi, generateTraceId, sdkLogger, } from '@portal-hq/utils' import type { MeldCreateCustomerRequest, MeldCreateCustomerResponse, MeldCreateRetailWidgetRequest, MeldCreateRetailWidgetResponse, MeldDiscoveryParams, MeldGetBuyLimitsResponse, MeldGetCountriesResponse, MeldGetCryptoCurrenciesResponse, MeldGetDefaultsResponse, MeldGetFiatCurrenciesResponse, MeldGetKycLimitsResponse, MeldGetPaymentMethodsResponse, MeldGetRetailQuoteRequest, MeldGetRetailQuoteResponse, MeldGetRetailTransactionResponse, MeldGetSellLimitsResponse, MeldGetServiceProvidersResponse, MeldSearchCustomerResponse, MeldSearchRetailTransactionsParams, MeldSearchRetailTransactionsResponse, } from './types' const MeldEndpoint = { BASE_PATH: '/api/v3/clients/me/integrations/meld', customers(): string { return `${this.BASE_PATH}/customers` }, retailQuote(): string { return `${this.BASE_PATH}/retail/quote` }, retailWidget(): string { return `${this.BASE_PATH}/retail/widget` }, retailTransactions(): string { return `${this.BASE_PATH}/retail/transactions` }, retailTransaction(id: string): string { return `${this.BASE_PATH}/retail/transactions/${encodeURIComponent(id)}` }, retailTransactionBySession(sessionId: string): string { return `${this.BASE_PATH}/retail/transactions/sessions/${encodeURIComponent(sessionId)}` }, discoveryServiceProviders(): string { return `${this.BASE_PATH}/discovery/service-providers` }, discoveryCountries(): string { return `${this.BASE_PATH}/discovery/countries` }, discoveryFiatCurrencies(): string { return `${this.BASE_PATH}/discovery/fiat-currencies` }, discoveryCryptoCurrencies(): string { return `${this.BASE_PATH}/discovery/crypto-currencies` }, discoveryPaymentMethods(): string { return `${this.BASE_PATH}/discovery/payment-methods` }, discoveryDefaults(): string { return `${this.BASE_PATH}/discovery/defaults` }, discoveryBuyLimits(): string { return `${this.BASE_PATH}/discovery/buy-limits` }, discoverySellLimits(): string { return `${this.BASE_PATH}/discovery/sell-limits` }, discoveryKycLimits(): string { return `${this.BASE_PATH}/discovery/kyc-limits` }, } as const export interface IPortalMeldApi { createCustomer( request: MeldCreateCustomerRequest, ): Promise searchCustomer(): Promise getRetailQuote( request: MeldGetRetailQuoteRequest, ): Promise createRetailWidget( request: MeldCreateRetailWidgetRequest, ): Promise searchRetailTransactions( params?: MeldSearchRetailTransactionsParams, ): Promise getRetailTransaction(id: string): Promise getRetailTransactionBySession( sessionId: string, ): Promise getServiceProviders( params?: MeldDiscoveryParams, ): Promise getCountries(params?: MeldDiscoveryParams): Promise getFiatCurrencies( params?: MeldDiscoveryParams, ): Promise getCryptoCurrencies( params?: MeldDiscoveryParams, ): Promise getPaymentMethods( params?: MeldDiscoveryParams, ): Promise getDefaults(params?: MeldDiscoveryParams): Promise getBuyLimits(params?: MeldDiscoveryParams): Promise getSellLimits( params?: MeldDiscoveryParams, ): Promise getKycLimits(params?: MeldDiscoveryParams): Promise } export interface PortalMeldApiOptions { api: IPortalApi } export class PortalMeldApi implements IPortalMeldApi { private readonly api: IPortalApi constructor(options: PortalMeldApiOptions) { this.api = options.api } public async createCustomer( request: MeldCreateCustomerRequest, ): Promise { const path = MeldEndpoint.customers() const response = await this.post< MeldCreateCustomerResponse, MeldCreateCustomerRequest >(path, request) await this.trackEvent(Events.MeldCreateCustomer, { path }) return response } public async searchCustomer(): Promise { const path = MeldEndpoint.customers() const response = await this.get(path) await this.trackEvent(Events.MeldSearchCustomer, { path }) return response } public async getRetailQuote( request: MeldGetRetailQuoteRequest, ): Promise { const path = MeldEndpoint.retailQuote() const response = await this.post< MeldGetRetailQuoteResponse, MeldGetRetailQuoteRequest >(path, request) await this.trackEvent(Events.MeldGetRetailQuote, { path, countryCode: request.countryCode, sourceCurrencyCode: request.sourceCurrencyCode, destinationCurrencyCode: request.destinationCurrencyCode, }) return response } public async createRetailWidget( request: MeldCreateRetailWidgetRequest, ): Promise { const path = MeldEndpoint.retailWidget() const response = await this.post< MeldCreateRetailWidgetResponse, MeldCreateRetailWidgetRequest >(path, request) await this.trackEvent(Events.MeldCreateRetailWidget, { path, sessionType: request.sessionType, }) return response } public async searchRetailTransactions( params?: MeldSearchRetailTransactionsParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.retailTransactions()}?${queryString}` : MeldEndpoint.retailTransactions() const response = await this.get(path) await this.trackEvent(Events.MeldSearchRetailTransactions, { path: MeldEndpoint.retailTransactions(), }) return response } public async getRetailTransaction( id: string, ): Promise { const path = MeldEndpoint.retailTransaction(id) const response = await this.get(path) await this.trackEvent(Events.MeldGetRetailTransaction, { path, id }) return response } public async getRetailTransactionBySession( sessionId: string, ): Promise { const path = MeldEndpoint.retailTransactionBySession(sessionId) const response = await this.get(path) await this.trackEvent(Events.MeldGetRetailTransactionBySession, { path, sessionId, }) return response } public async getServiceProviders( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryServiceProviders()}?${queryString}` : MeldEndpoint.discoveryServiceProviders() const response = await this.get(path) await this.trackEvent(Events.MeldGetServiceProviders, { path: MeldEndpoint.discoveryServiceProviders(), }) return response } public async getCountries( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryCountries()}?${queryString}` : MeldEndpoint.discoveryCountries() const response = await this.get(path) await this.trackEvent(Events.MeldGetCountries, { path: MeldEndpoint.discoveryCountries(), }) return response } public async getFiatCurrencies( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryFiatCurrencies()}?${queryString}` : MeldEndpoint.discoveryFiatCurrencies() const response = await this.get(path) await this.trackEvent(Events.MeldGetFiatCurrencies, { path: MeldEndpoint.discoveryFiatCurrencies(), }) return response } public async getCryptoCurrencies( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryCryptoCurrencies()}?${queryString}` : MeldEndpoint.discoveryCryptoCurrencies() const response = await this.get(path) await this.trackEvent(Events.MeldGetCryptoCurrencies, { path: MeldEndpoint.discoveryCryptoCurrencies(), }) return response } public async getPaymentMethods( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryPaymentMethods()}?${queryString}` : MeldEndpoint.discoveryPaymentMethods() const response = await this.get(path) await this.trackEvent(Events.MeldGetPaymentMethods, { path: MeldEndpoint.discoveryPaymentMethods(), }) return response } public async getDefaults( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryDefaults()}?${queryString}` : MeldEndpoint.discoveryDefaults() const response = await this.get(path) await this.trackEvent(Events.MeldGetDefaults, { path: MeldEndpoint.discoveryDefaults(), }) return response } public async getBuyLimits( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryBuyLimits()}?${queryString}` : MeldEndpoint.discoveryBuyLimits() const response = await this.get(path) await this.trackEvent(Events.MeldGetBuyLimits, { path: MeldEndpoint.discoveryBuyLimits(), }) return response } public async getSellLimits( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoverySellLimits()}?${queryString}` : MeldEndpoint.discoverySellLimits() const response = await this.get(path) await this.trackEvent(Events.MeldGetSellLimits, { path: MeldEndpoint.discoverySellLimits(), }) return response } public async getKycLimits( params?: MeldDiscoveryParams, ): Promise { const queryString = params ? this.buildQueryParams(params) : '' const path = queryString ? `${MeldEndpoint.discoveryKycLimits()}?${queryString}` : MeldEndpoint.discoveryKycLimits() const response = await this.get(path) await this.trackEvent(Events.MeldGetKycLimits, { path: MeldEndpoint.discoveryKycLimits(), }) return response } private buildQueryParams(params: Record): string { const queryParts: string[] = [] for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null) { const encodedValue = encodeURIComponent(String(value)) queryParts.push(`${key}=${encodedValue}`) } } return queryParts.join('&') } private async get(path: string): Promise { return this.api.requests.get(path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', }, traceId: generateTraceId(), }) } private async post(path: string, body: B): Promise { return this.api.requests.post(path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body, traceId: generateTraceId(), }) } private async trackEvent( event: string, properties: Record, ): Promise { try { await this.api.track(event, properties) } catch (error) { sdkLogger.warn(`[PortalMeldApi] Failed to track event "${event}":`, error) } } } export default PortalMeldApi