@mercurjs/docs 2.2.0-canary.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +24 -0
  2. package/content/blocks.mdx +209 -0
  3. package/content/learn/architecture.mdx +215 -0
  4. package/content/learn/attributes.mdx +89 -0
  5. package/content/learn/blocks.mdx +99 -0
  6. package/content/learn/commissions.mdx +175 -0
  7. package/content/learn/concepts.mdx +84 -0
  8. package/content/learn/installation.mdx +117 -0
  9. package/content/learn/introduction.mdx +71 -0
  10. package/content/learn/offers.mdx +78 -0
  11. package/content/learn/order-groups.mdx +114 -0
  12. package/content/learn/payouts.mdx +175 -0
  13. package/content/learn/product-requests.mdx +82 -0
  14. package/content/learn/products.mdx +79 -0
  15. package/content/learn/registry.mdx +155 -0
  16. package/content/learn/seller-members.mdx +62 -0
  17. package/content/learn/sellers.mdx +118 -0
  18. package/content/migration/from-1-x-to-2-0.mdx +152 -0
  19. package/content/migration/from-2-0-to-2-1.mdx +105 -0
  20. package/content/migration/overview.mdx +58 -0
  21. package/content/references/api/admin/commission-rates/batch-commission-rules.mdx +70 -0
  22. package/content/references/api/admin/commission-rates/create-commission-rate.mdx +95 -0
  23. package/content/references/api/admin/commission-rates/delete-commission-rate.mdx +40 -0
  24. package/content/references/api/admin/commission-rates/list-commission-rates.mdx +92 -0
  25. package/content/references/api/admin/commission-rates/retrieve-commission-rate.mdx +70 -0
  26. package/content/references/api/admin/commission-rates/update-commission-rate.mdx +84 -0
  27. package/content/references/api/admin/members/list-members.mdx +61 -0
  28. package/content/references/api/admin/offers/batch-create-offers.mdx +130 -0
  29. package/content/references/api/admin/offers/delete-offer.mdx +40 -0
  30. package/content/references/api/admin/offers/list-offers.mdx +106 -0
  31. package/content/references/api/admin/offers/retrieve-offer.mdx +82 -0
  32. package/content/references/api/admin/order-groups/list-order-groups.mdx +77 -0
  33. package/content/references/api/admin/order-groups/retrieve-order-group.mdx +56 -0
  34. package/content/references/api/admin/orders/get-order-commission-lines.mdx +65 -0
  35. package/content/references/api/admin/orders/get-order-order-group.mdx +56 -0
  36. package/content/references/api/admin/orders/list-orders.mdx +79 -0
  37. package/content/references/api/admin/payouts/list-payouts.mdx +75 -0
  38. package/content/references/api/admin/payouts/retrieve-payout.mdx +62 -0
  39. package/content/references/api/admin/product-attributes/create-attribute-value.mdx +76 -0
  40. package/content/references/api/admin/product-attributes/create-product-attribute.mdx +99 -0
  41. package/content/references/api/admin/product-attributes/delete-attribute-value.mdx +52 -0
  42. package/content/references/api/admin/product-attributes/delete-product-attribute.mdx +40 -0
  43. package/content/references/api/admin/product-attributes/list-product-attributes.mdx +91 -0
  44. package/content/references/api/admin/product-attributes/retrieve-product-attribute.mdx +81 -0
  45. package/content/references/api/admin/product-attributes/update-attribute-value.mdx +66 -0
  46. package/content/references/api/admin/product-attributes/update-product-attribute.mdx +76 -0
  47. package/content/references/api/admin/product-changes/cancel-product-change.mdx +68 -0
  48. package/content/references/api/admin/product-changes/confirm-product-change.mdx +47 -0
  49. package/content/references/api/admin/products/batch-product-attributes.mdx +96 -0
  50. package/content/references/api/admin/products/confirm-product.mdx +59 -0
  51. package/content/references/api/admin/products/create-product-variant.mdx +78 -0
  52. package/content/references/api/admin/products/create-product.mdx +169 -0
  53. package/content/references/api/admin/products/delete-product-variant.mdx +48 -0
  54. package/content/references/api/admin/products/delete-product.mdx +40 -0
  55. package/content/references/api/admin/products/list-product-variants.mdx +91 -0
  56. package/content/references/api/admin/products/list-products.mdx +109 -0
  57. package/content/references/api/admin/products/preview-product.mdx +78 -0
  58. package/content/references/api/admin/products/reject-product.mdx +59 -0
  59. package/content/references/api/admin/products/request-product-changes.mdx +59 -0
  60. package/content/references/api/admin/products/retrieve-product-variant.mdx +73 -0
  61. package/content/references/api/admin/products/retrieve-product.mdx +96 -0
  62. package/content/references/api/admin/products/set-product-sellers.mdx +47 -0
  63. package/content/references/api/admin/products/update-product-variant.mdx +89 -0
  64. package/content/references/api/admin/products/update-product.mdx +133 -0
  65. package/content/references/api/admin/sellers/add-seller-member.mdx +63 -0
  66. package/content/references/api/admin/sellers/approve-seller.mdx +54 -0
  67. package/content/references/api/admin/sellers/create-seller.mdx +100 -0
  68. package/content/references/api/admin/sellers/delete-member-invite.mdx +42 -0
  69. package/content/references/api/admin/sellers/delete-professional-details.mdx +52 -0
  70. package/content/references/api/admin/sellers/invite-seller-member.mdx +63 -0
  71. package/content/references/api/admin/sellers/list-member-invites.mdx +71 -0
  72. package/content/references/api/admin/sellers/list-seller-members.mdx +71 -0
  73. package/content/references/api/admin/sellers/list-seller-products.mdx +85 -0
  74. package/content/references/api/admin/sellers/list-sellers.mdx +96 -0
  75. package/content/references/api/admin/sellers/remove-seller-member.mdx +42 -0
  76. package/content/references/api/admin/sellers/resend-member-invite.mdx +55 -0
  77. package/content/references/api/admin/sellers/retrieve-seller.mdx +81 -0
  78. package/content/references/api/admin/sellers/suspend-seller.mdx +60 -0
  79. package/content/references/api/admin/sellers/terminate-seller.mdx +60 -0
  80. package/content/references/api/admin/sellers/unsuspend-seller.mdx +52 -0
  81. package/content/references/api/admin/sellers/unterminate-seller.mdx +52 -0
  82. package/content/references/api/admin/sellers/update-seller.mdx +83 -0
  83. package/content/references/api/admin/sellers/upsert-payment-details.mdx +88 -0
  84. package/content/references/api/admin/sellers/upsert-professional-details.mdx +78 -0
  85. package/content/references/api/admin/sellers/upsert-seller-address.mdx +91 -0
  86. package/content/references/api/admin.mdx +134 -0
  87. package/content/references/api/conventions.mdx +109 -0
  88. package/content/references/api/store/carts/add-line-item.mdx +115 -0
  89. package/content/references/api/store/carts/add-shipping-methods.mdx +84 -0
  90. package/content/references/api/store/carts/apply-promotions.mdx +83 -0
  91. package/content/references/api/store/carts/complete-cart.mdx +98 -0
  92. package/content/references/api/store/offers/list-offers.mdx +161 -0
  93. package/content/references/api/store/offers/retrieve-offer.mdx +112 -0
  94. package/content/references/api/store/order-groups/list-order-groups.mdx +105 -0
  95. package/content/references/api/store/order-groups/retrieve-order-group.mdx +78 -0
  96. package/content/references/api/store/product-attributes/list-product-attributes.mdx +134 -0
  97. package/content/references/api/store/product-attributes/retrieve-product-attribute.mdx +78 -0
  98. package/content/references/api/store/product-categories/list-product-categories.mdx +126 -0
  99. package/content/references/api/store/product-categories/retrieve-product-category.mdx +79 -0
  100. package/content/references/api/store/products/list-products.mdx +184 -0
  101. package/content/references/api/store/products/retrieve-product.mdx +107 -0
  102. package/content/references/api/store/search/search.mdx +136 -0
  103. package/content/references/api/store/sellers/list-sellers.mdx +105 -0
  104. package/content/references/api/store/sellers/retrieve-seller.mdx +62 -0
  105. package/content/references/api/store/shipping-options/list-shipping-options.mdx +74 -0
  106. package/content/references/api/store.mdx +67 -0
  107. package/content/references/api/vendor/members/accept-member-invite.mdx +65 -0
  108. package/content/references/api/vendor/members/retrieve-current-member.mdx +51 -0
  109. package/content/references/api/vendor/members/update-current-member.mdx +70 -0
  110. package/content/references/api/vendor/offers/batch-create-offers.mdx +123 -0
  111. package/content/references/api/vendor/offers/batch-offer-inventory-items.mdx +103 -0
  112. package/content/references/api/vendor/offers/create-offer.mdx +141 -0
  113. package/content/references/api/vendor/offers/delete-offer.mdx +51 -0
  114. package/content/references/api/vendor/offers/list-offers.mdx +125 -0
  115. package/content/references/api/vendor/offers/retrieve-offer.mdx +74 -0
  116. package/content/references/api/vendor/offers/update-offer.mdx +82 -0
  117. package/content/references/api/vendor/orders/cancel-fulfillment.mdx +63 -0
  118. package/content/references/api/vendor/orders/cancel-order.mdx +56 -0
  119. package/content/references/api/vendor/orders/complete-order.mdx +55 -0
  120. package/content/references/api/vendor/orders/create-fulfillment.mdx +76 -0
  121. package/content/references/api/vendor/orders/create-shipment.mdx +99 -0
  122. package/content/references/api/vendor/orders/get-order-commission-lines.mdx +66 -0
  123. package/content/references/api/vendor/orders/list-order-changes.mdx +75 -0
  124. package/content/references/api/vendor/orders/list-orders.mdx +103 -0
  125. package/content/references/api/vendor/orders/mark-fulfillment-delivered.mdx +62 -0
  126. package/content/references/api/vendor/orders/preview-order.mdx +65 -0
  127. package/content/references/api/vendor/orders/retrieve-order.mdx +84 -0
  128. package/content/references/api/vendor/payout-accounts/create-onboarding.mdx +68 -0
  129. package/content/references/api/vendor/payout-accounts/create-payout-account.mdx +59 -0
  130. package/content/references/api/vendor/payout-accounts/retrieve-payout-account-by-id.mdx +60 -0
  131. package/content/references/api/vendor/payout-accounts/retrieve-payout-account.mdx +77 -0
  132. package/content/references/api/vendor/payouts/list-payouts.mdx +90 -0
  133. package/content/references/api/vendor/payouts/retrieve-payout.mdx +63 -0
  134. package/content/references/api/vendor/product-attributes/list-product-attributes.mdx +135 -0
  135. package/content/references/api/vendor/product-attributes/retrieve-product-attribute.mdx +71 -0
  136. package/content/references/api/vendor/product-variants/list-variants.mdx +136 -0
  137. package/content/references/api/vendor/products/batch-product-attributes.mdx +104 -0
  138. package/content/references/api/vendor/products/cancel-product-change.mdx +62 -0
  139. package/content/references/api/vendor/products/create-product-variant.mdx +92 -0
  140. package/content/references/api/vendor/products/create-product.mdx +197 -0
  141. package/content/references/api/vendor/products/delete-product-variant.mdx +68 -0
  142. package/content/references/api/vendor/products/delete-product.mdx +62 -0
  143. package/content/references/api/vendor/products/list-product-variants.mdx +114 -0
  144. package/content/references/api/vendor/products/list-products.mdx +163 -0
  145. package/content/references/api/vendor/products/preview-product.mdx +61 -0
  146. package/content/references/api/vendor/products/retrieve-product-variant.mdx +76 -0
  147. package/content/references/api/vendor/products/retrieve-product.mdx +69 -0
  148. package/content/references/api/vendor/products/update-product-variant.mdx +104 -0
  149. package/content/references/api/vendor/products/update-product.mdx +148 -0
  150. package/content/references/api/vendor/sellers/create-seller.mdx +150 -0
  151. package/content/references/api/vendor/sellers/delete-professional-details.mdx +54 -0
  152. package/content/references/api/vendor/sellers/invite-seller-member.mdx +67 -0
  153. package/content/references/api/vendor/sellers/list-member-invites.mdx +82 -0
  154. package/content/references/api/vendor/sellers/list-seller-members.mdx +80 -0
  155. package/content/references/api/vendor/sellers/list-sellers.mdx +71 -0
  156. package/content/references/api/vendor/sellers/remove-seller-member.mdx +48 -0
  157. package/content/references/api/vendor/sellers/retrieve-current-seller-member.mdx +59 -0
  158. package/content/references/api/vendor/sellers/retrieve-current-seller.mdx +69 -0
  159. package/content/references/api/vendor/sellers/select-seller.mdx +44 -0
  160. package/content/references/api/vendor/sellers/update-current-seller.mdx +108 -0
  161. package/content/references/api/vendor/sellers/update-member-role.mdx +51 -0
  162. package/content/references/api/vendor/sellers/upsert-payment-details.mdx +74 -0
  163. package/content/references/api/vendor/sellers/upsert-professional-details.mdx +70 -0
  164. package/content/references/api/vendor/sellers/upsert-seller-address.mdx +81 -0
  165. package/content/references/api/vendor.mdx +150 -0
  166. package/content/references/configuration.mdx +155 -0
  167. package/content/references/modules/commission.mdx +106 -0
  168. package/content/references/modules/custom-fields.mdx +45 -0
  169. package/content/references/modules/media.mdx +55 -0
  170. package/content/references/modules/offer.mdx +64 -0
  171. package/content/references/modules/payout.mdx +121 -0
  172. package/content/references/modules/product-attribute.mdx +111 -0
  173. package/content/references/modules/product-edit.mdx +80 -0
  174. package/content/references/modules/search.mdx +112 -0
  175. package/content/references/modules/seller.mdx +175 -0
  176. package/content/references/overview.mdx +70 -0
  177. package/content/references/workflows/cart/add-seller-shipping-method-to-cart.mdx +44 -0
  178. package/content/references/workflows/cart/complete-cart-with-split-orders.mdx +36 -0
  179. package/content/references/workflows/cart/list-seller-shipping-options-for-cart.mdx +39 -0
  180. package/content/references/workflows/cart/update-cart-seller-promotions.mdx +42 -0
  181. package/content/references/workflows/commission/batch-commission-rules.mdx +45 -0
  182. package/content/references/workflows/commission/create-commission-rates.mdx +41 -0
  183. package/content/references/workflows/commission/delete-commission-rates.mdx +34 -0
  184. package/content/references/workflows/commission/refresh-order-commission-lines.mdx +30 -0
  185. package/content/references/workflows/commission/update-commission-rates.mdx +35 -0
  186. package/content/references/workflows/media/set-category-images.mdx +41 -0
  187. package/content/references/workflows/media/set-collection-images.mdx +41 -0
  188. package/content/references/workflows/member/accept-member-invite.mdx +33 -0
  189. package/content/references/workflows/member/add-seller-member.mdx +30 -0
  190. package/content/references/workflows/member/create-member-invites.mdx +34 -0
  191. package/content/references/workflows/member/delete-member-invite.mdx +24 -0
  192. package/content/references/workflows/member/remove-seller-member.mdx +28 -0
  193. package/content/references/workflows/member/resend-member-invite.mdx +28 -0
  194. package/content/references/workflows/member/update-member-role.mdx +28 -0
  195. package/content/references/workflows/member/update-member.mdx +35 -0
  196. package/content/references/workflows/offer/batch-offer-inventory-items.mdx +58 -0
  197. package/content/references/workflows/offer/create-offers.mdx +63 -0
  198. package/content/references/workflows/offer/delete-offers.mdx +35 -0
  199. package/content/references/workflows/offer/update-offers.mdx +51 -0
  200. package/content/references/workflows/order/cancel-order-fulfillment.mdx +34 -0
  201. package/content/references/workflows/order/confirm-claim-request.mdx +25 -0
  202. package/content/references/workflows/order/confirm-exchange-request.mdx +25 -0
  203. package/content/references/workflows/order/confirm-order-edit-request.mdx +25 -0
  204. package/content/references/workflows/order/confirm-return-receive.mdx +25 -0
  205. package/content/references/workflows/order/create-order-fulfillment.mdx +45 -0
  206. package/content/references/workflows/order-group/get-order-group-detail.mdx +29 -0
  207. package/content/references/workflows/order-group/get-order-groups-list.mdx +36 -0
  208. package/content/references/workflows/overview.mdx +72 -0
  209. package/content/references/workflows/payout/create-onboarding.mdx +36 -0
  210. package/content/references/workflows/payout/create-payout-account.mdx +33 -0
  211. package/content/references/workflows/payout/create-payout.mdx +30 -0
  212. package/content/references/workflows/payout/process-payout-for-webhook.mdx +32 -0
  213. package/content/references/workflows/product/confirm-products.mdx +46 -0
  214. package/content/references/workflows/product/create-products.mdx +63 -0
  215. package/content/references/workflows/product/link-sellers-to-product-category.mdx +40 -0
  216. package/content/references/workflows/product/link-sellers-to-product.mdx +40 -0
  217. package/content/references/workflows/product/reject-product.mdx +46 -0
  218. package/content/references/workflows/product/request-product-change.mdx +46 -0
  219. package/content/references/workflows/product-attribute/add-product-attributes-to-product.mdx +49 -0
  220. package/content/references/workflows/product-attribute/create-and-link-product-attributes-to-product.mdx +46 -0
  221. package/content/references/workflows/product-attribute/create-product-attribute-values.mdx +43 -0
  222. package/content/references/workflows/product-attribute/create-product-attributes.mdx +56 -0
  223. package/content/references/workflows/product-attribute/delete-product-attribute-values.mdx +30 -0
  224. package/content/references/workflows/product-attribute/delete-product-attributes.mdx +30 -0
  225. package/content/references/workflows/product-attribute/remove-product-attributes-from-product.mdx +29 -0
  226. package/content/references/workflows/product-attribute/update-product-attribute-values.mdx +44 -0
  227. package/content/references/workflows/product-attribute/update-product-attributes-on-product.mdx +41 -0
  228. package/content/references/workflows/product-attribute/update-product-attributes.mdx +48 -0
  229. package/content/references/workflows/product-attribute/upsert-product-attribute-values.mdx +43 -0
  230. package/content/references/workflows/product-edit/auto-confirm-product-change.mdx +39 -0
  231. package/content/references/workflows/product-edit/cancel-product-change.mdx +49 -0
  232. package/content/references/workflows/product-edit/confirm-product-change.mdx +57 -0
  233. package/content/references/workflows/product-edit/create-product-change.mdx +72 -0
  234. package/content/references/workflows/product-edit/reject-product-change.mdx +54 -0
  235. package/content/references/workflows/product-edit/stage-product-change.mdx +75 -0
  236. package/content/references/workflows/seller/approve-seller.mdx +34 -0
  237. package/content/references/workflows/seller/create-seller-account.mdx +59 -0
  238. package/content/references/workflows/seller/create-seller-defaults.mdx +22 -0
  239. package/content/references/workflows/seller/create-sellers.mdx +65 -0
  240. package/content/references/workflows/seller/delete-seller-professional-details.mdx +37 -0
  241. package/content/references/workflows/seller/delete-sellers.mdx +24 -0
  242. package/content/references/workflows/seller/invite-seller.mdx +28 -0
  243. package/content/references/workflows/seller/suspend-seller.mdx +35 -0
  244. package/content/references/workflows/seller/terminate-seller.mdx +35 -0
  245. package/content/references/workflows/seller/unsuspend-seller.mdx +34 -0
  246. package/content/references/workflows/seller/unterminate-seller.mdx +34 -0
  247. package/content/references/workflows/seller/update-seller-address.mdx +55 -0
  248. package/content/references/workflows/seller/update-seller-payment-details.mdx +52 -0
  249. package/content/references/workflows/seller/update-seller-professional-details.mdx +48 -0
  250. package/content/references/workflows/seller/update-sellers.mdx +57 -0
  251. package/content/resources/ai/llms.mdx +74 -0
  252. package/content/resources/ai/mcp.mdx +152 -0
  253. package/content/resources/ai/overview.mdx +97 -0
  254. package/content/resources/customization/custom-fields.mdx +182 -0
  255. package/content/resources/customization/extend-a-workflow.mdx +39 -0
  256. package/content/resources/customization/extending-panels.mdx +309 -0
  257. package/content/resources/deployment/medusa-cloud.mdx +167 -0
  258. package/content/resources/integrations/notifications.mdx +39 -0
  259. package/content/resources/integrations/search.mdx +122 -0
  260. package/content/resources/integrations/stripe-connect.mdx +395 -0
  261. package/content/resources/tutorials/add-a-block.mdx +74 -0
  262. package/content/resources/tutorials/attributes-and-variant-axes.mdx +111 -0
  263. package/content/resources/tutorials/build-a-block.mdx +134 -0
  264. package/content/resources/tutorials/configure-commissions.mdx +127 -0
  265. package/content/resources/tutorials/custom-api-route.mdx +142 -0
  266. package/content/resources/tutorials/custom-panel-page.mdx +78 -0
  267. package/content/resources/tutorials/first-marketplace.mdx +45 -0
  268. package/content/resources/tutorials/handle-product-requests.mdx +80 -0
  269. package/content/resources/tutorials/import-export-products.mdx +96 -0
  270. package/content/resources/tutorials/master-products-and-offers.mdx +89 -0
  271. package/content/resources/tutorials/recompose-a-page.mdx +129 -0
  272. package/content/resources/tutorials/replace-panel-components.mdx +116 -0
  273. package/content/resources/tutorials/seller-payouts-stripe.mdx +89 -0
  274. package/content/telemetry.mdx +67 -0
  275. package/content/tools/api-client.mdx +155 -0
  276. package/content/tools/cli.mdx +196 -0
  277. package/content/tools/dashboard-sdk.mdx +35 -0
  278. package/content/user-guide/admin/overview.mdx +23 -0
  279. package/content/user-guide/admin/stores.mdx +193 -0
  280. package/content/user-guide/vendor/overview.mdx +23 -0
  281. package/content/user-guide/vendor/stores.mdx +209 -0
  282. package/llms.txt +307 -0
  283. package/package.json +33 -0
@@ -0,0 +1,395 @@
1
+ ---
2
+ title: "Stripe Connect Integration"
3
+ description: "Set up Stripe Connect for marketplace payments and seller payouts — from Stripe Dashboard configuration to the full end-to-end payment lifecycle."
4
+ ---
5
+
6
+ A Mercur marketplace needs **two** Stripe integrations working together. The first is the standard Medusa payment provider, which charges customers at checkout. The second is the Mercur payout provider, which transfers funds from the platform to sellers after orders are fulfilled.
7
+
8
+ This guide covers both — setting up Stripe, configuring both providers, wiring up webhooks, and understanding how money flows from customer to seller.
9
+
10
+ <Tip>
11
+ This page is the configuration reference. For a hands-on walkthrough that takes one seller from onboarding to a settled payout, follow the [Set up seller payouts](/rc/resources/tutorials/seller-payouts-stripe) tutorial after completing the setup below.
12
+ </Tip>
13
+
14
+ <Info>
15
+ Mercur uses Stripe's [Separate Charges and Transfers](https://docs.stripe.com/connect/separate-charges-and-transfers) model. The platform collects the full payment from the customer, then creates transfers to each seller's connected account. This makes the platform the **Merchant of Record**, responsible for VAT, disputes, and chargebacks.
16
+ </Info>
17
+
18
+ ## Prerequisites
19
+
20
+ - A [Stripe account](https://dashboard.stripe.com) with Connect enabled
21
+ - Stripe Secret API key and Publishable API key
22
+ - A running Mercur project ([installation guide](/rc/learn/installation))
23
+ - Node.js 20+
24
+
25
+ ## Architecture overview
26
+
27
+ The payment lifecycle in a Mercur marketplace flows through two distinct Stripe integrations:
28
+
29
+ ```mermaid
30
+ flowchart LR
31
+ C[Customer] -->|PaymentIntent| P[Platform Stripe Account]
32
+ P -->|Transfer| S1[Seller A Connected Account]
33
+ P -->|Transfer| S2[Seller B Connected Account]
34
+ S1 -->|Payout| B1[Seller A Bank]
35
+ S2 -->|Payout| B2[Seller B Bank]
36
+ ```
37
+
38
+ The two integrations serve different purposes:
39
+
40
+ | Integration | Package | Purpose | Webhook endpoint |
41
+ |---|---|---|---|
42
+ | Customer payments | `@medusajs/medusa/payment-stripe` | Charges customers at checkout | `/hooks/payment/stripe_stripe` |
43
+ | Seller payouts | `@mercurjs/payout-stripe-connect` | Transfers funds to sellers | `/hooks/payout` |
44
+
45
+ ## Stripe Dashboard setup
46
+
47
+ <Steps>
48
+ <Step title="Enable Connect">
49
+ In your Stripe Dashboard, go to **Settings → Connect settings** and enable Connect. If you're starting fresh, Stripe will walk you through an onboarding flow to activate your platform account.
50
+ </Step>
51
+ <Step title="Find your API keys">
52
+ Go to **Developers → API keys**. You'll need:
53
+
54
+ - **Secret key** — starts with `sk_test_` (test mode) or `sk_live_` (production)
55
+ - **Publishable key** — starts with `pk_test_` or `pk_live_`
56
+ </Step>
57
+ <Step title="Set environment variables">
58
+ Add the following to your `.env` file:
59
+
60
+ ```bash
61
+ STRIPE_API_KEY=sk_test_...
62
+ STRIPE_WEBHOOK_SECRET=whsec_...
63
+ STRIPE_PAYOUT_WEBHOOK_SECRET=whsec_...
64
+ ```
65
+ </Step>
66
+ </Steps>
67
+
68
+ ## Configure Medusa payment provider
69
+
70
+ The Medusa payment provider handles customer-facing charges. Add it to your `medusa-config.ts`:
71
+
72
+ ```ts
73
+ module.exports = defineConfig({
74
+ // ...
75
+ modules: [
76
+ {
77
+ resolve: "@medusajs/medusa/payment",
78
+ options: {
79
+ providers: [
80
+ {
81
+ resolve: "@medusajs/medusa/payment-stripe",
82
+ id: "stripe",
83
+ options: {
84
+ apiKey: process.env.STRIPE_API_KEY,
85
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
86
+ capture: false, // Use manual capture for marketplace flow
87
+ automatic_payment_methods: true,
88
+ },
89
+ },
90
+ ],
91
+ },
92
+ },
93
+ ],
94
+ })
95
+ ```
96
+
97
+ ### Provider options
98
+
99
+ | Option | Type | Description |
100
+ |--------|------|-------------|
101
+ | `apiKey` | `string` | Stripe secret API key |
102
+ | `webhookSecret` | `string` | Signing secret for the payment webhook endpoint |
103
+ | `capture` | `boolean` | Set to `false` for manual capture (required for marketplace split flow) |
104
+ | `automatic_payment_methods` | `boolean` | Enable Stripe's automatic payment method detection |
105
+ | `paymentDescription` | `string` | Optional description shown on customer's bank statement |
106
+
107
+ <Info>
108
+ After configuring the provider, enable Stripe as a payment method in your Admin Panel: **Settings → Regions → Edit region → Payment providers → Stripe**.
109
+ </Info>
110
+
111
+ ## Configure Mercur payout provider
112
+
113
+ The payout provider handles transfers from the platform to seller connected accounts. Add it alongside the payment provider in `medusa-config.ts`:
114
+
115
+ ```ts
116
+ module.exports = defineConfig({
117
+ // ...
118
+ modules: [
119
+ // ... payment provider above
120
+ {
121
+ resolve: "@mercurjs/core/modules/payout",
122
+ options: {
123
+ providers: [
124
+ {
125
+ resolve: "@mercurjs/payout-stripe-connect",
126
+ id: "stripe-connect",
127
+ options: {
128
+ apiKey: process.env.STRIPE_API_KEY,
129
+ webhookSecret: process.env.STRIPE_PAYOUT_WEBHOOK_SECRET,
130
+ accountValidation: {
131
+ detailsSubmitted: true,
132
+ chargesEnabled: true,
133
+ payoutsEnabled: true,
134
+ noOutstandingRequirements: true,
135
+ requiredCapabilities: [],
136
+ },
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ },
142
+ ],
143
+ })
144
+ ```
145
+
146
+ ### Payout provider options
147
+
148
+ | Option | Type | Default | Description |
149
+ |--------|------|---------|-------------|
150
+ | `apiKey` | `string` | — | Stripe secret API key |
151
+ | `webhookSecret` | `string` | — | Signing secret for the payout webhook endpoint |
152
+ | `accountValidation` | `object` | See below | Controls when a connected account is considered `ACTIVE` |
153
+
154
+ ### Account validation options
155
+
156
+ These options determine what conditions a Stripe connected account must meet before Mercur marks it as `ACTIVE` and allows payouts:
157
+
158
+ | Option | Type | Default | Description |
159
+ |--------|------|---------|-------------|
160
+ | `detailsSubmitted` | `boolean` | `true` | Require Stripe to mark onboarding details as submitted |
161
+ | `chargesEnabled` | `boolean` | `true` | Require the account to be enabled for charges |
162
+ | `payoutsEnabled` | `boolean` | `true` | Require the account to be enabled for payouts |
163
+ | `noOutstandingRequirements` | `boolean` | `true` | Treat any pending Stripe requirements as a restricted account |
164
+ | `requiredCapabilities` | `string[]` | `[]` | Require specific Stripe capabilities to be active (e.g. `["card_payments", "transfers"]`) |
165
+
166
+ ### Payout module options
167
+
168
+ The payout module itself accepts timing options that control the capture and payout pipeline:
169
+
170
+ | Option | Type | Default | Description |
171
+ |--------|------|---------|-------------|
172
+ | `disabled` | `boolean` | `false` | Disable automatic capture checks and daily payout jobs |
173
+ | `authorizationWindowMs` | `number` | `604800000` (7 days) | How long a payment authorization remains valid |
174
+ | `sellerActionWindowMs` | `number` | `259200000` (72 hours) | How long sellers have to accept/fulfill before cancellation |
175
+ | `captureSafetyBufferMs` | `number` | `86400000` (24 hours) | Safety margin before authorization expiry — capture happens before `authorization expiry - buffer` |
176
+ | `requiredFulfillmentStatus` | `string` | `"fulfilled"` | Minimum fulfillment status before an order is eligible for capture |
177
+
178
+ ## Set up webhooks
179
+
180
+ You need **two separate webhook endpoints** in Stripe — one for payment events, one for payout events.
181
+
182
+ <Warning>
183
+ These are two distinct webhook endpoints, each with its own signing secret. Do not combine them into a single endpoint.
184
+ </Warning>
185
+
186
+ <Steps>
187
+ <Step title="Add the payment webhook (Medusa)">
188
+ In the Stripe Dashboard, go to **Developers → Webhooks → Add endpoint**.
189
+
190
+ - **Endpoint URL**: `https://{your-backend-url}/hooks/payment/stripe_stripe`
191
+ - **Events to subscribe to**:
192
+ - `payment_intent.succeeded`
193
+ - `payment_intent.amount_capturable_updated`
194
+ - `payment_intent.payment_failed`
195
+ - `charge.refunded`
196
+
197
+ Copy the signing secret and set it as `STRIPE_WEBHOOK_SECRET` in your `.env`.
198
+ </Step>
199
+ <Step title="Add the payout webhook (Mercur)">
200
+ Add a second webhook endpoint in the Stripe Dashboard.
201
+
202
+ - **Endpoint URL**: `https://{your-backend-url}/hooks/payout`
203
+ - **Events to subscribe to**:
204
+ - `account.updated`
205
+
206
+ Copy the signing secret and set it as `STRIPE_PAYOUT_WEBHOOK_SECRET` in your `.env`.
207
+ </Step>
208
+ </Steps>
209
+
210
+ ## How the payment flow works
211
+
212
+ Here's a concrete example. A customer buys items from two sellers:
213
+
214
+ - **Seller A**: €40 (product)
215
+ - **Seller B**: €30 (product)
216
+ - **Shipping**: €10
217
+ - **Cart total**: €80
218
+
219
+ ### Step 1 — Authorize payment
220
+
221
+ At checkout, a single `PaymentIntent` is created for €80 with `capture_method: "manual"`. The customer authenticates once (SCA-compliant). No money moves yet — the funds are held on the customer's card.
222
+
223
+ ### Step 2 — Split orders
224
+
225
+ Mercur's `completeCartWithSplitOrdersWorkflow` groups items by seller and creates separate orders — one for Seller A (€40) and one for Seller B (€30), plus shipping allocation.
226
+
227
+ ### Step 3 — Seller acceptance and fulfillment
228
+
229
+ Each seller reviews and fulfills their order through the Vendor Portal. The payout module's `sellerActionWindowMs` (default: 72 hours) defines how long sellers have to act.
230
+
231
+ ### Step 4 — Capture payment
232
+
233
+ Once orders meet the `requiredFulfillmentStatus` (default: `"fulfilled"`), the platform captures the authorized payment. The capture-check job runs automatically and respects the `captureSafetyBufferMs` to ensure capture happens before authorization expiry.
234
+
235
+ <Warning>
236
+ Card authorizations typically expire after **7 days**. The default configuration gives sellers 72 hours to fulfill, with a 24-hour safety buffer before capture. If your business requires longer seller action windows, consider whether the 7-day authorization window is sufficient.
237
+ </Warning>
238
+
239
+ ### Step 5 — Commission calculation and transfers
240
+
241
+ After capture, Mercur calculates commission for each order and creates Stripe Transfers for the net amounts:
242
+
243
+ ```
244
+ Seller A net = €40 - commission
245
+ Seller B net = €30 - commission
246
+ ```
247
+
248
+ Each transfer is linked to the original charge via `source_transaction` and grouped by `transfer_group` (the order ID).
249
+
250
+ ### Step 6 — Bank payouts
251
+
252
+ Stripe automatically pays out connected account balances to sellers' bank accounts on the configured payout schedule. Mercur tracks payout status changes via the `account.updated` webhook.
253
+
254
+ ## Seller onboarding
255
+
256
+ When a seller creates a payout account, the following lifecycle begins:
257
+
258
+ ```
259
+ PENDING → (Stripe onboarding) → ACTIVE
260
+
261
+ RESTRICTED → ACTIVE (remediation)
262
+
263
+ REJECTED (permanent)
264
+ ```
265
+
266
+ 1. **Account creation** — Mercur calls `stripe.accounts.create({ type: "express" })`, creating a Stripe Express connected account. The payout account starts in `PENDING` status.
267
+
268
+ 2. **Onboarding link** — The seller receives a Stripe-hosted onboarding URL via `stripe.accountLinks.create()`. They complete identity verification, bank account setup, and any required compliance steps directly on Stripe.
269
+
270
+ 3. **Webhook activation** — When the seller completes onboarding, Stripe sends an `account.updated` webhook. The provider evaluates the account against the `accountValidation` options and transitions the status:
271
+ - All validation checks pass → `ACTIVE`
272
+ - Missing requirements or disabled reason → `RESTRICTED`
273
+ - Disabled reason starts with `rejected.` → `REJECTED`
274
+
275
+ 4. **Ongoing monitoring** — Stripe may send additional `account.updated` events if requirements change. The provider re-evaluates and updates the status accordingly.
276
+
277
+ For more details on payout accounts, balances, and transactions, see [Payout](/rc/learn/payouts).
278
+
279
+ ## Transfers vs payouts
280
+
281
+ Stripe uses two distinct concepts for moving money, and it's important to understand the difference:
282
+
283
+ | | Transfer | Payout |
284
+ |---|---|---|
285
+ | **What it does** | Moves funds from platform balance to connected account balance | Moves funds from connected account balance to seller's bank account |
286
+ | **Speed** | Instant ledger movement | 1–3 business days (varies by country) |
287
+ | **Status lifecycle** | None — transfers are immediate | `pending` → `in_transit` → `paid` / `failed` |
288
+ | **Who triggers it** | Mercur (via `stripe.transfers.create()`) | Stripe (on the connected account's payout schedule) |
289
+ | **Mercur tracking** | Transfer created with status `PAID` immediately | Status tracked via webhooks |
290
+
291
+ When Mercur's `createPayout` method is called, the Stripe Connect provider creates a **Transfer** (platform → connected account). The actual bank payout (connected account → bank) happens automatically on Stripe's schedule.
292
+
293
+ ## Refunds and reversals
294
+
295
+ Refunding a charge does **not** automatically reverse the associated transfers. These are two separate operations:
296
+
297
+ 1. **Refund the PaymentIntent** — Returns funds to the customer's payment method
298
+ 2. **Reverse the Transfer(s)** — Claws back funds from the connected account(s)
299
+
300
+ For a full refund of a multi-seller order, you would need to reverse each seller's transfer individually. For partial refunds, you need to calculate how much to reverse from each seller based on which items are being refunded.
301
+
302
+ <Info>
303
+ Refund and transfer reversal orchestration is an area where custom workflow logic may be needed depending on your business rules. See Stripe's documentation on [reversing transfers](https://docs.stripe.com/connect/separate-charges-and-transfers#reverse-transfers) for the API details.
304
+ </Info>
305
+
306
+ ## EU/EEA considerations
307
+
308
+ ### Merchant of Record
309
+
310
+ Using Separate Charges and Transfers makes the platform the Merchant of Record. This means:
311
+
312
+ - The platform collects and remits VAT
313
+ - The platform handles consumer disputes and chargebacks
314
+ - The platform is responsible for PSD2 compliance
315
+
316
+ ### Strong Customer Authentication (SCA)
317
+
318
+ SCA is enforced at **authorization time**, not capture time. Since the customer authenticates once during checkout, no additional SCA step is needed when the platform captures later. The single authorization at checkout satisfies the SCA requirement for the entire order.
319
+
320
+ ### Authorization windows
321
+
322
+ Card authorizations in Europe typically last **7 days**. Design your seller acceptance workflow to complete well within this window. The payout module options give you control over the timing:
323
+
324
+ ```ts
325
+ {
326
+ resolve: "@mercurjs/core/modules/payout",
327
+ options: {
328
+ authorizationWindowMs: 7 * 24 * 60 * 60 * 1000, // 7 days
329
+ sellerActionWindowMs: 72 * 60 * 60 * 1000, // 72 hours
330
+ captureSafetyBufferMs: 24 * 60 * 60 * 1000, // 24 hours
331
+ // ...providers
332
+ },
333
+ }
334
+ ```
335
+
336
+ ## Testing
337
+
338
+ ### Test mode
339
+
340
+ Use Stripe test mode keys (`sk_test_`, `pk_test_`) during development. All connected accounts and payments created in test mode are isolated from production.
341
+
342
+ **Test card number**: `4242 4242 4242 4242` (any future expiry, any CVC)
343
+
344
+ ### Webhook forwarding with Stripe CLI
345
+
346
+ Since webhooks need to reach your local machine during development, use the [Stripe CLI](https://docs.stripe.com/stripe-cli) to forward events. You need **two separate listeners** — one for each webhook endpoint:
347
+
348
+ ```bash
349
+ # Terminal 1 — Payment webhooks
350
+ stripe listen --forward-to localhost:9000/hooks/payment/stripe_stripe
351
+
352
+ # Terminal 2 — Payout webhooks
353
+ stripe listen --forward-to localhost:9000/hooks/payout
354
+ ```
355
+
356
+ <Tip>
357
+ Each `stripe listen` process outputs its own webhook signing secret. Use these as your `STRIPE_WEBHOOK_SECRET` and `STRIPE_PAYOUT_WEBHOOK_SECRET` respectively during local development.
358
+ </Tip>
359
+
360
+ ### Testing the full flow
361
+
362
+ 1. Create a seller and complete Stripe's test onboarding flow
363
+ 2. Add products from the seller to a cart
364
+ 3. Complete checkout using the test card
365
+ 4. Verify orders are split by seller in the Admin Panel
366
+ 5. Fulfill the orders through the Vendor Portal
367
+ 6. Confirm transfers appear in the Stripe Dashboard under the connected account
368
+
369
+ ## FAQ
370
+
371
+ <AccordionGroup>
372
+ <Accordion title="Why do I need two webhook endpoints with two secrets?">
373
+ They belong to two different integrations: the payment webhook feeds Medusa's payment provider (charges, captures, refunds), while the payout webhook feeds Mercur's payout provider (connected-account status, transfer status). Each endpoint verifies its own signing secret — combining them silently breaks whichever side's signature doesn't match.
374
+ </Accordion>
375
+ <Accordion title="Why must capture be set to false on the payment provider?">
376
+ The marketplace flow authorizes at checkout and captures later, after sellers fulfill. Automatic capture would take the money before the split-order pipeline (fulfillment checks, commission, payouts) has run — `capture: false` hands that timing to the payout module's capture job.
377
+ </Accordion>
378
+ <Accordion title="A seller finished Stripe onboarding but is still not ACTIVE — why?">
379
+ Check the `accountValidation` options: by default the account must have details submitted, charges and payouts enabled, and **no outstanding requirements**. Stripe often adds follow-up requirements (e.g. extra KYC) after initial onboarding — the account shows as `RESTRICTED` until they're cleared.
380
+ </Accordion>
381
+ <Accordion title="Who handles VAT, disputes, and chargebacks?">
382
+ The platform. Separate Charges and Transfers makes the platform the Merchant of Record — see [EU/EEA considerations](#eueea-considerations) for what that entails.
383
+ </Accordion>
384
+ </AccordionGroup>
385
+
386
+ ## Next steps
387
+
388
+ <CardGroup cols={2}>
389
+ <Card title="Set up seller payouts" href="/rc/resources/tutorials/seller-payouts-stripe">
390
+ Hands-on: one seller from onboarding to a settled payout.
391
+ </Card>
392
+ <Card title="Payout" href="/rc/learn/payouts">
393
+ The pipeline reference — statuses, jobs, and webhook events.
394
+ </Card>
395
+ </CardGroup>
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: "Add a feature with a block"
3
+ description: "Install the reviews block end-to-end and see it live across the admin, vendor, and storefront surfaces."
4
+ ---
5
+
6
+ Blocks are complete features — backend module, workflows, API routes, and panel UI — installed as **source code** into your project. This tutorial installs the `reviews` block and follows it across every surface it touches.
7
+
8
+ <Info>
9
+ **Blocks are copied, not installed as dependencies.** `add` writes the block's source files into your project via the aliases in `blocks.json` — you own and can edit every file afterwards. Updates are opt-in through `diff` and `add --overwrite`. When you outgrow the catalog, [build your own block](/rc/resources/tutorials/build-a-block).
10
+ </Info>
11
+
12
+ ## Goal
13
+
14
+ Install reviews as a block and see it running everywhere it appears.
15
+
16
+ ## Install the block
17
+
18
+ <Steps>
19
+ <Step title="Discover the block">
20
+ Search the registry and inspect what the block ships before installing:
21
+
22
+ ```bash
23
+ bunx @mercurjs/cli@rc search --query reviews
24
+ bunx @mercurjs/cli@rc view reviews
25
+ ```
26
+
27
+ `view` lists the block's files by target (API, admin, vendor) and its dependencies.
28
+ </Step>
29
+ <Step title="Install it">
30
+ ```bash
31
+ bunx @mercurjs/cli@rc add reviews
32
+ ```
33
+
34
+ The CLI copies the source into the directories mapped by your `blocks.json` aliases and prints the block's post-install instructions — module registration, middlewares, migrations.
35
+ </Step>
36
+ <Step title="Run migrations and codegen">
37
+ The block introduced a reviews module, so generate and run its migrations, then refresh the typed route map:
38
+
39
+ ```bash
40
+ cd packages/api
41
+ bunx medusa db:generate reviews
42
+ bunx medusa db:migrate
43
+ bunx @mercurjs/cli@rc codegen
44
+ ```
45
+ </Step>
46
+ <Step title="See it live">
47
+ Start the project — reviews now appear in the admin panel (moderation), the vendor portal (per-seller reviews), and the Store API (customer-facing review routes).
48
+ </Step>
49
+ </Steps>
50
+
51
+ ## Verify
52
+
53
+ 1. The block's files exist in your repo under the alias-mapped paths.
54
+ 2. Migrations ran cleanly and the API boots.
55
+ 3. The admin and vendor panels show their reviews pages; the store review endpoints respond.
56
+ 4. `bunx @mercurjs/cli@rc diff reviews` reports no drift from the registry.
57
+
58
+ ## FAQ
59
+
60
+ <AccordionGroup>
61
+ <Accordion title="What if I've already modified files the block wants to write?">
62
+ The CLI asks before overwriting existing files (or force it with `--overwrite`). If you've customized a page the block also ships, merge by hand — you're merging source, not resolving package versions.
63
+ </Accordion>
64
+ <Accordion title="How do I update a block later?">
65
+ `bunx @mercurjs/cli@rc diff reviews` shows what changed in the registry since you installed. Take updates with `add reviews --overwrite`, re-applying any local edits afterwards.
66
+ </Accordion>
67
+ </AccordionGroup>
68
+
69
+ ## Next steps
70
+
71
+ <CardGroup cols={2}>
72
+ <Card title="Add a custom panel page" href="/rc/resources/tutorials/custom-panel-page" />
73
+ <Card title="Build your own block" href="/rc/resources/tutorials/build-a-block" />
74
+ </CardGroup>
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: "Create attributes and variant axes"
3
+ description: "Build the attribute catalog: a filterable attribute, a variant axis backed by a native product option, and an inline product-scoped axis."
4
+ ---
5
+
6
+ Attributes give the shared catalog structured, typed data — and for `multi_select` attributes, they can drive variant generation. This tutorial sets up the two kinds that matter most: a plain filterable attribute and a **variant axis**, which under the hood is a native Medusa global product option.
7
+
8
+ <Warning>
9
+ This is a release-candidate feature built on Medusa's global product options (Medusa 2.16 preview). Details may still shift before the stable release.
10
+ </Warning>
11
+
12
+ <Info>
13
+ **A variant axis IS a product option.** Attributes marked `is_variant_axis` aren't a parallel system bolted onto products — each one mirrors one-to-one onto a Medusa `ProductOption` and its values. Variants are then built with Medusa's standard machinery (`variants[].options`), exactly as in a plain Medusa project. Non-axis attributes never become options; they attach as plain value links. This is why only `multi_select` attributes can be axes — an axis needs an enumerable set of values to combine into variants.
14
+ </Info>
15
+
16
+ ## What you'll build
17
+
18
+ - A global `Material` attribute (multi-select, filterable) used for storefront filtering.
19
+ - A global `Color` variant axis shared across the catalog.
20
+ - An inline, product-scoped `Fit` axis created on the fly from a product form.
21
+
22
+ ## Global vs product-scoped
23
+
24
+ | Kind | Backed by | Appears in the global catalog? | Use for |
25
+ |------|-----------|-------------------------------|---------|
26
+ | Global attribute | Shared `ProductOption` (when axis) or value links | Yes | Data every product can use — Material, Color, Condition |
27
+ | Product-scoped attribute | Exclusive, product-owned option (when axis) | No | One-off fields or axes for a single product |
28
+
29
+ ## Build the attribute catalog
30
+
31
+ <Steps>
32
+ <Step title="Create a filterable attribute">
33
+ In the Admin Panel, the operator owns the attribute catalog. Create **Material** as a global `multi_select` attribute with values like Cotton, Wool, Linen, and turn on `is_filterable`. Global attributes (`product_id = null`) can be attached to any product and linked to categories so the right attributes surface for the right product types.
34
+
35
+ Since it's not a variant axis, Material describes the product — it will never generate variants.
36
+ </Step>
37
+ <Step title="Create a variant axis">
38
+ Create **Color** the same way, but enable `is_variant_axis`. This is only allowed for `multi_select` attributes — the platform rejects the flag on any other type.
39
+
40
+ Because Color is a **global** axis, it's backed by one shared product option. Every product that uses it links to that option, restricted to the subset of values the product actually offers — so "Color" means the same thing across the whole catalog, while one product can offer only Red and Blue.
41
+ </Step>
42
+ <Step title="Attach attributes to a product">
43
+ Products manage attributes through one **batch** endpoint that adds, removes, and updates in a single request:
44
+
45
+ ```bash
46
+ curl -X POST "http://localhost:9000/vendor/products/prod_123/attributes/batch" \
47
+ -H "Authorization: Bearer <token>" \
48
+ -H "Content-Type: application/json" \
49
+ -d '{
50
+ "add": [
51
+ { "id": "pattr_material", "value_ids": ["pattrval_cotton"] },
52
+ { "id": "pattr_color", "value_ids": ["pattrval_red", "pattrval_blue"] }
53
+ ]
54
+ }'
55
+ ```
56
+
57
+ At product create time, the same entry shape is passed as a unified `attributes[]` array. Because Color is an axis, selecting Red and Blue makes them available as variant options — variants are then defined with standard Medusa `variants[].options` mapping `"Color"` to `"Red"` or `"Blue"`.
58
+ </Step>
59
+ <Step title="Add an inline product-scoped axis">
60
+ Sometimes one product needs an axis that doesn't belong in the shared catalog. Define it **inline** by `title` instead of referencing an `id`:
61
+
62
+ ```bash
63
+ curl -X POST "http://localhost:9000/vendor/products/prod_123/attributes/batch" \
64
+ -H "Authorization: Bearer <token>" \
65
+ -H "Content-Type: application/json" \
66
+ -d '{
67
+ "add": [
68
+ { "title": "Fit", "values": ["Slim", "Regular"], "is_variant_axis": true }
69
+ ]
70
+ }'
71
+ ```
72
+
73
+ This creates a product-scoped attribute on the fly, backed by an **exclusive**, product-owned option. It won't appear in the global attribute list — it belongs to this product alone.
74
+ </Step>
75
+ </Steps>
76
+
77
+ <Note>
78
+ Attribute changes on products submitted by vendors flow through the same [change-request pipeline](/rc/learn/product-requests) as other product edits — `ATTRIBUTE_ADD` / `ATTRIBUTE_UPDATE` / `ATTRIBUTE_REMOVE` actions the operator reviews.
79
+ </Note>
80
+
81
+ ## Verify
82
+
83
+ 1. **Material** and **Color** appear in the Admin Panel's attribute catalog; **Fit** does not (it's product-scoped).
84
+ 2. The product detail shows Material as descriptive data and Color/Fit as variant axes.
85
+ 3. The product's variants combine the selected Color and Fit values, built from real product options.
86
+ 4. The Store API exposes Material for filtering on product listings (`is_filterable`).
87
+
88
+ ## FAQ
89
+
90
+ <AccordionGroup>
91
+ <Accordion title="Why can't a text or toggle attribute be a variant axis?">
92
+ An axis needs an enumerable, finite value set to combine into variants — Red/Blue × Slim/Regular. Free text has no enumerable values, and a toggle's two fixed values rarely describe purchasable variations. Only `multi_select` qualifies, and the platform enforces it.
93
+ </Accordion>
94
+ <Accordion title="What's the difference between a value link and an option?">
95
+ Non-axis attributes attach to a product as plain **value links** — descriptive data for display and filtering. Axis attributes are mirrored onto real Medusa **product options**, which participate in variant generation. Same authoring UI, structurally different underneath.
96
+ </Accordion>
97
+ <Accordion title="Can I promote a product-scoped attribute to a global one later?">
98
+ Not automatically — a product-scoped axis is backed by an exclusive option owned by that product. Create the global attribute in the catalog and re-attach products to it; treat inline attributes as intentionally local.
99
+ </Accordion>
100
+ </AccordionGroup>
101
+
102
+ ## Next steps
103
+
104
+ <CardGroup cols={2}>
105
+ <Card title="Product Attributes" href="/rc/learn/attributes">
106
+ Types, fields, and the full attribute-to-option mapping.
107
+ </Card>
108
+ <Card title="Master products & offers" href="/rc/resources/tutorials/master-products-and-offers">
109
+ How sellers list against the variants your axes generate.
110
+ </Card>
111
+ </CardGroup>