코드리뷰 전

type AuthSuccessResponse = {
  data: {
    token: string;
    validDate: string;
  };
  result: '00';
  message: string;
};

type AuthFailureResponse = {
  result: '99';
  message: string;
};

type AuthResponse = AuthSuccessResponse | AuthFailureResponse;

type ApiPayload = {
  [key: string]: any;
};

type RequestType = 'text/xml' | 'application/json' | 'x-www-form-urlencoded';

class CosmaxService {
  private authToken: string | null = '0ti6MDBcompanyx9jVB6LmJeusg=='; 
  private readonly BASE_URL: string = '<https://cosmaxapi.mobydicksoft.com>';

  private async validateToken(): Promise<void> {
    const params = new URLSearchParams();
    params.append('token', this.authToken || '');
    params.append('dm_id', '5');

    const response = await fetch(`${this.BASE_URL}/auth/auth_key.jsp`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params,
    });

    const data: AuthResponse = await response.json();

    if (data.result === '99') {
      console.log('기존 토큰이 유효하지 않음. 새로운 토큰 발급 중...');
      await this.createAuthKey();
      return;
    }

    this.authToken = data.data.token;
  }

  private async createAuthKey(): Promise<void> {
    const params = new URLSearchParams();
    params.append('dm_id', '5');
    params.append('key', this.authToken);

    const response = await fetch(`${this.BASE_URL}/auth/create_auth_key.jsp`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params,
    });

    const data: AuthResponse = await response.json();

    if (data.result === '99') {
      throw new Error(data.message);
    }

    this.authToken = data.data.token;
  }

  // 유효한 토큰 확인 함수
  private async ensureValidToken(): Promise<void> {
    await this.validateToken();
  }

  // API 요청 처리 함수
  public async makeApiRequest<T>(
    endpoint: string,
    payload: ApiPayload,
    requestType: RequestType = 'application/json',
  ): Promise<T> {
    await this.ensureValidToken();

    let body: string;
    const headers: HeadersInit = {};

    // 각 요청에서 token을 파라미터로 추가
    payload.token = this.authToken;

    // 요청 타입에 따라 처리
    if (requestType === 'x-www-form-urlencoded') {
      headers['Content-Type'] = 'application/x-www-form-urlencoded';
      const params = new URLSearchParams();
      Object.keys(payload).forEach((key) => params.append(key, payload[key]));
      body = params.toString();
    } else if (requestType === 'application/json') {
      headers['Content-Type'] = 'application/json';
      body = JSON.stringify(payload);
    } else if (requestType === 'text/xml') {
      headers['Content-Type'] = 'text/xml';
      body = payload as string; // XML 요청은 미리 문자열로 준비되어 있어야 함
    } else {
      throw new Error('Unsupported request type');
    }

    const response = await fetch(`${this.BASE_URL}${endpoint}`, {
      method: 'POST',
      headers,
      body,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Error ${response.status}: ${errorText}`);
    }

    const data: T = await response.json();
    return data;
  }
}

const cosmaxService = new CosmaxService();
export default cosmaxService;

// 사용예시
type ApiPayload = {
  token: string;
  dm_id: number;
  ord_id: string;
  ord_data: Array<{ prd_num: number; amt: number; sale_price: number; m_time: string }>;
};

async function modifyOrder() {
  const payload: ApiPayload = {
    token: 'token',
    dm_id: 5, 
    ord_id: 'ord_id', // 주문번호
    ord_data: [
      { prd_num: 1, amt: 1, sale_price: 5000, m_time: '1000' },
      { prd_num: 2, amt: 2, sale_price: 5000, m_time: '0101' },
      { prd_num: 3, amt: 1, sale_price: 5000, m_time: '0001' },
    ],
  };

  try {
    const response = await cosmaxService.makeApiRequest<{ ord_id: string; result: string; message: string }>(
      '/company/order_prd_modify.jsp',
      payload,
      'application/json', // Request type을 'application/json'으로 설정
    );

    console.log('수정된 주문 결과:', response);
  } catch (error) {
    console.error('주문 정보 수정 실패:', error);
  }
}

type AuthSuccessResponse = {
  data: {
    token: string;
    validDate: string;
  };
  result: '00';
  message: string;
};

type AuthFailureResponse = {
  result: '99';
  message: string;
};

type AuthResponse = AuthSuccessResponse | AuthFailureResponse;

type ApiPayload = {
  [key: string]: any;
};

type RequestType = 'text/xml' | 'application/json' | 'x-www-form-urlencoded';

class CosmaxService {
  private authToken: string | null = '0ti6MDBcompanyx9jVB6LmJeusg==';
  private readonly BASE_URL: string = '<https://cosmaxapi.mobydicksoft.com>';

  // 계정 관련 정보들을 변수화
  private mem_id: string;
  private dm_id: number;
  private str_id: number;

  constructor(mem_id: string, dm_id: number, str_id: number) {
    this.mem_id = mem_id;
    this.dm_id = dm_id;
    this.str_id = str_id;
  }

  private async validateToken(): Promise<void> {
    const params = new URLSearchParams();
    params.append('token', this.authToken || '');
    params.append('dm_id', '5');

    const response = await fetch(`${this.BASE_URL}/auth/auth_key.jsp`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params,
    });

    const data: AuthResponse = await response.json();

    if (data.result === '99') {
      console.log('기존 토큰이 유효하지 않음. 새로운 토큰 발급 중...');
      await this.createAuthKey();
      return;
    }

    this.authToken = data.data.token;
  }

  private async createAuthKey(): Promise<void> {
    const params = new URLSearchParams();
    params.append('dm_id', this.dm_id.toString());
    params.append('key', this.authToken);

    const response = await fetch(`${this.BASE_URL}/auth/create_auth_key.jsp`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params,
    });

    const data: AuthResponse = await response.json();

    if (data.result === '99') {
      throw new Error(data.message);
    }

    this.authToken = data.data.token;
  }

  private async ensureValidToken(): Promise<void> {
    await this.validateToken();
  }

  // API 요청 처리 함수
  private async makeApiRequest<T>(
    endpoint: string,
    payload: ApiPayload,
    requestType: RequestType = 'application/json',
  ): Promise<T> {
    await this.ensureValidToken();

    let body: string;
    const headers: HeadersInit = {};

    payload.token = this.authToken;
    payload.dm_id = this.dm_id;

    // 요청 타입에 따라 처리
    if (requestType === 'x-www-form-urlencoded') {
      headers['Content-Type'] = 'application/x-www-form-urlencoded';
      const params = new URLSearchParams();
      Object.keys(payload).forEach((key) => params.append(key, payload[key]));
      body = params.toString();
    } else if (requestType === 'application/json') {
      headers['Content-Type'] = 'application/json';
      body = JSON.stringify(payload);
    } else if (requestType === 'text/xml') {
      headers['Content-Type'] = 'text/xml';
      body = payload as string; // XML 요청은 미리 문자열로 준비되어 있어야 함
    } else {
      throw new Error('Unsupported request type');
    }

    const response = await fetch(`${this.BASE_URL}${endpoint}`, {
      method: 'POST',
      headers,
      body,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Error ${response.status}: ${errorText}`);
    }

    const data: T = await response.json();
    return data;
  }

  // 1. 상품 리스트 조회
  public async getItemList() {
    const endpoint = '/company/item_list.jsp';
    const payload = { dm_id: this.dm_id };
    return this.makeApiRequest(endpoint, payload, 'x-www-form-urlencoded');
  }

  // 2. 상품 정보 조회
  public async getItemInfo(itemId: string) {
    const endpoint = '/company/item_info.jsp';
    const payload = { dm_id: this.dm_id, item_id: itemId };
    return this.makeApiRequest(endpoint, payload, 'x-www-form-urlencoded');
  }

  // 4. 상품 주문
  public async orderProduct(
    orderId: string,
    orderData: Array<{ prd_num: number; amt: number; sale_price: number; m_time: string }>,
    customerInfo: { name: string; phone: string; address: string },
  ) {
    const endpoint = 'company/order.jsp';
    const payload = {
      token: this.authToken,
      dm_id: this.dm_id,
      ord_id: orderId,
      ord_data: orderData, // 주문 데이터 (JSON 형식)
      customer_info: customerInfo, // 고객 정보 (JSON 형식)
    };

    return this.makeApiRequest(endpoint, payload, 'x-www-form-urlendcoded');
  }
  // 5.주문 내역 조회
  public async getOrderHistory(orderId: string) {
    const endpoint = '/company/order_list.jsp';
    const payload = { dm_id: this.dm_id, ord_id: orderId };
    return this.makeApiRequest(endpoint, payload, 'x-www-form-urlencoded');
  }

  // 6.주문자 정보 수정
  public async modifyCustomerInfo(orderId: string, customerData: any) {
    const endpoint = '/company/customer_modify.jsp';
    const payload = {
      dm_id: this.dm_id,
      ord_id: orderId,
      ...customerData,
    };
    return this.makeApiRequest(endpoint, payload, 'x-www-form-urlencoded');
  }

  // 7. 주문 정보 수정
  public async modifyOrder(
    orderId: string,
    orderData: Array<{ prd_num: number; amt: number; sale_price: number; m_time: string }>,
  ) {
    const endpoint = '/company/order_prd_modify.jsp';
    const payload = {
      token: this.authToken,
      dm_id: this.dm_id,
      ord_id: orderId,
      ord_data: orderData,
    };

    return this.makeApiRequest(endpoint, payload, 'application/json');
  }

  // 8. 주문취소
  public async cancelOrder(orderId: string) {
    const endpoint = '/company/order_cancel.jsp';
    const payload = { dm_id: this.dm_id, ord_id: orderId };
    return this.makeApiRequest(endpoint, payload, 'application/json');
  }

  // 9. 배송상태조회
  public async checkDeliveryStatus(orderId: string) {
    const endpoint = '/company/dlvy_status.jsp';
    const payload = { dm_id: this.dm_id, ord_id: orderId };
    return this.makeApiRequest(endpoint, payload, 'application/json');
  }

  // 10. 기간별 발주내역 조회
  public async getOrderHistoryByPeriod(startDate: string, endDate: string) {
    const endpoint = '/orderlist_by_period.jsp';
    const payload = {
      dm_id: this.dm_id,
      sdate: startDate, // 조회 시작일
      edate: endDate, // 조회 종료일
    };
    return this.makeApiRequest(endpoint, payload, 'application/json');
  }

  // 11. 배송사조회
  public async getDeliveryCompanies() {
    const endpoint = '/company/dlvy_corp_list.jsp';
    const payload = { dm_id: this.dm_id };
    return this.makeApiRequest(endpoint, payload, 'application/json');
  }
}

const cosmaxService = new CosmaxService('418', 5, 27);
export default cosmaxService;

import * as z from 'zod';

// 각 서비스의 입력/출력 스키마 정의
export const CosmaxServicesResults = {
  // 1. 상품 리스트 조회
  getItemList: {
    name: '/company/item_list.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number().int(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
      data: z.array(
        z.object({
          cit_id: z.number(),
          cit_name: z.string(),
          cit_summary: z.string(),
          cit_market_price: z.number(),
          cit_supply_price: z.number(),
          cit_file_1: z.string(),
          cit_updated_datetime: z.string(),
          cit_unit: z.string(),
          cit_unit_per_count: z.number(),
          cit_unit_per_day_count: z.number(),
        })
      ),
    }),
  },

  // 2. 상품 정보 조회
  getItemInfo: {
    name: '/company/item_info.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      cit_id: z.number(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
      data: z.object({
        cit_id: z.number(),
        cit_name: z.string(),
        cit_summary: z.string(),
        cit_market_price: z.number(),
        cit_supply_price: z.number(),
        cit_file_1: z.string(),
        cit_updated_datetime: z.string(),
        cit_unit: z.string(),
        cit_unit_per_count: z.number(),
        cit_unit_per_day_count: z.number(),
      }),
    }),
  },

  // 4. 상품 주문
  orderProduct: {
    name: '/company/order.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      mem_id: z.number(),
      str_id: z.number(),
      mem_name: z.string(),
      mem_addr1: z.string(),
      mem_addr2: z.string(),
      mem_postno: z.string(),
      mem_phone: z.string(),
      mem_email: z.string(),
      mem_memo: z.string().optional(),
      oc_ord_id: z.string(),
      ppiu: z.enum(['Y', 'N']),
      ord_data: z.array(
        z.object({
          prd_num: z.number(),
          amt: z.number(),
          sale_price: z.number(),
          m_time: z.string().min(4).max(4),
        })
      ),
    }),
    outputParams: z.object({
      ord_id: z.string(),
      result: z.enum(['00', '99']),
      message: z.string(),
    }),
  },

  // 5. 주문 내역 조회
  getOrderHistory: {
    name: '/company/order_list.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      ord_id: z.string(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
      order_header: z.object({
        mem_id: z.number(),
        str_id: z.number(),
        name: z.string(),
        addr1: z.string(),
        addr2: z.string().optional(),
        email: z.string(),
        phone: z.string(),
        memo: z.string().optional(),
        amount: z.string(),
      }),
      order_detail_list: z.array(
        z.object({
          cod_id: z.number(),
          cit_id: z.number(),
          cod_title: z.string(),
          cit_price: z.number(),
          cod_count: z.number(),
          cod_unit: z.string(),
          cod_unit_per_count: z.number(),
          cod_m_time: z.string(),
        })
      ),
    }),
  },

  // 6. 주문자 정보 수정
  modifyCustomerInfo: {
    name: '/company/customer_modify.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      ord_id: z.string(),
      mem_name: z.string().optional(),
      mem_addr1: z.string().optional(),
      mem_addr2: z.string().optional(),
      mem_postno: z.string().optional(),
      mem_phone: z.string().optional(),
      mem_email: z.string().optional(),
      mem_memo: z.string().optional(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
    }),
  },

  // 7. 주문 정보 수정
  modifyOrder: {
    name: '/company/order_prd_modify.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      ord_id: z.string(),
      ord_data: z.array(
        z.object({
          prd_num: z.number(),
          amt: z.number(),
          sale_price: z.number(),
          m_time: z.string(),
        })
      ),
    }),
    outputParams: z.object({
      ord_id: z.string(),
      result: z.enum(['00', '99']),
      message: z.string(),
    }),
  },

  // 8. 주문 취소
  cancelOrder: {
    name: '/company/order_cancel.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      ord_id: z.string(),
    }),
    outputParams: z.object({
      ord_id: z.string(),
      result: z.enum(['00', '99']),
      message: z.string(),
    }),
  },

  // 9. 배송 상태 조회
  checkDeliveryStatus: {
    name: '/company/dlvy_status.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      ord_id: z.string(),
    }),
    outputParams: z.object({
      data: z.object({
        dlvy_status: z.number(),
        dlvy_corp: z.string(),
        dlvy_invoice: z.string(),
        dlvy_traceUrl: z.string().url(),
      }),
      result: z.enum(['00', '99']),
      message: z.string(),
    }),
  },

  // 10. 기간별 발주 내역 조회
  getOrderHistoryByPeriod: {
    name: '/company/orderlist_by_period.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
      sdate: z.string(),
      edate: z.string(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
      data: z.array(
        z.object({
          ord_id: z.string(),
          date: z.string(),
          name: z.string(),
          phone: z.string(),
          addr1: z.string(),
          addr2: z.string().optional(),
          sum: z.number(),
          dlvy_status: z.number(),
          dlvy_corp: z.string(),
          invoice: z.string(),
        })
      ),
    }),
  },

  // 11. 배송사 조회
  getDeliveryCompanies: {
    name: '/company/dlvy_corp_list.jsp',
    inputParams: z.object({
      token: z.string(),
      dm_id: z.number(),
    }),
    outputParams: z.object({
      result: z.enum(['00', '99']),
      message: z.string(),
      data: z.array(
        z.object({
          dlvy_id: z.number(),
          dlvy_name: z.string(),
          dlvy_track_url: z.string(),
          dlvy_is_use: z.enum(['Y', 'N']),
        })
      ),
    }),
  },
};