Initial commit of connector_walmart for Odoo 11.0 (using beta version of connector_ecommerce)

This commit is contained in:
Jared Kipe
2018-07-07 12:43:35 -07:00
parent b7096b5187
commit cfd25c425b
34 changed files with 2518 additions and 2 deletions

View File

@@ -0,0 +1,407 @@
# -*- coding: utf-8 -*-
# BSD License
#
# Copyright (c) 2016, Fulfil.IO Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# * Neither the name of Fulfil nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# © 2017 Hibou Corp. - Extended and converted to v3/JSON
import requests
import base64
import time
from uuid import uuid4
# from lxml import etree
# from lxml.builder import E, ElementMaker
from json import dumps, loads
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
class Walmart(object):
def __init__(self, consumer_id, channel_type, private_key):
self.base_url = 'https://marketplace.walmartapis.com/v3/%s'
self.consumer_id = consumer_id
self.channel_type = channel_type
self.private_key = private_key
self.session = requests.Session()
self.session.headers['Accept'] = 'application/json'
self.session.headers['WM_SVC.NAME'] = 'Walmart Marketplace'
self.session.headers['WM_CONSUMER.ID'] = self.consumer_id
self.session.headers['WM_CONSUMER.CHANNEL.TYPE'] = self.channel_type
@property
def items(self):
return Items(connection=self)
@property
def inventory(self):
return Inventory(connection=self)
@property
def prices(self):
return Prices(connection=self)
@property
def orders(self):
return Orders(connection=self)
def get_sign(self, url, method, timestamp):
return self.sign_data(
'\n'.join([self.consumer_id, url, method, timestamp]) + '\n'
)
def sign_data(self, data):
rsakey = RSA.importKey(base64.b64decode(self.private_key))
signer = PKCS1_v1_5.new(rsakey)
digest = SHA256.new()
digest.update(data.encode('utf-8'))
sign = signer.sign(digest)
return base64.b64encode(sign)
def get_headers(self, url, method):
timestamp = str(int(round(time.time() * 1000)))
headers = {
'WM_SEC.AUTH_SIGNATURE': self.get_sign(url, method, timestamp),
'WM_SEC.TIMESTAMP': timestamp,
'WM_QOS.CORRELATION_ID': str(uuid4()),
}
if method in ('POST', ):
headers['Content-Type'] = 'application/json'
return headers
def send_request(self, method, url, params=None, body=None):
encoded_url = url
if params:
encoded_url += '?%s' % urlencode(params)
headers = self.get_headers(encoded_url, method)
if method == 'GET':
return loads(self.session.get(url, params=params, headers=headers).text)
elif method == 'PUT':
return loads(self.session.put(url, params=params, headers=headers).text)
elif method == 'POST':
return loads(self.session.post(url, data=body, headers=headers).text)
class Resource(object):
"""
A base class for all Resources to extend
"""
def __init__(self, connection):
self.connection = connection
@property
def url(self):
return self.connection.base_url % self.path
def all(self, **kwargs):
return self.connection.send_request(
method='GET', url=self.url, params=kwargs)
def get(self, id):
url = self.url + '/%s' % id
return self.connection.send_request(method='GET', url=url)
def update(self, **kwargs):
return self.connection.send_request(
method='PUT', url=self.url, params=kwargs)
# def bulk_update(self, items):
# url = self.connection.base_url % 'feeds?feedType=%s' % self.feedType
# return self.connection.send_request(
# method='POST', url=url, data=self.get_payload(items))
class Items(Resource):
"""
Get all items
"""
path = 'items'
class Inventory(Resource):
"""
Retreives inventory of an item
"""
path = 'inventory'
feedType = 'inventory'
def get_payload(self, items):
return etree.tostring(
E.InventoryFeed(
E.InventoryHeader(E('version', '1.4')),
*[E(
'inventory',
E('sku', item['sku']),
E(
'quantity',
E('unit', 'EACH'),
E('amount', item['quantity']),
)
) for item in items],
xmlns='http://walmart.com/'
)
)
class Prices(Resource):
"""
Retreives price of an item
"""
path = 'prices'
feedType = 'price'
def get_payload(self, items):
# root = ElementMaker(
# nsmap={'gmp': 'http://walmart.com/'}
# )
# return etree.tostring(
# root.PriceFeed(
# E.PriceHeader(E('version', '1.5')),
# *[E.Price(
# E(
# 'itemIdentifier',
# E('sku', item['sku'])
# ),
# E(
# 'pricingList',
# E(
# 'pricing',
# E(
# 'currentPrice',
# E(
# 'value',
# **{
# 'currency': item['currenctCurrency'],
# 'amount': item['currenctPrice']
# }
# )
# ),
# E('currentPriceType', item['priceType']),
# E(
# 'comparisonPrice',
# E(
# 'value',
# **{
# 'currency': item['comparisonCurrency'],
# 'amount': item['comparisonPrice']
# }
# )
# ),
# E(
# 'priceDisplayCode',
# **{
# 'submapType': item['displayCode']
# }
# ),
# )
# )
# ) for item in items]
# ), xml_declaration=True, encoding='utf-8'
# )
payload = {}
return
class Orders(Resource):
"""
Retrieves Order details
"""
path = 'orders'
def all(self, **kwargs):
next_cursor = kwargs.pop('nextCursor', '')
return self.connection.send_request(
method='GET', url=self.url + next_cursor, params=kwargs)
def released(self, **kwargs):
next_cursor = kwargs.pop('nextCursor', '')
url = self.url + '/released'
return self.connection.send_request(
method='GET', url=url + next_cursor, params=kwargs)
def acknowledge(self, id):
url = self.url + '/%s/acknowledge' % id
return self.connection.send_request(method='POST', url=url)
def cancel(self, id, lines):
url = self.url + '/%s/cancel' % id
return self.connection.send_request(
method='POST', url=url, body=self.get_cancel_payload(lines))
def get_cancel_payload(self, lines):
"""
{
"orderCancellation": {
"orderLines": {
"orderLine": [
{
"lineNumber": "1",
"orderLineStatuses": {
"orderLineStatus": [
{
"status": "Cancelled",
"cancellationReason": "CUSTOMER_REQUESTED_SELLER_TO_CANCEL",
"statusQuantity": {
"unitOfMeasurement": "EA",
"amount": "1"
}
}
]
}
}
]
}
}
}
:param lines:
:return: string
"""
payload = {
'orderCancellation': {
'orderLines': [{
'lineNumber': line['number'],
'orderLineStatuses': {
'orderLineStatus': [
{
'status': 'Cancelled',
'cancellationReason': 'CUSTOMER_REQUESTED_SELLER_TO_CANCEL',
'statusQuantity': {
'unitOfMeasurement': 'EA',
'amount': line['amount'],
}
}
]
}
} for line in lines]
}
}
return dumps(payload)
def ship(self, id, lines):
url = self.url + '/%s/shipping' % id
return self.connection.send_request(
method='POST',
url=url,
body=self.get_ship_payload(lines)
)
def get_ship_payload(self, lines):
"""
:param lines: list[ dict(number, amount, carrier, methodCode, trackingNumber, trackingUrl) ]
:return:
"""
"""
{
"orderShipment": {
"orderLines": {
"orderLine": [
{
"lineNumber": "1",
"orderLineStatuses": {
"orderLineStatus": [
{
"status": "Shipped",
"statusQuantity": {
"unitOfMeasurement": "EA",
"amount": "1"
},
"trackingInfo": {
"shipDateTime": 1488480443000,
"carrierName": {
"otherCarrier": null,
"carrier": "UPS"
},
"methodCode": "Express",
"trackingNumber": "12345",
"trackingURL": "www.fedex.com"
}
}
]
}
}
]
}
}
}
:param lines:
:return:
"""
payload = {
"orderShipment": {
"orderLines": {
"orderLine": [
{
"lineNumber": str(line['number']),
"orderLineStatuses": {
"orderLineStatus": [
{
"status": "Shipped",
"statusQuantity": {
"unitOfMeasurement": "EA",
"amount": str(line['amount'])
},
"trackingInfo": {
"shipDateTime": line['shipDateTime'],
"carrierName": {
"otherCarrier": None,
"carrier": line['carrier']
},
"methodCode": line['methodCode'],
"trackingNumber": line['trackingNumber'],
"trackingURL": line['trackingUrl']
}
}
]
}
}
for line in lines]
}
}
}
return dumps(payload)