Add connector amazon spapi don#1
Add connector amazon spapi don#1dnplkndll wants to merge 11 commits intoadd-connector_amazon_spapifrom
Conversation
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.
2b4a873 to
44d31aa
Compare
There was a problem hiding this comment.
hello guys, thanks for your work.
- 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)
- 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) |
| marketplace_id=self.marketplace_id.marketplace_id, | ||
| payload=payload, |
There was a problem hiding this comment.
| 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!| 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, | ||
| } |
There was a problem hiding this comment.
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.
| 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 | ||
| ) |
There was a problem hiding this comment.
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
| 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 |
| f" <AmazonOrderID>{self.external_id}</AmazonOrderID>", | ||
| f" <FulfillmentDate>{ship_date}</FulfillmentDate>", |
There was a problem hiding this comment.
this can lead to XML Injection Vulnerability
it's better to use ElementTree from xml.etree
| 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, | ||
| ) |
There was a problem hiding this comment.
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 |
| if skus: | ||
| params["Skus"] = ",".join(skus[:20]) | ||
|
|
||
| return self._call_api("GET", "/products/pricing/v0/price", params=params) |
There was a problem hiding this comment.
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>
| 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>", | ||
| ] |
There was a problem hiding this comment.
I guess we should also use xml.etree.ElementTree here
| [ | ||
| " <Message>", | ||
| f" <MessageID>{idx}</MessageID>", | ||
| " <Inventory>", | ||
| f" <SKU>{binding.seller_sku}</SKU>", | ||
| " <Quantity>", | ||
| f" <Available>{int(available_qty)}</Available>", | ||
| " </Quantity>", | ||
| " </Inventory>", | ||
| " </Message>", | ||
| ] |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
| row.get("status", "").lower() |
Unused
| params["Skus"] = ",".join(skus) | ||
|
|
||
| return self._call_api( | ||
| "GET", "/products/pricing/2022-05-01/competitivePrice", params=params |
There was a problem hiding this comment.
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/competitivePriceand/products/pricing/v0/price - Or migrate to the
v2022-05-01batch endpoints with POST method

No description provided.