Skip to content
This repository has been archived by the owner on Feb 6, 2024. It is now read-only.

Commit

Permalink
feat: orders api
Browse files Browse the repository at this point in the history
  • Loading branch information
gigobyte committed May 8, 2020
1 parent 817f097 commit d340e9e
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 3 deletions.
7 changes: 7 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ export class ParsingError extends MWSError {
Object.setPrototypeOf(this, ParsingError.prototype)
}
}

export class BadParameterError extends MWSError {
constructor(public error: string, ...parameters: string[]) {
super(...parameters)
Object.setPrototypeOf(this, ParsingError.prototype)
}
}
/* eslint-enable max-classes-per-file */
36 changes: 33 additions & 3 deletions src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,26 @@ export interface MWSOptions {
}

type HttpMethod = 'GET' | 'POST'
type Parameters = Record<string, string | string[]>
type Parameters = Record<string, string | number | (number | string)[] | undefined>
type CleanParameters = Record<string, string>

export enum Resource {
Sellers = 'Sellers',
Orders = 'Orders',
}

interface ResourceActions {
[Resource.Sellers]:
| 'ListMarketplaceParticipations'
| 'ListMarketplaceParticipationsByNextToken'
| 'GetServiceStatus'
[Resource.Orders]:
| 'ListOrders'
| 'ListOrdersByNextToken'
| 'GetOrder'
| 'ListOrderItems'
| 'ListOrderItemsByNextToken'
| 'GetServiceStatus'
}

interface Request {
Expand Down Expand Up @@ -55,12 +64,33 @@ interface RequestResponse {
headers: Record<string, string>
}

const canonicalizeParameters = (parameters: Parameters): string => {
const canonicalizeParameters = (parameters: CleanParameters): string => {
const sp = new URLSearchParams(parameters)
sp.sort()
return sp.toString().replace(/\+/g, '%20')
}

const cleanParameters = (parameters: Parameters): CleanParameters => {
const result: CleanParameters = {}

// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in parameters) {
const value = parameters[key]

if (value !== undefined) {
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
result[`${key}.${index + 1}`] = String(value)
}
} else {
result[key] = String(value)
}
}
}

return result
}

const defaultFetch = ({ url, method, headers, data }: Request): Promise<RequestResponse> =>
axios({ method, url, headers, data }).then((response) => ({
data: response.data,
Expand Down Expand Up @@ -106,7 +136,7 @@ export class HttpClient {
SignatureVersion: '2',
Timestamp: new Date().toISOString(),
Version: info.version,
...info.parameters,
...cleanParameters(info.parameters),
}

const parametersForSigning = canonicalizeParameters(parameters)
Expand Down
212 changes: 212 additions & 0 deletions src/sections/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { date } from 'purify-ts'

import { BadParameterError } from '../error'
import { HttpClient, Resource } from '../http'

const ORDERS_API_VERSION = '2013-09-01'

export enum OrderStatus {
PendingAvailability = 'PendingAvailability',
Pending = 'Pending',
Unshipped = 'Unshipped',
PartiallyShipped = 'PartiallyShipped',
Shipped = 'Shipped',
Canceled = 'Canceled',
Unfulfillable = 'Unfulfillable',
}

export enum FulfillmentChannel {
AFN = 'AFN',
MFN = 'MFN',
}

export enum PaymentMethod {
COD = 'COD',
CVS = 'CVS',
Other = 'Other',
}

export enum EasyShipShipmentStatus {
PendingPickUp = 'PendingPickUp',
LabelCanceled = 'LabelCanceled',
PickedUp = 'PickedUp',
OutForDelivery = 'OutForDelivery',
Damaged = 'Damaged',
Delivered = 'Delivered',
RejectedByBuyer = 'RejectedByBuyer',
Undeliverable = 'Undeliverable',
ReturnedToSeller = 'ReturnedToSeller',
ReturningToSller = 'ReturningToSller',
Lost = 'Lost',
}

interface ListOrderParameters {
createdAfter?: string
createdBefore?: string
lastUpdatedAfter?: string
lastUpdatedBefore?: string
orderStatuses?: OrderStatus[]
marketplaceIds: string[]
fulfillmentChannels?: FulfillmentChannel[]
paymentMethods?: PaymentMethod[]
buyerEmail?: string
sellerOrderId?: string
maxResultsPerPage?: number
easyShipShipmentStatuses?: EasyShipShipmentStatus[]
}

const validateCreatedAfter = (parameters: ListOrderParameters): void => {
if (!parameters.createdAfter && !parameters.lastUpdatedAfter) {
throw new BadParameterError(
'Parameter "createdAfter" is required if "lastUpdatedAfter" is not specified.',
)
}

if (parameters.createdAfter && parameters.lastUpdatedAfter) {
throw new BadParameterError(
'Specifying both "createdAfter" and "lastUpdatedAfter" is not allowed.',
)
}

if (parameters.createdAfter && date.decode(parameters.createdAfter).isLeft()) {
throw new BadParameterError('"createdAfter" must be in ISO 8601 date time format.')
}
}

const validateCreatedBefore = (parameters: ListOrderParameters): void => {
if (parameters.createdBefore && date.decode(parameters.createdBefore).isLeft()) {
throw new BadParameterError('"createdBefore" must be in ISO 8601 date time format.')
}
}

const validateLastUpdatedAfter = (parameters: ListOrderParameters): void => {
if (!parameters.lastUpdatedAfter && !parameters.createdAfter) {
throw new BadParameterError(
'Parameter "lastUpdatedAfter" is required if "createdAfter" is not specified.',
)
}

if (parameters.lastUpdatedAfter && (parameters.buyerEmail || parameters.sellerOrderId)) {
throw new BadParameterError(
'If "lastUpdatedAfter" is specified, then "buyerEmail" and "sellerOrderId" cannot be specified.',
)
}

if (parameters.lastUpdatedAfter && date.decode(parameters.lastUpdatedAfter).isLeft()) {
throw new BadParameterError('"lastUpdatedAfter" must be in ISO 8601 date time format.')
}
}

const validateLastUpdatedBefore = (parameters: ListOrderParameters): void => {
if (parameters.lastUpdatedBefore && date.decode(parameters.lastUpdatedBefore).isLeft()) {
throw new BadParameterError('"lastUpdatedBefore" must be in ISO 8601 date time format.')
}
}

const validateBuyerEmail = (parameters: ListOrderParameters): void => {
if (
parameters.buyerEmail &&
(parameters.fulfillmentChannels ||
parameters.orderStatuses ||
parameters.paymentMethods ||
parameters.lastUpdatedAfter ||
parameters.lastUpdatedBefore ||
parameters.sellerOrderId)
) {
throw new BadParameterError(
'If "buyerEmail" is specified, then "fulfillmentChannels", "orderStatuses", "paymentMethods", "lastUpdatedAfter", "lastUpdatedBefore", and "sellerOrderId" cannot be specified.',
)
}
}

const validateSellerOrderId = (parameters: ListOrderParameters): void => {
if (
parameters.sellerOrderId &&
(parameters.fulfillmentChannels ||
parameters.orderStatuses ||
parameters.paymentMethods ||
parameters.lastUpdatedAfter ||
parameters.lastUpdatedBefore ||
parameters.buyerEmail)
) {
throw new BadParameterError(
'If "sellerOrderId" is specified, then "fulfillmentChannels", "orderStatuses", "paymentMethods", "lastUpdatedAfter", "lastUpdatedBefore", and "buyerEmail" cannot be specified.',
)
}
}

const validateOrderStatuses = (parameters: ListOrderParameters): void => {
if (parameters.orderStatuses) {
let foundUnshipped = false
let foundPartiallyShipped = false

for (let i = 0; i < parameters.orderStatuses.length; i += 1) {
if (parameters.orderStatuses[i] === OrderStatus.Unshipped) {
foundUnshipped = true
}

if (parameters.orderStatuses[i] === OrderStatus.PartiallyShipped) {
foundPartiallyShipped = true
}
}

if ((foundUnshipped && !foundPartiallyShipped) || (foundPartiallyShipped && !foundUnshipped)) {
throw new BadParameterError(
'"orderStatuses": Unshipped and PartiallyShipped must be used together. Using one and not the other is not valid.',
)
}
}
}

const validateMarketplaceIds = (parameters: ListOrderParameters): void => {
if (parameters.marketplaceIds && parameters.marketplaceIds.length > 50) {
throw new BadParameterError('"marketplaceIds": A maximum of 50 ids is allowed.')
}
}

const validateMaxResultsPerPage = (parameters: ListOrderParameters): void => {
if (
parameters.maxResultsPerPage !== undefined &&
(parameters.maxResultsPerPage < 1 || parameters.maxResultsPerPage > 100)
) {
throw new BadParameterError('"maxResultsPerPage": Value must be 1 - 100.')
}
}

export class Orders {
constructor(private httpClient: HttpClient) {}

async listOrders(parameters: ListOrderParameters) {
validateCreatedAfter(parameters)
validateCreatedBefore(parameters)
validateLastUpdatedAfter(parameters)
validateLastUpdatedBefore(parameters)
validateOrderStatuses(parameters)
validateMarketplaceIds(parameters)
validateBuyerEmail(parameters)
validateSellerOrderId(parameters)
validateMaxResultsPerPage(parameters)

const [response, meta] = await this.httpClient.request('POST', {
resource: Resource.Orders,
version: ORDERS_API_VERSION,
action: 'ListOrders',
parameters: {
CreatedAfter: parameters.createdAfter,
CreatedBefore: parameters.createdBefore,
LastUpdatedAfter: parameters.lastUpdatedAfter,
LastUpdatedBefore: parameters.lastUpdatedBefore,
'OrderStatus.Status': parameters.orderStatuses,
'MarketplaceId.Id': parameters.marketplaceIds,
'FulfillmentChannel.Channel': parameters.fulfillmentChannels,
'PaymentMethod.Method': parameters.paymentMethods,
'EasyShipShipmentStatus.Status': parameters.easyShipShipmentStatuses,
BuyerEmail: parameters.buyerEmail,
SellerOrderId: parameters.sellerOrderId,
MaxResultsPerPage: parameters.maxResultsPerPage,
},
})

return [response, meta]
}
}

0 comments on commit d340e9e

Please sign in to comment.