whitebox-co / walmart-marketplace-api

A fully typed TypeScript, Javascript, and Node.js API library for the Walmart Marketplace API
MIT License
24 stars 10 forks source link

Canada support #83

Open mendeljacks opened 2 years ago

mendeljacks commented 2 years ago

When using the walmart canada marketplace api, the routes need to be modified to include the 'ca' part. eg /v3/ca/feeds instead of /v3/feeds.

Options I considered:

  1. Is there a mechanism to re-run the generator for the ca api?
  2. or perhaps adding the ability to override the url as an optional parameter?

I would prefer option 1 since the apis are not exactly the same (eg no multi-node shipping support in canada)

ps. I vibe with the philosophy of this package, thanks for the great work

josh-fisher commented 2 years ago

Hi @mendeljacks - Interesting issue that I had not even anticipated. Our Market is us so I did not even consider any other market. Unfortunately, It looks like the routes are not the only difference.

As you pointed out the APIs are different. In fact, they have different schemas altogether. If you look at the utils/schema-downloader.ts file you will see that country is an input param to the apiDetailUrl.

Additionally, it looks like each market has different headers (See table below) and Canada has a completely different Authentication mechanism where some java jar file has to be run to sign each call.

Header us ca mx
Authorization X   X
WM_SEC.ACCESS_TOKEN X   X
WM_CONSUMER.CHANNEL.TYPE X X X
WM_QOS.CORRELATION_ID X X X
WM_SVC.NAME X X X
WM_CONSUMER.ID   X  
WM_SEC.AUTH_SIGNATURE   X  
WM_SEC.TIMESTAMP   X  
WM_TENANT_ID   X  
WM_LOCALE_ID   X X
WM_MARKET     X

Mexico would be an easier market to implement currently as it uses the same authentication and authorization schemas, with relatively small header additions, but Canada is a completely different story.

I'm not sure how quickly we will get around to implementing Canada as it is certainly not a priority at the moment, but I will definitely make a branch and explore some ideas.

mendeljacks commented 2 years ago

I figured out how to authenticate with only using nodejs and the built in crypto library: (to run this you will need the three walmart environment variables)

import axios from 'axios'
import { createSign, randomBytes } from 'crypto'

const PK_HEADER = '\n-----BEGIN PRIVATE KEY-----\n'
const PK_FOOTER = '\n-----END PRIVATE KEY-----\n'

const BASE_URL = 'https://marketplace.walmartapis.com'
const WALMART_CONSUMER = '***'
const WALMART_SECRET = '***'
const WALMART_CHANNEL = '***'

const generateCorrelationId = () => {
    return randomBytes(16).toString('hex')
}

const generateSignature = (url, method, timestamp) => {
    const privateKey = `${PK_HEADER}${WALMART_SECRET}${PK_FOOTER}`

    const stringToSign =
        WALMART_CONSUMER + '\n' + url + '\n' + method.toUpperCase() + '\n' + timestamp + '\n'

    const sign = createSign('RSA-SHA256')

    sign.update(stringToSign)

    return sign.sign(privateKey, 'base64')
}

const doRequest = async (endpoint, method, body = {}) => {
    const url = BASE_URL + endpoint
    const timestamp = Date.now()
    const signature = generateSignature(url, method, timestamp)

    const headers = {
        'WM_SVC.NAME': 'Walmart Gateway API',
        'WM_CONSUMER.ID': WALMART_CONSUMER,
        'WM_SEC.TIMESTAMP': timestamp,
        'WM_SEC.AUTH_SIGNATURE': signature,
        'WM_QOS.CORRELATION_ID': generateCorrelationId(),
        'WM_CONSUMER.CHANNEL.TYPE': WALMART_CHANNEL,
        ...(method === 'post' ? { accept: 'application/json' } : {})
    }

    const response = await axios({
        method: method,
        url: url,
        data: body,
        headers: headers
    }).catch(err => {
        console.log(err?.response?.data)
        debugger
    })

    debugger
}

// Get a specific sku
doRequest('/v3/ca/items/2298', 'get')

// Update inventory
const sku = '2298'
const body = JSON.stringify({
    InventoryHeader: {
        version: '1.4'
    },
    Inventory: [
        {
            sku: 'test1',
            quantity: {
                unit: 'EACH',
                amount: 10
            }
        },
        {
            sku: '894728',
            quantity: {
                unit: 'EACH',
                amount: 20
            }
        }
    ]
})
doRequest(`/v3/ca/feeds?feedType=inventory`, 'post', { file: Buffer.from(body) })