import { createEnsureSuccessResponseInterceptor, createHeaderRequestInterceptor, FetchRequestInterceptor, } from '@/common/interceptors/fetch-request.interceptor' import { HttpClientUtil } from '@/common/utils/http-client.util' import { TspProviderResponseStatus } from '@/generated/prisma/enums' import { Injectable, Logger } from '@nestjs/common' import { IProviderSwitchAdapter, TspProviderBulkSendResultDto, TspProviderCorrectionSendPayloadDto, TspProviderCorrectionSendResponseDto, TspProviderGetResultDto, TspProviderOriginalSendPayloadDto, TspProviderRevokePayloadDto, TspProviderRevokeResponseDto, TspProviderSendItemResultDto, } from '../../dto/provider-switch.dto' import { NamaProviderGetResponseDto, NamaProviderResponseStatus, NamaProviderRevokeRequestDto, NamaProviderRevokeResponseDto, NamaProviderSendItemResponseDto, } from './nama-provider.dto' import { NamaProviderUtils } from './nama-provider.util' @Injectable() export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter { readonly code = 'NAMA' private readonly logger = new Logger(NamaProviderSwitchAdapter.name) private readonly sandboxBaseUrl = process.env.NAMA_PROVIDER_SANDBOX_URL || 'https://external-api-dev.namatsp.ir' private readonly productionBaseUrl = process.env.NAMA_PROVIDER_PRODUCTION_URL || 'https://api.nama.ir' private readonly sendPath = '/api/v1/invoices/single-send' private readonly sendBulkPath = '/api/v1/invoices' private readonly checkBulkStatus = '/api/v1/invoices/check-by-uuids' private readonly getPath = '/api/v1/invoices' constructor( private readonly httpClient: HttpClientUtil, private readonly namaProviderUtils: NamaProviderUtils, ) {} private get baseUrl() { return this.sandboxBaseUrl // return process.env.NODE_ENV === 'production' // ? this.productionBaseUrl // : this.sandboxBaseUrl } private buildSendUrl() { return `${this.baseUrl}${this.sendPath}` } private buildGetUrl(invoiceId: string) { return `${this.baseUrl}${this.getPath}/${invoiceId}` } private createRequestInterceptors(token: string): FetchRequestInterceptor[] { return [ createHeaderRequestInterceptor({ 'Content-Type': 'application/json', Authorization: token, }), createEnsureSuccessResponseInterceptor(), { onError: (context, error) => { this.logger.error(`NAMA request failed: ${context.url}`, error) return Promise.reject(error) }, }, ] } async originalSend( payload: TspProviderOriginalSendPayloadDto, ): Promise { const mappedRequest = this.namaProviderUtils.mapToNamaRequestDto(payload) try { const response = await this.httpClient.request( this.buildSendUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.tsp_token), ) const providerResponse: NamaProviderSendItemResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderSendItemResultDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: !response.ok, message: providerResponse.message, provider_response_payload: JSON.parse(JSON.stringify(providerResponse)), received_at: providerResponse.tsp_update_time, sent_at: new Date().toISOString(), tax_id: providerResponse.tax_id, status: this.namaProviderUtils.mapResponseStatus( providerResponse.status as NamaProviderResponseStatus, ), } return result } catch (err) { this.logger.error('NAMA send failed', err) const failure: TspProviderSendItemResultDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: true, message: (err as Error).message, sent_at: new Date().toISOString(), received_at: new Date().toISOString(), tax_id: null, status: TspProviderResponseStatus.NOT_SEND, } throw failure } } async correctionSend( payload: TspProviderCorrectionSendPayloadDto, ): Promise { const mappedRequest = this.namaProviderUtils.mapToNamaRequestDto(payload) try { const response = await this.httpClient.request( this.buildSendUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.tsp_token), ) const providerResponse: NamaProviderSendItemResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderSendItemResultDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: !response.ok, message: providerResponse.message, sent_at: new Date().toISOString(), provider_response_payload: JSON.parse(JSON.stringify(providerResponse)), received_at: providerResponse.tsp_update_time, tax_id: providerResponse.tax_id, status: this.namaProviderUtils.mapResponseStatus( providerResponse.status as NamaProviderResponseStatus, ), } return result } catch (err) { this.logger.error('NAMA send failed', err) const failure: TspProviderSendItemResultDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: true, message: (err as Error).message, sent_at: new Date().toISOString(), received_at: new Date().toISOString(), tax_id: null, status: TspProviderResponseStatus.NOT_SEND, } return failure } } async sendBulk( payloads: TspProviderOriginalSendPayloadDto[], ): Promise { const result: TspProviderBulkSendResultDto[] = [] for (const payload of payloads) { const itemResults = await this.originalSend(payload) result.push({ invoice_id: payload.invoice_id, items: [itemResults], }) } return result } async get(invoiceId: string, tsp_token: string): Promise { try { const response = await this.httpClient.request( this.buildGetUrl(invoiceId), { method: 'GET', }, this.createRequestInterceptors(tsp_token), ) const providerResponse: NamaProviderGetResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderGetResultDto = { hasError: !response.ok, message: providerResponse.message, provider_response_payload: providerResponse, sent_at: new Date().toISOString(), received_at: providerResponse.tsp_update_time || new Date().toISOString(), tax_id: providerResponse.tax_id, status: this.namaProviderUtils.mapResponseStatus( providerResponse.status as NamaProviderResponseStatus, ), } return result } catch (err) { this.logger.error('NAMA send failed', err) // const failure: TspProviderGetResultDto = { // invoice_id: invoiceId, // hasError: true, // message: (err as Error).message, // received_at: new Date().toISOString(), // tax_id: null, // status: TspProviderResponseStatus.NOT_SEND, // } // return failure throw err } } async revoke( payload: TspProviderRevokePayloadDto, ): Promise { const mappedRequest: NamaProviderRevokeRequestDto = this.namaProviderUtils.mapRevokeToNamaRequestDto(payload) this.logger.debug('NAMA provider response', mappedRequest) try { const response = await this.httpClient.request( this.buildSendUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.tsp_token), ) const providerResponse: NamaProviderRevokeResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderRevokeResponseDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: !response.ok, message: providerResponse.message, sent_at: new Date().toISOString(), provider_response_payload: providerResponse, received_at: providerResponse.tsp_update_time, tax_id: providerResponse.tax_id, status: this.namaProviderUtils.mapResponseStatus( providerResponse.status as NamaProviderResponseStatus, ), } return result } catch (err) { this.logger.error('NAMA Revoke failed', err) const failure: TspProviderRevokeResponseDto = { invoice_id: payload.invoice_id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: true, message: (err as Error).message, sent_at: new Date().toISOString(), received_at: new Date().toISOString(), tax_id: '', status: TspProviderResponseStatus.FAILURE, } return failure } } }