Skip to content

Add connector amazon spapi don#1

Open
dnplkndll wants to merge 11 commits intoadd-connector_amazon_spapifrom
add-connector_amazon_spapi-don
Open

Add connector amazon spapi don#1
dnplkndll wants to merge 11 commits intoadd-connector_amazon_spapifrom
add-connector_amazon_spapi-don

Conversation

@dnplkndll
Copy link

No description provided.

dnplkndll and others added 10 commits January 4, 2026 15:42
Add x-amzn-api-sandbox header support when test_mode is enabled.
This triggers Amazon's static sandbox responses for testing API
integration without affecting live data.
…sync

Implement Reports API adapter and bulk catalog sync functionality:
- Add AmazonReportsAdapter with create/get/cancel report methods
- Add sync_catalog_bulk() for initial population of product bindings
- Parse GET_MERCHANT_LISTINGS_ALL_DATA report (TSV format)
- Add "Bulk Catalog Sync (Reports)" button to Shop form
- Add tests for Reports API adapter
- Add SP_API_CAPABILITIES.md documentation

This enables bulk initial sync of product bindings by fetching all
seller listings via Amazon's Reports API instead of single-SKU lookups.
…tifications API scope

Add cron-based catalog sync:
- Add catalog_sync_interval field (manual/daily/weekly)
- Add last_catalog_sync timestamp field
- Add cron_sync_catalog_bulk() method
- Add ir.cron for daily catalog sync via Reports API
- Weekly sync runs on Mondays

Document Notifications API implementation scope:
- Webhook controller with token authentication
- SNS message signature verification
- Backend fields for subscription management
- Notifications adapter API methods
- Available notification types (ORDER_CHANGE, LISTINGS_ITEM_STATUS_CHANGE, etc.)
- Security considerations
- Implementation phases
- Alternative SQS polling approach
…tifications

Add webhook endpoint to receive Amazon SP-API notifications via SNS:

- New webhook controller with SNS signature verification
- Notification log model for tracking all received notifications
- Event handlers for ORDER_CHANGE, LISTINGS_ITEM_STATUS_CHANGE,
  FEED_PROCESSING_FINISHED, and other notification types
- Backend webhook configuration (token, URL, notification type toggles)
- Notifications API adapter for managing SNS subscriptions
- Comprehensive test suite for webhook functionality

This enables near real-time processing of Amazon events, reducing
the need for frequent polling via cron jobs.
@kobros-tech kobros-tech force-pushed the add-connector_amazon_spapi branch 3 times, most recently from 2b4a873 to 44d31aa Compare January 4, 2026 21:43
Copy link
Member

@xaviedoanhduy xaviedoanhduy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello guys, thanks for your work.

  1. Model Relationships and Constraints
    The current architecture is quite flexible and robust, especially with the presence of queues to improve order data entry.

However, as mentioned, we should consider favoring the amazon.product.binding model more, similar to amazon.offer of sale_amazon of ee

amazon.backend
├── amazon.marketplace (1:N)
├── amazon.shop (1:N)
├── amazon.notification.log (1:N)
└── amazon.feed (1:N)

amazon.sale.order (inherits sale.order via delegation)
└── amazon.sale.order.line (inherits sale.order.line)
    └── product_binding_id → amazon.product.binding [NOT USED!]

amazon.product.binding (inherits product.product)
└── amazon.competitive.price (1:N pricing history)
image
  1. No Restricted Data Token (RDT)
    Amazon SP-API requires a Restricted Data Token (RDT) to access Personally Identifiable Information (PII) such as buyer email, name, and shipping address. Without RDT, these fields return empty or redacted values.

Impact: Order import will succeed but partner creation will fail or create incomplete records because ShippingAddress and BuyerInfo objects will be empty.

API Operations Requiring RDT

API Operation When RDT Required
Orders API getOrders When response includes buyer/shipping info
Orders API getOrder When response includes buyer/shipping info
Orders API getOrderItems When response includes buyer info
Orders API getOrderAddress Always (returns shipping address)
Orders API getOrderBuyerInfo Always (returns buyer info)

Comment on lines 138 to 139
marketplace_id=self.marketplace_id.marketplace_id,
payload=payload,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
marketplace_id=self.marketplace_id.marketplace_id,
payload=payload,
marketplace_id=self.marketplace_id.marketplace_id,
payload=payload,

payload and marketplace_id parameters are non-existent in _call_sp_api()

/models/backend.py only accepts:

def _call_sp_api(self, method, endpoint, params=None, json_data=None):
    # marketplace_id and payload are NOT accepted!

Comment on lines 733 to 743
binding_vals = {
"backend_id": shop.backend_id.id,
"amazon_order_id": amazon_order_binding.id,
"order_id": amazon_order_binding.id,
"external_id": item_id,
"seller_sku": seller_sku,
"asin": amazon_item.get("ASIN"),
"product_title": amazon_item.get("Title"),
"quantity": quantity,
"quantity_shipped": quantity_shipped,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're on the right track by defining the amazon.product.binding model and referencing it through the product_binding_id field, but it seems this field isn't being used.

and I guess it needs to be populated in here.

Comment on lines 761 to 767
def _get_product_by_sku(self, shop, seller_sku):
"""Find product by Amazon SKU"""
# TODO: Implement product matching logic via amazon.product.binding
# For now, search by default_code (internal reference)
return self.env["product.product"].search(
[("default_code", "=", seller_sku)], limit=1
)
Copy link
Member

@xaviedoanhduy xaviedoanhduy Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can improve the TODO part to favor searching on the amazon.product.binding model first, then use it as a fallback to avoid breaking the current logic

Suggested change
def _get_product_by_sku(self, shop, seller_sku):
"""Find product by Amazon SKU"""
# TODO: Implement product matching logic via amazon.product.binding
# For now, search by default_code (internal reference)
return self.env["product.product"].search(
[("default_code", "=", seller_sku)], limit=1
)
def _get_product_by_sku(self, shop, seller_sku):
"""Find product by Amazon SKU"""
# Search binding first
binding = self.env["amazon.product.binding"].search([
("backend_id", "=", shop.backend_id.id),
("seller_sku", "=", seller_sku),
], limit=1)
if binding:
return binding, binding.odoo_id
# Fallback to direct product search
product = self.env["product.product"].search([
("default_code", "=", seller_sku)
], limit=1)
return self.env["amazon.product.binding"], product

Comment on lines 78 to 79
f" <AmazonOrderID>{self.external_id}</AmazonOrderID>",
f" <FulfillmentDate>{ship_date}</FulfillmentDate>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can lead to XML Injection Vulnerability
it's better to use ElementTree from xml.etree

Comment on lines 652 to 663
amazon_order_id = fields.Many2one(
comodel_name="amazon.sale.order",
required=True,
ondelete="cascade",
)
order_id = fields.Many2one(
comodel_name="amazon.sale.order",
string="Order",
compute="_compute_order_id",
store=True,
readonly=True,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two fields pointing to the same model - redundant and confusing.

params["Skus"] = ",".join(skus)

return self._call_api(
"GET", "/products/pricing/v0/competitivePrice", params=params
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although v0 api functionality is still usable, it's now labeled as Legacy – perhaps it's better to favor version v2022-05-01 instead.

image

if skus:
params["Skus"] = ",".join(skus[:20])

return self._call_api("GET", "/products/pricing/v0/price", params=params)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to /products/pricing/v0/competitivePrice, the v0 APIs have been flagged as legacy versions

- Fix feed.py _call_sp_api() parameters (json_data instead of payload)
- Fix XML injection vulnerability using ElementTree in order.py
- Remove redundant order_id field from amazon.sale.order.line
- Update _get_product_by_sku to search product binding first
- Update pricing API versions from v0 to 2022-05-01
- Remove seller_sku from backend model (belongs on product binding)
- Fix get_listings_item() to accept seller_sku parameter
- Rewrite sync_catalog() to iterate over existing bindings
- Update tests to match fixed API signatures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment on lines +788 to +797
xml_lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<AmazonEnvelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
' xsi:noNamespaceSchemaLocation="amzn-envelope.xsd">',
" <Header>",
" <DocumentVersion>1.01</DocumentVersion>",
" <MerchantIdentifier>" + merchant_id + "</MerchantIdentifier>",
" </Header>",
" <MessageType>Inventory</MessageType>",
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should also use xml.etree.ElementTree here

Comment on lines +804 to +814
[
" <Message>",
f" <MessageID>{idx}</MessageID>",
" <Inventory>",
f" <SKU>{binding.seller_sku}</SKU>",
" <Quantity>",
f" <Available>{int(available_qty)}</Available>",
" </Quantity>",
" </Inventory>",
" </Message>",
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor _build_inventory_feed_xml to use xml.etree.ElementTree like _build_fulfillment_feed_xml
and _build_shipment_feed_xml do.

for row in reader:
sku = row.get("seller-sku")
asin = row.get("asin1")
row.get("status", "").lower()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
row.get("status", "").lower()

Unused

params["Skus"] = ",".join(skus)

return self._call_api(
"GET", "/products/pricing/2022-05-01/competitivePrice", params=params
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The Product Pricing API v2022-05-01 has these endpoints:

  • POST /products/pricing/2022-05-01/offer/featuredOfferExpectedPrice (batch)
  • POST /products/pricing/2022-05-01/competitiveSummary (batch)

The v0 legacy endpoints are:

  • GET /products/pricing/v0/competitivePrice
  • GET /products/pricing/v0/price

The code appears to be mixing version paths. The 2022-05-01 version uses batch POST endpoints, while the v0
version uses GET endpoints.

Recommendation: Either:

  • Use the legacy v0 endpoints: /products/pricing/v0/competitivePrice and /products/pricing/v0/price
  • Or migrate to the v2022-05-01 batch endpoints with POST method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants