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 { TspProviderCorrectionSendPayloadDto, TspProviderCorrectionSendResponseDto, TspProviderGetResultDto, TspProviderOriginalSendItemResultDto, TspProviderOriginalSendPayloadDto, TspProviderRevokePayloadDto, TspProviderRevokeResponseDto, } from '../../dto' import { IProviderSwitchAdapter, TspProviderBulkSendResultDto, } from '../../dto/provider-switch.dto' import { NamaProviderGetResponseDto, NamaProviderOriginalResponseDto, NamaProviderResponseStatus, NamaProviderRevokeRequestDto, NamaProviderRevokeResponseDto, } from './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.mapToNamaOriginalRequestDto(payload) try { const response = await this.httpClient.request( this.buildSendUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.token), ) const providerResponse: NamaProviderOriginalResponseDto = await response.json() this.logger.debug('NAMA provider response', response) this.logger.debug('NAMA provider providerResponse', providerResponse) const result: TspProviderOriginalSendItemResultDto = { invoice_id: payload.id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: !response.ok, message: providerResponse.message, received_at: new Date().toISOString(), provider_response: JSON.parse(JSON.stringify(providerResponse)), tax_id: providerResponse.tax_id, error_message: providerResponse.error_message, fiscal_warnings: providerResponse.tax_api_response, validation_errors: providerResponse.validation_error, status: this.namaProviderUtils.mapResponseStatus( providerResponse.status as NamaProviderResponseStatus, ), } return result } catch (err) { this.logger.error('NAMA send failed', err) const failure: TspProviderOriginalSendItemResultDto = { invoice_id: payload.id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: true, message: (err as Error).message, received_at: new Date().toISOString(), status: TspProviderResponseStatus.SEND_FAILURE, } return failure } } async correctionSend( payload: TspProviderCorrectionSendPayloadDto, ): Promise { const mappedRequest = this.namaProviderUtils.mapToNamaOriginalRequestDto(payload) try { const response = await this.httpClient.request( this.buildSendUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.token), ) const providerResponse: NamaProviderOriginalResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderOriginalSendItemResultDto = { invoice_id: payload.id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: !response.ok, message: providerResponse.message, provider_response: JSON.parse(JSON.stringify(providerResponse)), received_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: TspProviderOriginalSendItemResultDto = { invoice_id: payload.id, provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)), hasError: true, message: (err as Error).message, 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.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: 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.mapToNamaRevokeRequestDto(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: new Date().toISOString(), 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 } } }