import { Injectable } from '@nestjs/common'; import { CourierProviderCode } from '../courier.constants'; import { CourierApiError } from '../courier.error'; import type { BatchShipmentQuery, CourierCallOptions, CreateShipmentInput, CreateShipmentResult, DeliveryCoverageResult, FreightEstimateResult, ICourierProvider, ShipmentDetail, ShipmentQuery, TrackCallbackResponse, TrackNode, } from '../courier.types'; import { XiaofeixiaClient } from './xiaofeixia.client'; import { XIAOFEIXIA_CMD } from './xiaofeixia.constants'; import type { XiaofeixiaCreateOrderData, XiaofeixiaDeliveryCoverageData, XiaofeixiaFreightEstimateData, XiaofeixiaOrderDetail, XiaofeixiaTrackNode, } from './xiaofeixia.types'; @Injectable() export class XiaofeixiaProvider implements ICourierProvider { readonly code = CourierProviderCode.XIAOFEIXIA; constructor(private readonly client: XiaofeixiaClient) {} async createShipment(input: CreateShipmentInput, options?: CourierCallOptions): Promise { const data = await this.client.request( XIAOFEIXIA_CMD.CREATE_ORDER, { customerId: input.customerId, outNumber: input.outNumber, fromAddress: input.from.address, fromAddressDetail: input.from.addressDetail, fromCoordinate: this.formatCoordinate(input.from.coordinate), fromMobile: input.from.mobile, fromName: input.from.name, toAddress: input.to.address, toAddressDetail: input.to.addressDetail, toCoordinate: this.formatCoordinate(input.to.coordinate), toMobile: input.to.mobile, toName: input.to.name, goodsName: input.goodsName, goodsNum: input.goodsNum, weight: input.weight, insuredSumPrice: input.insuredSumPrice, collectionPrice: input.collectionPrice, payMode: input.payMode, remark: input.remark, }, options?.xiaofeixia, ); return { providerShipmentId: data.id, trackingNumber: data.number, }; } async cancelShipment(query: ShipmentQuery, options?: CourierCallOptions): Promise { this.assertShipmentQuery(query); await this.client.request( XIAOFEIXIA_CMD.CANCEL_ORDER, { number: query.trackingNumber, outNumber: query.outNumber, }, options?.xiaofeixia, ); } async getShipment(query: ShipmentQuery, options?: CourierCallOptions): Promise { this.assertShipmentQuery(query); const data = await this.client.request( XIAOFEIXIA_CMD.GET_ORDER, { number: query.trackingNumber, outNumber: query.outNumber, }, options?.xiaofeixia, ); return this.mapOrderDetail(data); } async batchGetShipments(query: BatchShipmentQuery, options?: CourierCallOptions): Promise { const number = query.trackingNumbers?.join(','); const outNumber = query.outNumbers?.join(','); if (!number && !outNumber) { throw new CourierApiError('运单号与商家单号至少传一个', '300000', this.code); } const data = await this.client.request( XIAOFEIXIA_CMD.BATCH_GET_ORDER, { number, outNumber, }, options?.xiaofeixia, ); return (data ?? []).map((item) => this.mapOrderDetail(item)); } async getTrack(query: ShipmentQuery, options?: CourierCallOptions): Promise { this.assertShipmentQuery(query); const data = await this.client.request( XIAOFEIXIA_CMD.TRACK_ROUTE, { number: query.trackingNumber, outNumber: query.outNumber, }, options?.xiaofeixia, ); return data ?? []; } async checkDeliveryCoverage(toAddress: string, options?: CourierCallOptions): Promise { const data = await this.client.request( XIAOFEIXIA_CMD.DELIVERY_COVERAGE, { toAddress }, options?.xiaofeixia, ); return { arriveTime: data.arriveTime, siteName: data.name, siteId: data.id, }; } async estimateFreight(weight: number, options?: CourierCallOptions): Promise { const data = await this.client.request( XIAOFEIXIA_CMD.ESTIMATE_FREIGHT, { weight }, options?.xiaofeixia, ); return { freightPrice: data.freightPrice }; } buildTrackCallbackResponse(success = true): TrackCallbackResponse { return { code: success ? '100000' : '300000', message: success ? 'success' : 'fail', }; } private assertShipmentQuery(query: ShipmentQuery): void { if (!query.trackingNumber && !query.outNumber) { throw new CourierApiError('运单号与商家单号至少传一个', '300000', this.code); } } private formatCoordinate(coordinate?: { lng: number; lat: number }): string | undefined { if (!coordinate) return undefined; return JSON.stringify({ lng: coordinate.lng, lat: coordinate.lat }); } private mapOrderDetail(data: XiaofeixiaOrderDetail): ShipmentDetail { return { trackingNumber: data.number, toCarrierName: data.toCarrierName, toSiteName: data.toSiteName, toName: data.toName, toMobile: data.toMobile, toAddress: data.toAddress, toAddressDetail: data.toAddressDetail, fromCarrierName: data.fromCarrierName, fromSiteName: data.fromSiteName, fromName: data.fromName, fromMobile: data.fromMobile, fromAddress: data.fromAddress, fromAddressDetail: data.fromAddressDetail, weight: data.weight, payModeName: data.payModeName, freightPrice: data.freightPrice, insuredPrice: data.insuredPrice, collectionPrice: data.collectionPrice, sumPrice: data.sumPrice, goodsName: data.goodsName, remark: data.remark, }; } }