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, TspProviderGetResponseDto, TspProviderOriginalResponseDto, TspProviderOriginalSendPayloadDto, TspProviderReturnSendPayloadDto, TspProviderReturnSendResponseDto, TspProviderRevokePayloadDto, TspProviderRevokeResponseDto, } from '../../dto' import { IProviderSwitchAdapter, TspProviderBulkSendResultDto, } from '../../dto/provider-switch.dto' import { NamaProviderCorrectionResponseDto, NamaProviderGetResponseDto, NamaProviderOriginalResponseDto, NamaProviderResponseStatus, NamaProviderRevokeRequestDto, NamaProviderRevokeResponseDto, } from './dto' import { NamaProviderReturnResponseDto } from './dto/returnFromSale.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 correctionPath = this.sendPath private readonly returnFromSalePath = this.sendPath 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 buildCorrectionUrl() { return `${this.baseUrl}${this.correctionPath}` } private buildReturnFromSaleUrl() { return `${this.baseUrl}${this.returnFromSalePath}` } 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() const result: TspProviderOriginalResponseDto = { 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) { console.log('err@@@@@@@@@@@@@@@@@@', err) console.log(JSON.parse(JSON.stringify(mappedRequest))) const failure: TspProviderOriginalResponseDto = { 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.mapToNamaCorrectionDto(payload) try { const response = await this.httpClient.request( this.buildCorrectionUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.token), ) const providerResponse: NamaProviderCorrectionResponseDto = await response.json() this.logger.debug('NAMA provider correction response', providerResponse) const result: TspProviderCorrectionSendResponseDto = { 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 correction send failed', err) const failure: TspProviderCorrectionSendResponseDto = { 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 returnSend( payload: TspProviderReturnSendPayloadDto, ): Promise { const mappedRequest = this.namaProviderUtils.mapToNamaReturnDto(payload) try { const response = await this.httpClient.request( this.buildReturnFromSaleUrl(), { method: 'POST', body: JSON.stringify(mappedRequest), }, this.createRequestInterceptors(payload.token), ) const providerResponse: NamaProviderReturnResponseDto = await response.json() this.logger.debug('NAMA provider return response', providerResponse) const result: TspProviderReturnSendResponseDto = { 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 return from sale send failed', err) const failure: TspProviderReturnSendResponseDto = { 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 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, token: string): Promise { try { const response = await this.httpClient.request( this.buildGetUrl(invoiceId), { method: 'GET', }, this.createRequestInterceptors(token), ) const providerResponse: NamaProviderGetResponseDto = await response.json() this.logger.debug('NAMA provider response', providerResponse) const result: TspProviderGetResponseDto = { hasError: !response.ok, provider_response: JSON.parse(JSON.stringify(providerResponse)), message: providerResponse.message, received_at: new Date().toISOString(), 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: TspProviderGetResponseDto = { hasError: true, message: (err as Error).message, received_at: new Date().toISOString(), } return failure } } 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 } } }