@amboras-dev/aliexpress-dropshipping 1.2.2 → 1.3.1

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.
package/README.md CHANGED
@@ -1,15 +1,35 @@
1
1
  # @amboras-dev/aliexpress-dropshipping
2
2
 
3
- Hybrid package for the Amboras AliExpress Dropshipping integration.
4
- It exports the React admin settings form and optional storefront preflight
5
- hooks/guards. All AliExpress OAuth, signing
6
- (MD5 for `/sync`, HMAC-SHA256 for `/rest/auth/token/*`), category
7
- browsing, product import, and token refresh live in the Medusa backend
8
- module.
9
-
10
- See the plugin admin page for connect / test-connection / disconnect
11
- UI and the merchant OAuth flow.
12
-
13
- Storefront checkout integrations can import `useAliexpressPreflight` from
14
- `@amboras-dev/aliexpress-dropshipping/client` and block payment when the
15
- orchestrator reports a hard fulfillment failure.
3
+ Storefront package for the Amboras AliExpress Dropshipping integration.
4
+ It exports the cart preflight hook and its result types.
5
+
6
+ ## Where things live
7
+
8
+ All AliExpress OAuth, request signing (MD5 for `/sync`, HMAC-SHA256 for `/rest/auth/token/*`), category browsing, product import, order forwarding and token refresh live in amboras-dev-orchestrator, under `backend/src/modules/dropshipping`.
9
+ They are not part of the Medusa backend.
10
+
11
+ The only Medusa Store API route for this integration is `POST /store/aliexpress-dropshipping/preflight-cart`.
12
+
13
+ ## Preflight hook
14
+
15
+ Storefront checkout integrations import `useAliexpressPreflight` from `@amboras-dev/aliexpress-dropshipping/client`.
16
+
17
+ ```ts
18
+ const { mutateAsync, isPending } = useAliexpressPreflight()
19
+ const result = await mutateAsync({ cartId })
20
+ ```
21
+
22
+ The hook returns `{ mutateAsync, isPending }`.
23
+ `mutateAsync` resolves to a `PreflightResult` and rejects if the route responds with a non-2xx status.
24
+ Block payment when `result.ok` is false.
25
+ Price drift is reported in `result.warnings` with code `PRICE_DRIFT` and is never a blocking item error.
26
+
27
+ ## Deprecated exports
28
+
29
+ These are kept to avoid a breaking release and will be removed in the next major version.
30
+ Nothing in the platform consumes them.
31
+
32
+ - `AliExpressDropshippingSettingsForm` and its props.
33
+ It models per-store AppKey/AppSecret, but the real flow is a platform app key plus merchant OAuth.
34
+ - `AliExpressPluginConfig`, `AliExpressCategoryDto`, `AliExpressProductSummaryDto`, `AliExpressProductDto`, `AliExpressProductVariantDto`, `AliExpressImportResultDto`, `AliExpressConnectionStatusDto`, `AliExpressTestConnectionResult`, `AliExpressReauthReason`.
35
+ The routes they described (status, test-connection, product import, config update) do not exist in Medusa.
@@ -1,50 +1,3 @@
1
- import * as React from 'react';
2
- import { c as AliExpressPluginConfig, a as AliExpressConnectionStatusDto, h as AliExpressTestConnectionResult } from '../types-CLTkhq_u.mjs';
3
-
4
- /**
5
- * Admin settings form for the AliExpress Dropshipping integration.
6
- *
7
- * Class B: this component owns the UI and talks to the Medusa Store API
8
- * under `/store/aliexpress-dropshipping/*`. It does NOT embed any
9
- * AliExpress SDK, does not sign requests, and does not touch OAuth
10
- * tokens directly - the backend does all of that.
11
- *
12
- * The component is intentionally dependency-light (no external UI kit,
13
- * no @tanstack/react-query peer) so the admin bundle can drop it in
14
- * without extra peer dep resolution.
15
- */
16
-
17
- interface AliExpressDropshippingSettingsFormProps {
18
- /** Current config for this store (fetched by the parent server component). */
19
- initialConfig: AliExpressPluginConfig;
20
- /** Current connection status (from `GET /status`). */
21
- initialStatus: AliExpressConnectionStatusDto;
22
- /**
23
- * Persist non-secret + optionally new secret values. The parent maps
24
- * this onto `PATCH /store/aliexpress-dropshipping/config` (or
25
- * equivalent) and then re-fetches config on success.
26
- *
27
- * `appSecret` is only sent when the merchant actually typed a new
28
- * value; if they leave the field untouched, this method receives
29
- * `undefined` for that key so the backend keeps the existing secret.
30
- */
31
- onSaveConfig: (patch: {
32
- appKey?: string;
33
- appSecret?: string;
34
- enabled?: boolean;
35
- }) => Promise<void>;
36
- /** Kick off `POST /oauth/start` and redirect to the returned URL. */
37
- onConnect: () => Promise<void>;
38
- /** Kick off `POST /oauth/disconnect` and clear merchant credentials. */
39
- onDisconnect: () => Promise<void>;
40
- /** Call `POST /test-connection` and return the result. */
41
- onTestConnection: () => Promise<AliExpressTestConnectionResult>;
42
- }
43
- /**
44
- * Amboras admin settings surface for AliExpress Dropshipping.
45
- * Renders AppKey + AppSecret inputs, Test / Connect / Disconnect
46
- * buttons, status card, and a "Coming soon" order-forwarding block.
47
- */
48
- declare function AliExpressDropshippingSettingsForm(props: AliExpressDropshippingSettingsFormProps): React.ReactElement;
49
-
50
- export { AliExpressDropshippingSettingsForm, type AliExpressDropshippingSettingsFormProps };
1
+ export { AliExpressDropshippingSettingsForm, AliExpressDropshippingSettingsFormProps } from '../index.mjs';
2
+ import '../types-Cp-7f9WI.mjs';
3
+ import 'react';
@@ -1,50 +1,3 @@
1
- import * as React from 'react';
2
- import { c as AliExpressPluginConfig, a as AliExpressConnectionStatusDto, h as AliExpressTestConnectionResult } from '../types-CLTkhq_u.js';
3
-
4
- /**
5
- * Admin settings form for the AliExpress Dropshipping integration.
6
- *
7
- * Class B: this component owns the UI and talks to the Medusa Store API
8
- * under `/store/aliexpress-dropshipping/*`. It does NOT embed any
9
- * AliExpress SDK, does not sign requests, and does not touch OAuth
10
- * tokens directly - the backend does all of that.
11
- *
12
- * The component is intentionally dependency-light (no external UI kit,
13
- * no @tanstack/react-query peer) so the admin bundle can drop it in
14
- * without extra peer dep resolution.
15
- */
16
-
17
- interface AliExpressDropshippingSettingsFormProps {
18
- /** Current config for this store (fetched by the parent server component). */
19
- initialConfig: AliExpressPluginConfig;
20
- /** Current connection status (from `GET /status`). */
21
- initialStatus: AliExpressConnectionStatusDto;
22
- /**
23
- * Persist non-secret + optionally new secret values. The parent maps
24
- * this onto `PATCH /store/aliexpress-dropshipping/config` (or
25
- * equivalent) and then re-fetches config on success.
26
- *
27
- * `appSecret` is only sent when the merchant actually typed a new
28
- * value; if they leave the field untouched, this method receives
29
- * `undefined` for that key so the backend keeps the existing secret.
30
- */
31
- onSaveConfig: (patch: {
32
- appKey?: string;
33
- appSecret?: string;
34
- enabled?: boolean;
35
- }) => Promise<void>;
36
- /** Kick off `POST /oauth/start` and redirect to the returned URL. */
37
- onConnect: () => Promise<void>;
38
- /** Kick off `POST /oauth/disconnect` and clear merchant credentials. */
39
- onDisconnect: () => Promise<void>;
40
- /** Call `POST /test-connection` and return the result. */
41
- onTestConnection: () => Promise<AliExpressTestConnectionResult>;
42
- }
43
- /**
44
- * Amboras admin settings surface for AliExpress Dropshipping.
45
- * Renders AppKey + AppSecret inputs, Test / Connect / Disconnect
46
- * buttons, status card, and a "Coming soon" order-forwarding block.
47
- */
48
- declare function AliExpressDropshippingSettingsForm(props: AliExpressDropshippingSettingsFormProps): React.ReactElement;
49
-
50
- export { AliExpressDropshippingSettingsForm, type AliExpressDropshippingSettingsFormProps };
1
+ export { AliExpressDropshippingSettingsForm, AliExpressDropshippingSettingsFormProps } from '../index.js';
2
+ import '../types-Cp-7f9WI.js';
3
+ import 'react';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/index.ts","../../src/settings-form.tsx","../../src/types.ts"],"sourcesContent":["export {\n AliExpressDropshippingSettingsForm,\n type AliExpressDropshippingSettingsFormProps,\n} from '../settings-form'\n","'use client'\n\n/**\n * Admin settings form for the AliExpress Dropshipping integration.\n *\n * Class B: this component owns the UI and talks to the Medusa Store API\n * under `/store/aliexpress-dropshipping/*`. It does NOT embed any\n * AliExpress SDK, does not sign requests, and does not touch OAuth\n * tokens directly - the backend does all of that.\n *\n * The component is intentionally dependency-light (no external UI kit,\n * no @tanstack/react-query peer) so the admin bundle can drop it in\n * without extra peer dep resolution.\n */\n\nimport * as React from 'react'\nimport type {\n AliExpressPluginConfig,\n AliExpressConnectionStatusDto,\n AliExpressTestConnectionResult,\n} from './types'\nimport { SECRET_SET_SENTINEL } from './types'\n\nexport interface AliExpressDropshippingSettingsFormProps {\n /** Current config for this store (fetched by the parent server component). */\n initialConfig: AliExpressPluginConfig\n /** Current connection status (from `GET /status`). */\n initialStatus: AliExpressConnectionStatusDto\n /**\n * Persist non-secret + optionally new secret values. The parent maps\n * this onto `PATCH /store/aliexpress-dropshipping/config` (or\n * equivalent) and then re-fetches config on success.\n *\n * `appSecret` is only sent when the merchant actually typed a new\n * value; if they leave the field untouched, this method receives\n * `undefined` for that key so the backend keeps the existing secret.\n */\n onSaveConfig: (patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n }) => Promise<void>\n /** Kick off `POST /oauth/start` and redirect to the returned URL. */\n onConnect: () => Promise<void>\n /** Kick off `POST /oauth/disconnect` and clear merchant credentials. */\n onDisconnect: () => Promise<void>\n /** Call `POST /test-connection` and return the result. */\n onTestConnection: () => Promise<AliExpressTestConnectionResult>\n}\n\ntype SaveState = 'idle' | 'saving' | 'saved' | 'error'\ntype TestState =\n | { kind: 'idle' }\n | { kind: 'running' }\n | { kind: 'ok'; categoryCount: number }\n | { kind: 'err'; code: string; msg: string }\n\n/**\n * Amboras admin settings surface for AliExpress Dropshipping.\n * Renders AppKey + AppSecret inputs, Test / Connect / Disconnect\n * buttons, status card, and a \"Coming soon\" order-forwarding block.\n */\nexport function AliExpressDropshippingSettingsForm(\n props: AliExpressDropshippingSettingsFormProps,\n): React.ReactElement {\n const {\n initialConfig,\n initialStatus,\n onSaveConfig,\n onConnect,\n onDisconnect,\n onTestConnection,\n } = props\n\n const [appKey, setAppKey] = React.useState<string>(\n initialConfig.appKey ?? '',\n )\n const secretPreset =\n initialConfig.appSecret === SECRET_SET_SENTINEL ||\n (typeof initialConfig.appSecret === 'string' &&\n initialConfig.appSecret.length > 0)\n const [appSecret, setAppSecret] = React.useState<string>('')\n const [secretDirty, setSecretDirty] = React.useState<boolean>(false)\n\n const [saveState, setSaveState] = React.useState<SaveState>('idle')\n const [saveError, setSaveError] = React.useState<string | null>(null)\n\n const [testState, setTestState] = React.useState<TestState>({ kind: 'idle' })\n const [testEverSucceeded, setTestEverSucceeded] = React.useState<boolean>(\n initialStatus.connected,\n )\n\n const [busyOAuth, setBusyOAuth] = React.useState<boolean>(false)\n\n const hasStoredSecret = secretPreset || secretDirty\n const canTest = appKey.trim().length > 0 && hasStoredSecret\n const canConnect = testEverSucceeded && !initialStatus.connected\n const canDisconnect = initialStatus.connected\n\n const handleSave = React.useCallback(async () => {\n setSaveState('saving')\n setSaveError(null)\n try {\n const patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n } = {}\n if (appKey.trim() !== (initialConfig.appKey ?? '')) {\n patch.appKey = appKey.trim()\n }\n if (secretDirty && appSecret.length > 0) {\n patch.appSecret = appSecret\n }\n await onSaveConfig(patch)\n setSaveState('saved')\n setSecretDirty(false)\n setAppSecret('')\n } catch (err) {\n setSaveState('error')\n setSaveError(err instanceof Error ? err.message : 'Save failed')\n }\n }, [appKey, appSecret, secretDirty, initialConfig.appKey, onSaveConfig])\n\n const handleTest = React.useCallback(async () => {\n setTestState({ kind: 'running' })\n try {\n const result = await onTestConnection()\n if (result.ok) {\n setTestState({ kind: 'ok', categoryCount: result.categoryCount })\n setTestEverSucceeded(true)\n } else {\n setTestState({ kind: 'err', code: result.code, msg: result.msg })\n }\n } catch (err) {\n setTestState({\n kind: 'err',\n code: 'UNKNOWN',\n msg: err instanceof Error ? err.message : 'Test failed',\n })\n }\n }, [onTestConnection])\n\n const handleConnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onConnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onConnect])\n\n const handleDisconnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onDisconnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onDisconnect])\n\n return (\n <div className=\"aliexpress-dropshipping-settings\">\n <section aria-labelledby=\"ae-credentials-heading\">\n <h2 id=\"ae-credentials-heading\">Developer app credentials</h2>\n <p>\n Enter your AliExpress Open Platform app credentials. These are\n stored encrypted at rest and used to sign every AliExpress API\n call for this store.\n </p>\n\n <label>\n <span>App Key</span>\n <input\n type=\"text\"\n value={appKey}\n onChange={(e) => setAppKey(e.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <label>\n <span>App Secret</span>\n <input\n type=\"password\"\n value={appSecret}\n placeholder={secretPreset ? '(secret set - leave blank to keep)' : ''}\n onChange={(e) => {\n setAppSecret(e.target.value)\n setSecretDirty(true)\n }}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saveState === 'saving' || appKey.trim().length === 0}\n >\n {saveState === 'saving' ? 'Saving...' : 'Save credentials'}\n </button>\n <button\n type=\"button\"\n onClick={handleTest}\n disabled={!canTest || testState.kind === 'running'}\n >\n {testState.kind === 'running' ? 'Testing...' : 'Test connection'}\n </button>\n </div>\n\n {saveState === 'saved' ? <p role=\"status\">Saved.</p> : null}\n {saveState === 'error' && saveError ? (\n <p role=\"alert\">Save failed: {saveError}</p>\n ) : null}\n\n {testState.kind === 'ok' ? (\n <p role=\"status\">\n Connected to AliExpress. Found {testState.categoryCount} categories.\n </p>\n ) : null}\n {testState.kind === 'err' ? (\n <p role=\"alert\">\n Test failed ({testState.code}): {testState.msg}\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-oauth-heading\">\n <h2 id=\"ae-oauth-heading\">Merchant account connection</h2>\n <p>\n Connect a specific AliExpress seller account to enable product\n import into this store. This is separate from the developer app\n credentials above.\n </p>\n\n {initialStatus.needsReauth ? (\n <p role=\"alert\" className=\"reauth-banner\">\n Re-authentication required. Please reconnect your AliExpress account.\n </p>\n ) : null}\n\n <div className=\"status-card\">\n <div>\n <strong>Status:</strong>{' '}\n {initialStatus.connected ? 'Connected' : 'Not connected'}\n </div>\n {initialStatus.sellerId ? (\n <div>\n <strong>Seller ID:</strong> {initialStatus.sellerId}\n </div>\n ) : null}\n {initialStatus.lastSuccessfulCallAt ? (\n <div>\n <strong>Last successful call:</strong>{' '}\n {initialStatus.lastSuccessfulCallAt}\n </div>\n ) : null}\n </div>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleConnect}\n disabled={!canConnect || busyOAuth}\n >\n {busyOAuth ? 'Working...' : 'Connect merchant AliExpress account'}\n </button>\n <button\n type=\"button\"\n onClick={handleDisconnect}\n disabled={!canDisconnect || busyOAuth}\n >\n Disconnect\n </button>\n </div>\n\n {!testEverSucceeded ? (\n <p>\n Run <em>Test connection</em> once with your AppKey/AppSecret before\n connecting a merchant account.\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-coming-soon-heading\">\n <h2 id=\"ae-coming-soon-heading\">Order forwarding</h2>\n <p>\n Coming soon. Automatic order forwarding to AliExpress requires\n our app to be approved out of Test status by AliExpress. Product\n import and browsing work today.\n </p>\n </section>\n </div>\n )\n}\n\nexport default AliExpressDropshippingSettingsForm\n","/**\n * Public types for @amboras-dev/aliexpress-dropshipping.\n *\n * These are the single source of truth for the wire shape between the\n * Amboras admin settings form and the medusa-backend-orchestrator\n * Store API routes under `/store/aliexpress-dropshipping/*`. The DTO\n * files on the medusa side must stay byte-compatible with these.\n */\n\n/**\n * Sentinel returned in place of a stored secret when the settings form\n * reads back current config. Matches the pattern used by meta-ads and\n * pinterest so the admin never sees the plaintext secret after write.\n */\nexport const SECRET_SET_SENTINEL = '__SECRET_SET__' as const\nexport type SecretSetSentinel = typeof SECRET_SET_SENTINEL\n\n/**\n * Per-store AliExpress plugin configuration.\n *\n * - `appKey` / `appSecret` are per-store developer app credentials\n * supplied by the merchant. The admin form receives\n * `SECRET_SET_SENTINEL` for `appSecret` on read, and only sends a\n * real value on user edit.\n * - `connectedSellerId` is populated after the merchant completes the\n * OAuth flow to `/rest/auth/token/create`.\n * - `needsReauth` is flipped by the backend runtime detector when it\n * sees `MissingParameter access_token` or `IllegalAccessToken`\n * sub-codes on any envelope.\n * - `lastSuccessfulCallAt` is an ISO-8601 timestamp for the status card.\n */\nexport interface AliExpressPluginConfig {\n enabled: boolean\n appKey: string | null\n appSecret: string | SecretSetSentinel | null\n connectedSellerId: string | null\n needsReauth: boolean\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * Reasons the backend surfaces when a merchant token becomes invalid.\n * Kept narrow so the admin banner can render a specific message.\n */\nexport enum AliExpressReauthReason {\n MissingAccessToken = 'missing_access_token',\n IllegalAccessToken = 'illegal_access_token',\n RefreshFailed = 'refresh_failed',\n Disconnected = 'disconnected',\n}\n\n/**\n * Category node returned by `aliexpress.ds.category.get`. The API\n * returns a flat list; the admin renders it as a tree by walking\n * `parentCategoryId`.\n */\nexport interface AliExpressCategoryDto {\n categoryId: string\n categoryName: string\n parentCategoryId: string | null\n}\n\n/**\n * Product summary shape returned by `aliexpress.ds.recommend.feed.get`.\n * Used by the \"browse products\" admin surface.\n */\nexport interface AliExpressProductSummaryDto {\n productId: string\n title: string\n imageUrl: string | null\n minPrice: number | null\n maxPrice: number | null\n currency: string | null\n productDetailUrl: string | null\n}\n\n/**\n * Full product shape returned by `aliexpress.ds.product.get`. Consumed\n * by the medusa-side mapper `aliexpressProductToMedusaDraft`.\n */\nexport interface AliExpressProductDto {\n productId: string\n title: string\n description: string | null\n images: string[]\n variants: AliExpressProductVariantDto[]\n weightGrams: number | null\n sourceUrl: string | null\n}\n\nexport interface AliExpressProductVariantDto {\n skuId: string\n title: string | null\n price: number\n currency: string\n stock: number | null\n attributes: Record<string, string>\n}\n\n/**\n * Return shape from `POST /store/aliexpress-dropshipping/products/:id/import`.\n */\nexport interface AliExpressImportResultDto {\n medusaProductId: string\n sourceProductId: string\n importedVariants: number\n}\n\n/**\n * Return shape from `GET /store/aliexpress-dropshipping/status` - drives\n * the admin status card.\n */\nexport interface AliExpressConnectionStatusDto {\n connected: boolean\n sellerId: string | null\n needsReauth: boolean\n reauthReason: AliExpressReauthReason | null\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * Return shape from `POST /store/aliexpress-dropshipping/test-connection`.\n */\nexport type AliExpressTestConnectionResult =\n | { ok: true; categoryCount: number }\n | { ok: false; code: string; msg: string }\n\nexport interface PreflightMerchantError {\n code: string\n message: string\n}\n\nexport interface PreflightAddressError {\n code: string\n message: string\n field: string\n}\n\nexport interface PreflightItemError {\n variantId: string\n productTitle: string\n code: 'CORRUPT_METADATA' | 'PRODUCT_OFFLINE' | 'OUT_OF_STOCK' | 'CANNOT_SHIP' | 'PRICE_DRIFT'\n message: string\n removable: boolean\n}\n\nexport interface PreflightResult {\n ok: boolean\n merchantErrors: PreflightMerchantError[]\n addressErrors: PreflightAddressError[]\n itemErrors: PreflightItemError[]\n warnings: Array<{ code: string; message: string }>\n degraded?: boolean\n warning?: string\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/index.ts","../../src/settings-form.tsx","../../src/types.ts"],"sourcesContent":["export {\n AliExpressDropshippingSettingsForm,\n type AliExpressDropshippingSettingsFormProps,\n} from '../settings-form'\n","'use client'\n\n/**\n * Admin settings form for the AliExpress Dropshipping integration.\n *\n * @deprecated Not used by any consumer. It models per-store AppKey/AppSecret,\n * but the real flow is a platform app key plus merchant OAuth, owned by\n * amboras-dev-orchestrator (`backend/src/modules/dropshipping`). The Medusa\n * Store API has no config, status or test-connection routes for it to call.\n *\n * The component is intentionally dependency-light (no external UI kit,\n * no @tanstack/react-query peer) so the admin bundle can drop it in\n * without extra peer dep resolution.\n */\n\nimport * as React from 'react'\nimport type {\n AliExpressPluginConfig,\n AliExpressConnectionStatusDto,\n AliExpressTestConnectionResult,\n} from './types'\nimport { SECRET_SET_SENTINEL } from './types'\n\n/** @deprecated See {@link AliExpressDropshippingSettingsForm}. */\nexport interface AliExpressDropshippingSettingsFormProps {\n /** Current config for this store (fetched by the parent server component). */\n initialConfig: AliExpressPluginConfig\n /** Current connection status (from `GET /status`). */\n initialStatus: AliExpressConnectionStatusDto\n /**\n * Persist non-secret + optionally new secret values. The parent maps\n * this onto `PATCH /store/aliexpress-dropshipping/config` (or\n * equivalent) and then re-fetches config on success.\n *\n * `appSecret` is only sent when the merchant actually typed a new\n * value; if they leave the field untouched, this method receives\n * `undefined` for that key so the backend keeps the existing secret.\n */\n onSaveConfig: (patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n }) => Promise<void>\n /** Kick off `POST /oauth/start` and redirect to the returned URL. */\n onConnect: () => Promise<void>\n /** Kick off `POST /oauth/disconnect` and clear merchant credentials. */\n onDisconnect: () => Promise<void>\n /** Call `POST /test-connection` and return the result. */\n onTestConnection: () => Promise<AliExpressTestConnectionResult>\n}\n\ntype SaveState = 'idle' | 'saving' | 'saved' | 'error'\ntype TestState =\n | { kind: 'idle' }\n | { kind: 'running' }\n | { kind: 'ok'; categoryCount: number }\n | { kind: 'err'; code: string; msg: string }\n\n/**\n * Amboras admin settings surface for AliExpress Dropshipping.\n * Renders AppKey + AppSecret inputs, Test / Connect / Disconnect\n * buttons, status card, and a \"Coming soon\" order-forwarding block.\n */\n/** @deprecated Not used by any consumer; see the file header. */\nexport function AliExpressDropshippingSettingsForm(\n props: AliExpressDropshippingSettingsFormProps,\n): React.ReactElement {\n const {\n initialConfig,\n initialStatus,\n onSaveConfig,\n onConnect,\n onDisconnect,\n onTestConnection,\n } = props\n\n const [appKey, setAppKey] = React.useState<string>(\n initialConfig.appKey ?? '',\n )\n const secretPreset =\n initialConfig.appSecret === SECRET_SET_SENTINEL ||\n (typeof initialConfig.appSecret === 'string' &&\n initialConfig.appSecret.length > 0)\n const [appSecret, setAppSecret] = React.useState<string>('')\n const [secretDirty, setSecretDirty] = React.useState<boolean>(false)\n\n const [saveState, setSaveState] = React.useState<SaveState>('idle')\n const [saveError, setSaveError] = React.useState<string | null>(null)\n\n const [testState, setTestState] = React.useState<TestState>({ kind: 'idle' })\n const [testEverSucceeded, setTestEverSucceeded] = React.useState<boolean>(\n initialStatus.connected,\n )\n\n const [busyOAuth, setBusyOAuth] = React.useState<boolean>(false)\n\n const hasStoredSecret = secretPreset || secretDirty\n const canTest = appKey.trim().length > 0 && hasStoredSecret\n const canConnect = testEverSucceeded && !initialStatus.connected\n const canDisconnect = initialStatus.connected\n\n const handleSave = React.useCallback(async () => {\n setSaveState('saving')\n setSaveError(null)\n try {\n const patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n } = {}\n if (appKey.trim() !== (initialConfig.appKey ?? '')) {\n patch.appKey = appKey.trim()\n }\n if (secretDirty && appSecret.length > 0) {\n patch.appSecret = appSecret\n }\n await onSaveConfig(patch)\n setSaveState('saved')\n setSecretDirty(false)\n setAppSecret('')\n } catch (err) {\n setSaveState('error')\n setSaveError(err instanceof Error ? err.message : 'Save failed')\n }\n }, [appKey, appSecret, secretDirty, initialConfig.appKey, onSaveConfig])\n\n const handleTest = React.useCallback(async () => {\n setTestState({ kind: 'running' })\n try {\n const result = await onTestConnection()\n if (result.ok) {\n setTestState({ kind: 'ok', categoryCount: result.categoryCount })\n setTestEverSucceeded(true)\n } else {\n setTestState({ kind: 'err', code: result.code, msg: result.msg })\n }\n } catch (err) {\n setTestState({\n kind: 'err',\n code: 'UNKNOWN',\n msg: err instanceof Error ? err.message : 'Test failed',\n })\n }\n }, [onTestConnection])\n\n const handleConnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onConnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onConnect])\n\n const handleDisconnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onDisconnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onDisconnect])\n\n return (\n <div className=\"aliexpress-dropshipping-settings\">\n <section aria-labelledby=\"ae-credentials-heading\">\n <h2 id=\"ae-credentials-heading\">Developer app credentials</h2>\n <p>\n Enter your AliExpress Open Platform app credentials. These are\n stored encrypted at rest and used to sign every AliExpress API\n call for this store.\n </p>\n\n <label>\n <span>App Key</span>\n <input\n type=\"text\"\n value={appKey}\n onChange={(e) => setAppKey(e.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <label>\n <span>App Secret</span>\n <input\n type=\"password\"\n value={appSecret}\n placeholder={secretPreset ? '(secret set - leave blank to keep)' : ''}\n onChange={(e) => {\n setAppSecret(e.target.value)\n setSecretDirty(true)\n }}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saveState === 'saving' || appKey.trim().length === 0}\n >\n {saveState === 'saving' ? 'Saving...' : 'Save credentials'}\n </button>\n <button\n type=\"button\"\n onClick={handleTest}\n disabled={!canTest || testState.kind === 'running'}\n >\n {testState.kind === 'running' ? 'Testing...' : 'Test connection'}\n </button>\n </div>\n\n {saveState === 'saved' ? <p role=\"status\">Saved.</p> : null}\n {saveState === 'error' && saveError ? (\n <p role=\"alert\">Save failed: {saveError}</p>\n ) : null}\n\n {testState.kind === 'ok' ? (\n <p role=\"status\">\n Connected to AliExpress. Found {testState.categoryCount} categories.\n </p>\n ) : null}\n {testState.kind === 'err' ? (\n <p role=\"alert\">\n Test failed ({testState.code}): {testState.msg}\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-oauth-heading\">\n <h2 id=\"ae-oauth-heading\">Merchant account connection</h2>\n <p>\n Connect a specific AliExpress seller account to enable product\n import into this store. This is separate from the developer app\n credentials above.\n </p>\n\n {initialStatus.needsReauth ? (\n <p role=\"alert\" className=\"reauth-banner\">\n Re-authentication required. Please reconnect your AliExpress account.\n </p>\n ) : null}\n\n <div className=\"status-card\">\n <div>\n <strong>Status:</strong>{' '}\n {initialStatus.connected ? 'Connected' : 'Not connected'}\n </div>\n {initialStatus.sellerId ? (\n <div>\n <strong>Seller ID:</strong> {initialStatus.sellerId}\n </div>\n ) : null}\n {initialStatus.lastSuccessfulCallAt ? (\n <div>\n <strong>Last successful call:</strong>{' '}\n {initialStatus.lastSuccessfulCallAt}\n </div>\n ) : null}\n </div>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleConnect}\n disabled={!canConnect || busyOAuth}\n >\n {busyOAuth ? 'Working...' : 'Connect merchant AliExpress account'}\n </button>\n <button\n type=\"button\"\n onClick={handleDisconnect}\n disabled={!canDisconnect || busyOAuth}\n >\n Disconnect\n </button>\n </div>\n\n {!testEverSucceeded ? (\n <p>\n Run <em>Test connection</em> once with your AppKey/AppSecret before\n connecting a merchant account.\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-coming-soon-heading\">\n <h2 id=\"ae-coming-soon-heading\">Order forwarding</h2>\n <p>\n Coming soon. Automatic order forwarding to AliExpress requires\n our app to be approved out of Test status by AliExpress. Product\n import and browsing work today.\n </p>\n </section>\n </div>\n )\n}\n\nexport default AliExpressDropshippingSettingsForm\n","/**\n * Public types for @amboras-dev/aliexpress-dropshipping.\n *\n * Only the preflight types (`PreflightResult` and friends) describe a live\n * contract: the Medusa Store API exposes exactly one route,\n * `POST /store/aliexpress-dropshipping/preflight-cart`.\n *\n * OAuth, request signing, category browsing, product import and token\n * refresh all live in amboras-dev-orchestrator\n * (`backend/src/modules/dropshipping`), not in the Medusa backend. The\n * connection/config/product DTOs below are kept only for backward\n * compatibility and are deprecated.\n */\n\n/**\n * Sentinel returned in place of a stored secret when the settings form\n * reads back current config. Matches the pattern used by meta-ads and\n * pinterest so the admin never sees the plaintext secret after write.\n */\nexport const SECRET_SET_SENTINEL = '__SECRET_SET__' as const\nexport type SecretSetSentinel = typeof SECRET_SET_SENTINEL\n\n/**\n * Per-store AliExpress plugin configuration.\n *\n * @deprecated Models per-store developer credentials, but the real flow\n * uses a platform app key plus merchant OAuth handled by\n * amboras-dev-orchestrator. No Medusa route reads or writes this shape.\n *\n * - `appKey` / `appSecret` are per-store developer app credentials\n * supplied by the merchant. The admin form receives\n * `SECRET_SET_SENTINEL` for `appSecret` on read, and only sends a\n * real value on user edit.\n * - `connectedSellerId` is populated after the merchant completes the\n * OAuth flow to `/rest/auth/token/create`.\n * - `needsReauth` is flipped by the backend runtime detector when it\n * sees `MissingParameter access_token` or `IllegalAccessToken`\n * sub-codes on any envelope.\n * - `lastSuccessfulCallAt` is an ISO-8601 timestamp for the status card.\n */\nexport interface AliExpressPluginConfig {\n enabled: boolean\n appKey: string | null\n appSecret: string | SecretSetSentinel | null\n connectedSellerId: string | null\n needsReauth: boolean\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * @deprecated Reauth state is owned by amboras-dev-orchestrator.\n *\n * Reasons the backend surfaces when a merchant token becomes invalid.\n * Kept narrow so the admin banner can render a specific message.\n */\nexport enum AliExpressReauthReason {\n MissingAccessToken = 'missing_access_token',\n IllegalAccessToken = 'illegal_access_token',\n RefreshFailed = 'refresh_failed',\n Disconnected = 'disconnected',\n}\n\n/**\n * @deprecated No Medusa route returns this; category browsing lives in\n * amboras-dev-orchestrator.\n *\n * Category node returned by `aliexpress.ds.category.get`. The API\n * returns a flat list; the admin renders it as a tree by walking\n * `parentCategoryId`.\n */\nexport interface AliExpressCategoryDto {\n categoryId: string\n categoryName: string\n parentCategoryId: string | null\n}\n\n/**\n * @deprecated No Medusa route returns this; browsing lives in\n * amboras-dev-orchestrator.\n *\n * Product summary shape returned by `aliexpress.ds.recommend.feed.get`.\n */\nexport interface AliExpressProductSummaryDto {\n productId: string\n title: string\n imageUrl: string | null\n minPrice: number | null\n maxPrice: number | null\n currency: string | null\n productDetailUrl: string | null\n}\n\n/**\n * @deprecated No Medusa route or mapper uses this; product import lives in\n * amboras-dev-orchestrator.\n *\n * Full product shape returned by `aliexpress.ds.product.get`.\n */\nexport interface AliExpressProductDto {\n productId: string\n title: string\n description: string | null\n images: string[]\n variants: AliExpressProductVariantDto[]\n weightGrams: number | null\n sourceUrl: string | null\n}\n\n/** @deprecated See {@link AliExpressProductDto}. */\nexport interface AliExpressProductVariantDto {\n skuId: string\n title: string | null\n price: number\n currency: string\n stock: number | null\n attributes: Record<string, string>\n}\n\n/**\n * @deprecated The route `POST /store/aliexpress-dropshipping/products/:id/import`\n * does not exist; import is done by amboras-dev-orchestrator.\n */\nexport interface AliExpressImportResultDto {\n medusaProductId: string\n sourceProductId: string\n importedVariants: number\n}\n\n/**\n * @deprecated The route `GET /store/aliexpress-dropshipping/status` does not\n * exist; connection status is served by amboras-dev-orchestrator.\n */\nexport interface AliExpressConnectionStatusDto {\n connected: boolean\n sellerId: string | null\n needsReauth: boolean\n reauthReason: AliExpressReauthReason | null\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * @deprecated The route `POST /store/aliexpress-dropshipping/test-connection`\n * does not exist; it is served by amboras-dev-orchestrator.\n */\nexport type AliExpressTestConnectionResult =\n | { ok: true; categoryCount: number }\n | { ok: false; code: string; msg: string }\n\n/** Known merchant-level codes; the server may add others. */\nexport type PreflightMerchantErrorCode =\n | 'MERCHANT_DISCONNECTED'\n | 'MERCHANT_UNAVAILABLE'\n | 'MERCHANT_REAUTH_REQUIRED'\n | 'SHIPPING_VERIFICATION_FAILED'\n\n/** Known address codes; the server may add others. */\nexport type PreflightAddressErrorCode =\n | 'ADDRESS_REQUIRED'\n | 'CITY_UNKNOWN'\n | 'ADDRESS_NOT_LATIN'\n | 'ADDRESS_FIELD_LENGTH'\n | 'PHONE_INVALID'\n | 'PROVINCE_REQUIRED'\n | 'COUNTRY_INVALID'\n | 'POSTAL_CODE_INVALID'\n | 'ADDRESS_INVALID'\n\nexport interface PreflightMerchantError {\n code: PreflightMerchantErrorCode | (string & {})\n message: string\n}\n\nexport interface PreflightAddressError {\n code: PreflightAddressErrorCode | (string & {})\n message: string\n field: string\n}\n\nexport interface PreflightItemError {\n variantId: string\n productTitle: string\n code:\n | 'CORRUPT_METADATA'\n | 'PRODUCT_OFFLINE'\n | 'OUT_OF_STOCK'\n | 'CANNOT_SHIP'\n /** @deprecated Price drift is only ever reported as a warning ({@link PreflightWarningCode}), never as an item error. Kept for backward compatibility. */\n | 'PRICE_DRIFT'\n | 'SHIPPING_VERIFICATION_FAILED'\n message: string\n removable: boolean\n}\n\n/** Codes that appear in `PreflightResult.warnings`. Non-blocking. */\nexport type PreflightWarningCode = 'PRICE_DRIFT' | 'STOCK_UNKNOWN' | 'FREIGHT_EXPENSIVE'\n\nexport interface PreflightWarning {\n /** Known codes are listed in {@link PreflightWarningCode}; other strings may be added by the server. */\n code: PreflightWarningCode | (string & {})\n message: string\n}\n\nexport interface PreflightResult {\n ok: boolean\n merchantErrors: PreflightMerchantError[]\n addressErrors: PreflightAddressError[]\n itemErrors: PreflightItemError[]\n warnings: PreflightWarning[]\n degraded?: boolean\n warning?: string\n}\n\n/**\n * Body of the 429 the preflight route returns when a cart or IP is checked too often.\n * `Retry-After` carries the same number of seconds as `retryAfter`.\n */\nexport interface PreflightRateLimitedResponse {\n error: string\n code: 'rate_limited'\n retryAfter: number\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACeA,YAAuB;;;ACIhB,IAAM,sBAAsB;;;ADmJ3B;AAtGD,SAAS,mCACd,OACoB;AACpB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,CAAC,QAAQ,SAAS,IAAU;AAAA,IAChC,cAAc,UAAU;AAAA,EAC1B;AACA,QAAM,eACJ,cAAc,cAAc,uBAC3B,OAAO,cAAc,cAAc,YAClC,cAAc,UAAU,SAAS;AACrC,QAAM,CAAC,WAAW,YAAY,IAAU,eAAiB,EAAE;AAC3D,QAAM,CAAC,aAAa,cAAc,IAAU,eAAkB,KAAK;AAEnE,QAAM,CAAC,WAAW,YAAY,IAAU,eAAoB,MAAM;AAClE,QAAM,CAAC,WAAW,YAAY,IAAU,eAAwB,IAAI;AAEpE,QAAM,CAAC,WAAW,YAAY,IAAU,eAAoB,EAAE,MAAM,OAAO,CAAC;AAC5E,QAAM,CAAC,mBAAmB,oBAAoB,IAAU;AAAA,IACtD,cAAc;AAAA,EAChB;AAEA,QAAM,CAAC,WAAW,YAAY,IAAU,eAAkB,KAAK;AAE/D,QAAM,kBAAkB,gBAAgB;AACxC,QAAM,UAAU,OAAO,KAAK,EAAE,SAAS,KAAK;AAC5C,QAAM,aAAa,qBAAqB,CAAC,cAAc;AACvD,QAAM,gBAAgB,cAAc;AAEpC,QAAM,aAAmB,kBAAY,YAAY;AAC/C,iBAAa,QAAQ;AACrB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,QAIF,CAAC;AACL,UAAI,OAAO,KAAK,OAAO,cAAc,UAAU,KAAK;AAClD,cAAM,SAAS,OAAO,KAAK;AAAA,MAC7B;AACA,UAAI,eAAe,UAAU,SAAS,GAAG;AACvC,cAAM,YAAY;AAAA,MACpB;AACA,YAAM,aAAa,KAAK;AACxB,mBAAa,OAAO;AACpB,qBAAe,KAAK;AACpB,mBAAa,EAAE;AAAA,IACjB,SAAS,KAAK;AACZ,mBAAa,OAAO;AACpB,mBAAa,eAAe,QAAQ,IAAI,UAAU,aAAa;AAAA,IACjE;AAAA,EACF,GAAG,CAAC,QAAQ,WAAW,aAAa,cAAc,QAAQ,YAAY,CAAC;AAEvE,QAAM,aAAmB,kBAAY,YAAY;AAC/C,iBAAa,EAAE,MAAM,UAAU,CAAC;AAChC,QAAI;AACF,YAAM,SAAS,MAAM,iBAAiB;AACtC,UAAI,OAAO,IAAI;AACb,qBAAa,EAAE,MAAM,MAAM,eAAe,OAAO,cAAc,CAAC;AAChE,6BAAqB,IAAI;AAAA,MAC3B,OAAO;AACL,qBAAa,EAAE,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA,MAClE;AAAA,IACF,SAAS,KAAK;AACZ,mBAAa;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,QACN,KAAK,eAAe,QAAQ,IAAI,UAAU;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,gBAAsB,kBAAY,YAAY;AAClD,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,UAAU;AAAA,IAClB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,mBAAyB,kBAAY,YAAY;AACrD,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,aAAa;AAAA,IACrB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SACE,6CAAC,SAAI,WAAU,oCACb;AAAA,iDAAC,aAAQ,mBAAgB,0BACvB;AAAA,kDAAC,QAAG,IAAG,0BAAyB,uCAAyB;AAAA,MACzD,4CAAC,OAAE,gKAIH;AAAA,MAEA,6CAAC,WACC;AAAA,oDAAC,UAAK,qBAAO;AAAA,QACb;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA,YACzC,cAAa;AAAA,YACb,YAAY;AAAA;AAAA,QACd;AAAA,SACF;AAAA,MAEA,6CAAC,WACC;AAAA,oDAAC,UAAK,wBAAU;AAAA,QAChB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa,eAAe,uCAAuC;AAAA,YACnE,UAAU,CAAC,MAAM;AACf,2BAAa,EAAE,OAAO,KAAK;AAC3B,6BAAe,IAAI;AAAA,YACrB;AAAA,YACA,cAAa;AAAA,YACb,YAAY;AAAA;AAAA,QACd;AAAA,SACF;AAAA,MAEA,6CAAC,SAAI,WAAU,WACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU,cAAc,YAAY,OAAO,KAAK,EAAE,WAAW;AAAA,YAE5D,wBAAc,WAAW,cAAc;AAAA;AAAA,QAC1C;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU,CAAC,WAAW,UAAU,SAAS;AAAA,YAExC,oBAAU,SAAS,YAAY,eAAe;AAAA;AAAA,QACjD;AAAA,SACF;AAAA,MAEC,cAAc,UAAU,4CAAC,OAAE,MAAK,UAAS,oBAAM,IAAO;AAAA,MACtD,cAAc,WAAW,YACxB,6CAAC,OAAE,MAAK,SAAQ;AAAA;AAAA,QAAc;AAAA,SAAU,IACtC;AAAA,MAEH,UAAU,SAAS,OAClB,6CAAC,OAAE,MAAK,UAAS;AAAA;AAAA,QACiB,UAAU;AAAA,QAAc;AAAA,SAC1D,IACE;AAAA,MACH,UAAU,SAAS,QAClB,6CAAC,OAAE,MAAK,SAAQ;AAAA;AAAA,QACA,UAAU;AAAA,QAAK;AAAA,QAAI,UAAU;AAAA,SAC7C,IACE;AAAA,OACN;AAAA,IAEA,6CAAC,aAAQ,mBAAgB,oBACvB;AAAA,kDAAC,QAAG,IAAG,oBAAmB,yCAA2B;AAAA,MACrD,4CAAC,OAAE,+JAIH;AAAA,MAEC,cAAc,cACb,4CAAC,OAAE,MAAK,SAAQ,WAAU,iBAAgB,mFAE1C,IACE;AAAA,MAEJ,6CAAC,SAAI,WAAU,eACb;AAAA,qDAAC,SACC;AAAA,sDAAC,YAAO,qBAAO;AAAA,UAAU;AAAA,UACxB,cAAc,YAAY,cAAc;AAAA,WAC3C;AAAA,QACC,cAAc,WACb,6CAAC,SACC;AAAA,sDAAC,YAAO,wBAAU;AAAA,UAAS;AAAA,UAAE,cAAc;AAAA,WAC7C,IACE;AAAA,QACH,cAAc,uBACb,6CAAC,SACC;AAAA,sDAAC,YAAO,mCAAqB;AAAA,UAAU;AAAA,UACtC,cAAc;AAAA,WACjB,IACE;AAAA,SACN;AAAA,MAEA,6CAAC,SAAI,WAAU,WACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU,CAAC,cAAc;AAAA,YAExB,sBAAY,eAAe;AAAA;AAAA,QAC9B;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU,CAAC,iBAAiB;AAAA,YAC7B;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MAEC,CAAC,oBACA,6CAAC,OAAE;AAAA;AAAA,QACG,4CAAC,QAAG,6BAAe;AAAA,QAAK;AAAA,SAE9B,IACE;AAAA,OACN;AAAA,IAEA,6CAAC,aAAQ,mBAAgB,0BACvB;AAAA,kDAAC,QAAG,IAAG,0BAAyB,8BAAgB;AAAA,MAChD,4CAAC,OAAE,6KAIH;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AliExpressDropshippingSettingsForm
3
- } from "../chunk-65TJ4JF6.mjs";
3
+ } from "../chunk-XSSYR25X.mjs";
4
4
  export {
5
5
  AliExpressDropshippingSettingsForm
6
6
  };
@@ -221,4 +221,4 @@ export {
221
221
  AliExpressReauthReason,
222
222
  AliExpressDropshippingSettingsForm
223
223
  };
224
- //# sourceMappingURL=chunk-65TJ4JF6.mjs.map
224
+ //# sourceMappingURL=chunk-XSSYR25X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/settings-form.tsx"],"sourcesContent":["/**\n * Public types for @amboras-dev/aliexpress-dropshipping.\n *\n * Only the preflight types (`PreflightResult` and friends) describe a live\n * contract: the Medusa Store API exposes exactly one route,\n * `POST /store/aliexpress-dropshipping/preflight-cart`.\n *\n * OAuth, request signing, category browsing, product import and token\n * refresh all live in amboras-dev-orchestrator\n * (`backend/src/modules/dropshipping`), not in the Medusa backend. The\n * connection/config/product DTOs below are kept only for backward\n * compatibility and are deprecated.\n */\n\n/**\n * Sentinel returned in place of a stored secret when the settings form\n * reads back current config. Matches the pattern used by meta-ads and\n * pinterest so the admin never sees the plaintext secret after write.\n */\nexport const SECRET_SET_SENTINEL = '__SECRET_SET__' as const\nexport type SecretSetSentinel = typeof SECRET_SET_SENTINEL\n\n/**\n * Per-store AliExpress plugin configuration.\n *\n * @deprecated Models per-store developer credentials, but the real flow\n * uses a platform app key plus merchant OAuth handled by\n * amboras-dev-orchestrator. No Medusa route reads or writes this shape.\n *\n * - `appKey` / `appSecret` are per-store developer app credentials\n * supplied by the merchant. The admin form receives\n * `SECRET_SET_SENTINEL` for `appSecret` on read, and only sends a\n * real value on user edit.\n * - `connectedSellerId` is populated after the merchant completes the\n * OAuth flow to `/rest/auth/token/create`.\n * - `needsReauth` is flipped by the backend runtime detector when it\n * sees `MissingParameter access_token` or `IllegalAccessToken`\n * sub-codes on any envelope.\n * - `lastSuccessfulCallAt` is an ISO-8601 timestamp for the status card.\n */\nexport interface AliExpressPluginConfig {\n enabled: boolean\n appKey: string | null\n appSecret: string | SecretSetSentinel | null\n connectedSellerId: string | null\n needsReauth: boolean\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * @deprecated Reauth state is owned by amboras-dev-orchestrator.\n *\n * Reasons the backend surfaces when a merchant token becomes invalid.\n * Kept narrow so the admin banner can render a specific message.\n */\nexport enum AliExpressReauthReason {\n MissingAccessToken = 'missing_access_token',\n IllegalAccessToken = 'illegal_access_token',\n RefreshFailed = 'refresh_failed',\n Disconnected = 'disconnected',\n}\n\n/**\n * @deprecated No Medusa route returns this; category browsing lives in\n * amboras-dev-orchestrator.\n *\n * Category node returned by `aliexpress.ds.category.get`. The API\n * returns a flat list; the admin renders it as a tree by walking\n * `parentCategoryId`.\n */\nexport interface AliExpressCategoryDto {\n categoryId: string\n categoryName: string\n parentCategoryId: string | null\n}\n\n/**\n * @deprecated No Medusa route returns this; browsing lives in\n * amboras-dev-orchestrator.\n *\n * Product summary shape returned by `aliexpress.ds.recommend.feed.get`.\n */\nexport interface AliExpressProductSummaryDto {\n productId: string\n title: string\n imageUrl: string | null\n minPrice: number | null\n maxPrice: number | null\n currency: string | null\n productDetailUrl: string | null\n}\n\n/**\n * @deprecated No Medusa route or mapper uses this; product import lives in\n * amboras-dev-orchestrator.\n *\n * Full product shape returned by `aliexpress.ds.product.get`.\n */\nexport interface AliExpressProductDto {\n productId: string\n title: string\n description: string | null\n images: string[]\n variants: AliExpressProductVariantDto[]\n weightGrams: number | null\n sourceUrl: string | null\n}\n\n/** @deprecated See {@link AliExpressProductDto}. */\nexport interface AliExpressProductVariantDto {\n skuId: string\n title: string | null\n price: number\n currency: string\n stock: number | null\n attributes: Record<string, string>\n}\n\n/**\n * @deprecated The route `POST /store/aliexpress-dropshipping/products/:id/import`\n * does not exist; import is done by amboras-dev-orchestrator.\n */\nexport interface AliExpressImportResultDto {\n medusaProductId: string\n sourceProductId: string\n importedVariants: number\n}\n\n/**\n * @deprecated The route `GET /store/aliexpress-dropshipping/status` does not\n * exist; connection status is served by amboras-dev-orchestrator.\n */\nexport interface AliExpressConnectionStatusDto {\n connected: boolean\n sellerId: string | null\n needsReauth: boolean\n reauthReason: AliExpressReauthReason | null\n lastSuccessfulCallAt: string | null\n}\n\n/**\n * @deprecated The route `POST /store/aliexpress-dropshipping/test-connection`\n * does not exist; it is served by amboras-dev-orchestrator.\n */\nexport type AliExpressTestConnectionResult =\n | { ok: true; categoryCount: number }\n | { ok: false; code: string; msg: string }\n\n/** Known merchant-level codes; the server may add others. */\nexport type PreflightMerchantErrorCode =\n | 'MERCHANT_DISCONNECTED'\n | 'MERCHANT_UNAVAILABLE'\n | 'MERCHANT_REAUTH_REQUIRED'\n | 'SHIPPING_VERIFICATION_FAILED'\n\n/** Known address codes; the server may add others. */\nexport type PreflightAddressErrorCode =\n | 'ADDRESS_REQUIRED'\n | 'CITY_UNKNOWN'\n | 'ADDRESS_NOT_LATIN'\n | 'ADDRESS_FIELD_LENGTH'\n | 'PHONE_INVALID'\n | 'PROVINCE_REQUIRED'\n | 'COUNTRY_INVALID'\n | 'POSTAL_CODE_INVALID'\n | 'ADDRESS_INVALID'\n\nexport interface PreflightMerchantError {\n code: PreflightMerchantErrorCode | (string & {})\n message: string\n}\n\nexport interface PreflightAddressError {\n code: PreflightAddressErrorCode | (string & {})\n message: string\n field: string\n}\n\nexport interface PreflightItemError {\n variantId: string\n productTitle: string\n code:\n | 'CORRUPT_METADATA'\n | 'PRODUCT_OFFLINE'\n | 'OUT_OF_STOCK'\n | 'CANNOT_SHIP'\n /** @deprecated Price drift is only ever reported as a warning ({@link PreflightWarningCode}), never as an item error. Kept for backward compatibility. */\n | 'PRICE_DRIFT'\n | 'SHIPPING_VERIFICATION_FAILED'\n message: string\n removable: boolean\n}\n\n/** Codes that appear in `PreflightResult.warnings`. Non-blocking. */\nexport type PreflightWarningCode = 'PRICE_DRIFT' | 'STOCK_UNKNOWN' | 'FREIGHT_EXPENSIVE'\n\nexport interface PreflightWarning {\n /** Known codes are listed in {@link PreflightWarningCode}; other strings may be added by the server. */\n code: PreflightWarningCode | (string & {})\n message: string\n}\n\nexport interface PreflightResult {\n ok: boolean\n merchantErrors: PreflightMerchantError[]\n addressErrors: PreflightAddressError[]\n itemErrors: PreflightItemError[]\n warnings: PreflightWarning[]\n degraded?: boolean\n warning?: string\n}\n\n/**\n * Body of the 429 the preflight route returns when a cart or IP is checked too often.\n * `Retry-After` carries the same number of seconds as `retryAfter`.\n */\nexport interface PreflightRateLimitedResponse {\n error: string\n code: 'rate_limited'\n retryAfter: number\n}\n","'use client'\n\n/**\n * Admin settings form for the AliExpress Dropshipping integration.\n *\n * @deprecated Not used by any consumer. It models per-store AppKey/AppSecret,\n * but the real flow is a platform app key plus merchant OAuth, owned by\n * amboras-dev-orchestrator (`backend/src/modules/dropshipping`). The Medusa\n * Store API has no config, status or test-connection routes for it to call.\n *\n * The component is intentionally dependency-light (no external UI kit,\n * no @tanstack/react-query peer) so the admin bundle can drop it in\n * without extra peer dep resolution.\n */\n\nimport * as React from 'react'\nimport type {\n AliExpressPluginConfig,\n AliExpressConnectionStatusDto,\n AliExpressTestConnectionResult,\n} from './types'\nimport { SECRET_SET_SENTINEL } from './types'\n\n/** @deprecated See {@link AliExpressDropshippingSettingsForm}. */\nexport interface AliExpressDropshippingSettingsFormProps {\n /** Current config for this store (fetched by the parent server component). */\n initialConfig: AliExpressPluginConfig\n /** Current connection status (from `GET /status`). */\n initialStatus: AliExpressConnectionStatusDto\n /**\n * Persist non-secret + optionally new secret values. The parent maps\n * this onto `PATCH /store/aliexpress-dropshipping/config` (or\n * equivalent) and then re-fetches config on success.\n *\n * `appSecret` is only sent when the merchant actually typed a new\n * value; if they leave the field untouched, this method receives\n * `undefined` for that key so the backend keeps the existing secret.\n */\n onSaveConfig: (patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n }) => Promise<void>\n /** Kick off `POST /oauth/start` and redirect to the returned URL. */\n onConnect: () => Promise<void>\n /** Kick off `POST /oauth/disconnect` and clear merchant credentials. */\n onDisconnect: () => Promise<void>\n /** Call `POST /test-connection` and return the result. */\n onTestConnection: () => Promise<AliExpressTestConnectionResult>\n}\n\ntype SaveState = 'idle' | 'saving' | 'saved' | 'error'\ntype TestState =\n | { kind: 'idle' }\n | { kind: 'running' }\n | { kind: 'ok'; categoryCount: number }\n | { kind: 'err'; code: string; msg: string }\n\n/**\n * Amboras admin settings surface for AliExpress Dropshipping.\n * Renders AppKey + AppSecret inputs, Test / Connect / Disconnect\n * buttons, status card, and a \"Coming soon\" order-forwarding block.\n */\n/** @deprecated Not used by any consumer; see the file header. */\nexport function AliExpressDropshippingSettingsForm(\n props: AliExpressDropshippingSettingsFormProps,\n): React.ReactElement {\n const {\n initialConfig,\n initialStatus,\n onSaveConfig,\n onConnect,\n onDisconnect,\n onTestConnection,\n } = props\n\n const [appKey, setAppKey] = React.useState<string>(\n initialConfig.appKey ?? '',\n )\n const secretPreset =\n initialConfig.appSecret === SECRET_SET_SENTINEL ||\n (typeof initialConfig.appSecret === 'string' &&\n initialConfig.appSecret.length > 0)\n const [appSecret, setAppSecret] = React.useState<string>('')\n const [secretDirty, setSecretDirty] = React.useState<boolean>(false)\n\n const [saveState, setSaveState] = React.useState<SaveState>('idle')\n const [saveError, setSaveError] = React.useState<string | null>(null)\n\n const [testState, setTestState] = React.useState<TestState>({ kind: 'idle' })\n const [testEverSucceeded, setTestEverSucceeded] = React.useState<boolean>(\n initialStatus.connected,\n )\n\n const [busyOAuth, setBusyOAuth] = React.useState<boolean>(false)\n\n const hasStoredSecret = secretPreset || secretDirty\n const canTest = appKey.trim().length > 0 && hasStoredSecret\n const canConnect = testEverSucceeded && !initialStatus.connected\n const canDisconnect = initialStatus.connected\n\n const handleSave = React.useCallback(async () => {\n setSaveState('saving')\n setSaveError(null)\n try {\n const patch: {\n appKey?: string\n appSecret?: string\n enabled?: boolean\n } = {}\n if (appKey.trim() !== (initialConfig.appKey ?? '')) {\n patch.appKey = appKey.trim()\n }\n if (secretDirty && appSecret.length > 0) {\n patch.appSecret = appSecret\n }\n await onSaveConfig(patch)\n setSaveState('saved')\n setSecretDirty(false)\n setAppSecret('')\n } catch (err) {\n setSaveState('error')\n setSaveError(err instanceof Error ? err.message : 'Save failed')\n }\n }, [appKey, appSecret, secretDirty, initialConfig.appKey, onSaveConfig])\n\n const handleTest = React.useCallback(async () => {\n setTestState({ kind: 'running' })\n try {\n const result = await onTestConnection()\n if (result.ok) {\n setTestState({ kind: 'ok', categoryCount: result.categoryCount })\n setTestEverSucceeded(true)\n } else {\n setTestState({ kind: 'err', code: result.code, msg: result.msg })\n }\n } catch (err) {\n setTestState({\n kind: 'err',\n code: 'UNKNOWN',\n msg: err instanceof Error ? err.message : 'Test failed',\n })\n }\n }, [onTestConnection])\n\n const handleConnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onConnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onConnect])\n\n const handleDisconnect = React.useCallback(async () => {\n setBusyOAuth(true)\n try {\n await onDisconnect()\n } finally {\n setBusyOAuth(false)\n }\n }, [onDisconnect])\n\n return (\n <div className=\"aliexpress-dropshipping-settings\">\n <section aria-labelledby=\"ae-credentials-heading\">\n <h2 id=\"ae-credentials-heading\">Developer app credentials</h2>\n <p>\n Enter your AliExpress Open Platform app credentials. These are\n stored encrypted at rest and used to sign every AliExpress API\n call for this store.\n </p>\n\n <label>\n <span>App Key</span>\n <input\n type=\"text\"\n value={appKey}\n onChange={(e) => setAppKey(e.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <label>\n <span>App Secret</span>\n <input\n type=\"password\"\n value={appSecret}\n placeholder={secretPreset ? '(secret set - leave blank to keep)' : ''}\n onChange={(e) => {\n setAppSecret(e.target.value)\n setSecretDirty(true)\n }}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saveState === 'saving' || appKey.trim().length === 0}\n >\n {saveState === 'saving' ? 'Saving...' : 'Save credentials'}\n </button>\n <button\n type=\"button\"\n onClick={handleTest}\n disabled={!canTest || testState.kind === 'running'}\n >\n {testState.kind === 'running' ? 'Testing...' : 'Test connection'}\n </button>\n </div>\n\n {saveState === 'saved' ? <p role=\"status\">Saved.</p> : null}\n {saveState === 'error' && saveError ? (\n <p role=\"alert\">Save failed: {saveError}</p>\n ) : null}\n\n {testState.kind === 'ok' ? (\n <p role=\"status\">\n Connected to AliExpress. Found {testState.categoryCount} categories.\n </p>\n ) : null}\n {testState.kind === 'err' ? (\n <p role=\"alert\">\n Test failed ({testState.code}): {testState.msg}\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-oauth-heading\">\n <h2 id=\"ae-oauth-heading\">Merchant account connection</h2>\n <p>\n Connect a specific AliExpress seller account to enable product\n import into this store. This is separate from the developer app\n credentials above.\n </p>\n\n {initialStatus.needsReauth ? (\n <p role=\"alert\" className=\"reauth-banner\">\n Re-authentication required. Please reconnect your AliExpress account.\n </p>\n ) : null}\n\n <div className=\"status-card\">\n <div>\n <strong>Status:</strong>{' '}\n {initialStatus.connected ? 'Connected' : 'Not connected'}\n </div>\n {initialStatus.sellerId ? (\n <div>\n <strong>Seller ID:</strong> {initialStatus.sellerId}\n </div>\n ) : null}\n {initialStatus.lastSuccessfulCallAt ? (\n <div>\n <strong>Last successful call:</strong>{' '}\n {initialStatus.lastSuccessfulCallAt}\n </div>\n ) : null}\n </div>\n\n <div className=\"actions\">\n <button\n type=\"button\"\n onClick={handleConnect}\n disabled={!canConnect || busyOAuth}\n >\n {busyOAuth ? 'Working...' : 'Connect merchant AliExpress account'}\n </button>\n <button\n type=\"button\"\n onClick={handleDisconnect}\n disabled={!canDisconnect || busyOAuth}\n >\n Disconnect\n </button>\n </div>\n\n {!testEverSucceeded ? (\n <p>\n Run <em>Test connection</em> once with your AppKey/AppSecret before\n connecting a merchant account.\n </p>\n ) : null}\n </section>\n\n <section aria-labelledby=\"ae-coming-soon-heading\">\n <h2 id=\"ae-coming-soon-heading\">Order forwarding</h2>\n <p>\n Coming soon. Automatic order forwarding to AliExpress requires\n our app to be approved out of Test status by AliExpress. Product\n import and browsing work today.\n </p>\n </section>\n </div>\n )\n}\n\nexport default AliExpressDropshippingSettingsForm\n"],"mappings":"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","names":["AliExpressReauthReason"]}
@@ -1,11 +1,17 @@
1
- import { P as PreflightResult } from '../types-CLTkhq_u.mjs';
2
- export { j as PreflightAddressError, k as PreflightItemError, l as PreflightMerchantError } from '../types-CLTkhq_u.mjs';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import { P as PreflightResult } from '../types-Cp-7f9WI.mjs';
2
+ export { a as PreflightAddressError, b as PreflightAddressErrorCode, c as PreflightItemError, d as PreflightMerchantError, e as PreflightMerchantErrorCode, f as PreflightRateLimitedResponse } from '../types-Cp-7f9WI.mjs';
4
3
 
5
4
  interface UseAliexpressPreflightOptions {
6
5
  baseUrl?: string;
7
6
  publishableKey?: string;
8
7
  }
8
+ /**
9
+ * Calls `POST /store/aliexpress-dropshipping/preflight-cart`.
10
+ *
11
+ * Returns `{ mutateAsync, isPending }`, not a `PreflightResult`.
12
+ * `mutateAsync({ cartId })` resolves to the `PreflightResult` and rejects on a
13
+ * non-2xx response.
14
+ */
9
15
  declare function useAliexpressPreflight(options?: UseAliexpressPreflightOptions): {
10
16
  mutateAsync: ({ cartId }: {
11
17
  cartId: string;
@@ -13,34 +19,4 @@ declare function useAliexpressPreflight(options?: UseAliexpressPreflightOptions)
13
19
  isPending: boolean;
14
20
  };
15
21
 
16
- interface AliexpressCheckoutGuardProps {
17
- /**
18
- * Cart id to preflight. Passed by the storefront checkout page or the plugin slot's
19
- * `context.cartId`. If undefined (cart not yet loaded), the guard renders nothing.
20
- */
21
- cartId?: string;
22
- /** Optional TanStack Query hook options (e.g. custom baseUrl for tests). */
23
- options?: UseAliexpressPreflightOptions;
24
- /** Optional callback merchant storefronts can wire to react to the block state. */
25
- onBlock?: (result: PreflightResult) => void;
26
- }
27
- /**
28
- * Standalone slot-compatible AliExpress preflight component.
29
- *
30
- * Rendered by the plugin registry at the `checkoutStart` slot. On mount + whenever
31
- * `cartId` changes, runs the pre-payment shipping/address/OAuth check. If any AE line
32
- * item is unfulfillable, renders an inline warning block above the checkout form.
33
- *
34
- * NOTE: this is a SOFT warning (not a hard block on the pay button). For hard blocking,
35
- * the merchant's checkout page should call `useAliexpressPreflight` directly and gate
36
- * its own `cart.complete()` on the result. See package README for both integration
37
- * patterns.
38
- *
39
- * Fail-open: if the preflight endpoint is unreachable, the guard renders nothing rather
40
- * than block checkout. Better to occasionally let through an unfulfillable order (which
41
- * downstream retry + refund handles) than to lock the merchant out of checkouts on a
42
- * transient outage.
43
- */
44
- declare function AliexpressCheckoutGuard({ cartId, options, onBlock }: AliexpressCheckoutGuardProps): react_jsx_runtime.JSX.Element | null;
45
-
46
- export { AliexpressCheckoutGuard, type AliexpressCheckoutGuardProps, PreflightResult, type UseAliexpressPreflightOptions, useAliexpressPreflight };
22
+ export { PreflightResult, type UseAliexpressPreflightOptions, useAliexpressPreflight };
@@ -1,11 +1,17 @@
1
- import { P as PreflightResult } from '../types-CLTkhq_u.js';
2
- export { j as PreflightAddressError, k as PreflightItemError, l as PreflightMerchantError } from '../types-CLTkhq_u.js';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import { P as PreflightResult } from '../types-Cp-7f9WI.js';
2
+ export { a as PreflightAddressError, b as PreflightAddressErrorCode, c as PreflightItemError, d as PreflightMerchantError, e as PreflightMerchantErrorCode, f as PreflightRateLimitedResponse } from '../types-Cp-7f9WI.js';
4
3
 
5
4
  interface UseAliexpressPreflightOptions {
6
5
  baseUrl?: string;
7
6
  publishableKey?: string;
8
7
  }
8
+ /**
9
+ * Calls `POST /store/aliexpress-dropshipping/preflight-cart`.
10
+ *
11
+ * Returns `{ mutateAsync, isPending }`, not a `PreflightResult`.
12
+ * `mutateAsync({ cartId })` resolves to the `PreflightResult` and rejects on a
13
+ * non-2xx response.
14
+ */
9
15
  declare function useAliexpressPreflight(options?: UseAliexpressPreflightOptions): {
10
16
  mutateAsync: ({ cartId }: {
11
17
  cartId: string;
@@ -13,34 +19,4 @@ declare function useAliexpressPreflight(options?: UseAliexpressPreflightOptions)
13
19
  isPending: boolean;
14
20
  };
15
21
 
16
- interface AliexpressCheckoutGuardProps {
17
- /**
18
- * Cart id to preflight. Passed by the storefront checkout page or the plugin slot's
19
- * `context.cartId`. If undefined (cart not yet loaded), the guard renders nothing.
20
- */
21
- cartId?: string;
22
- /** Optional TanStack Query hook options (e.g. custom baseUrl for tests). */
23
- options?: UseAliexpressPreflightOptions;
24
- /** Optional callback merchant storefronts can wire to react to the block state. */
25
- onBlock?: (result: PreflightResult) => void;
26
- }
27
- /**
28
- * Standalone slot-compatible AliExpress preflight component.
29
- *
30
- * Rendered by the plugin registry at the `checkoutStart` slot. On mount + whenever
31
- * `cartId` changes, runs the pre-payment shipping/address/OAuth check. If any AE line
32
- * item is unfulfillable, renders an inline warning block above the checkout form.
33
- *
34
- * NOTE: this is a SOFT warning (not a hard block on the pay button). For hard blocking,
35
- * the merchant's checkout page should call `useAliexpressPreflight` directly and gate
36
- * its own `cart.complete()` on the result. See package README for both integration
37
- * patterns.
38
- *
39
- * Fail-open: if the preflight endpoint is unreachable, the guard renders nothing rather
40
- * than block checkout. Better to occasionally let through an unfulfillable order (which
41
- * downstream retry + refund handles) than to lock the merchant out of checkouts on a
42
- * transient outage.
43
- */
44
- declare function AliexpressCheckoutGuard({ cartId, options, onBlock }: AliexpressCheckoutGuardProps): react_jsx_runtime.JSX.Element | null;
45
-
46
- export { AliexpressCheckoutGuard, type AliexpressCheckoutGuardProps, PreflightResult, type UseAliexpressPreflightOptions, useAliexpressPreflight };
22
+ export { PreflightResult, type UseAliexpressPreflightOptions, useAliexpressPreflight };
@@ -20,7 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/client/index.ts
21
21
  var client_exports = {};
22
22
  __export(client_exports, {
23
- AliexpressCheckoutGuard: () => AliexpressCheckoutGuard,
24
23
  useAliexpressPreflight: () => useAliexpressPreflight
25
24
  });
26
25
  module.exports = __toCommonJS(client_exports);
@@ -50,67 +49,8 @@ function useAliexpressPreflight(options = {}) {
50
49
  }, [options.baseUrl, options.publishableKey]);
51
50
  return { mutateAsync, isPending };
52
51
  }
53
-
54
- // src/client/preflight-guard.tsx
55
- var import_react2 = require("react");
56
- var import_jsx_runtime = require("react/jsx-runtime");
57
- function AliexpressCheckoutGuard({ cartId, options, onBlock }) {
58
- const preflight = useAliexpressPreflight(options);
59
- const [blockedResult, setBlockedResult] = (0, import_react2.useState)(null);
60
- (0, import_react2.useEffect)(() => {
61
- if (!cartId) return;
62
- let cancelled = false;
63
- void preflight.mutateAsync({ cartId }).then((result) => {
64
- if (cancelled) return;
65
- if (!result.ok) {
66
- setBlockedResult(result);
67
- onBlock?.(result);
68
- } else {
69
- setBlockedResult(null);
70
- }
71
- }).catch(() => {
72
- if (!cancelled) setBlockedResult(null);
73
- });
74
- return () => {
75
- cancelled = true;
76
- };
77
- }, [cartId]);
78
- if (!blockedResult) return null;
79
- const itemErrors = blockedResult.itemErrors ?? [];
80
- const addressErrors = blockedResult.addressErrors ?? [];
81
- const merchantErrors = blockedResult.merchantErrors ?? [];
82
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
83
- "div",
84
- {
85
- role: "alert",
86
- style: {
87
- border: "1px solid #f5c2c7",
88
- background: "#f8d7da",
89
- color: "#842029",
90
- padding: "12px 16px",
91
- borderRadius: 8,
92
- marginBottom: 16,
93
- fontSize: 14,
94
- lineHeight: 1.4
95
- },
96
- children: [
97
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { display: "block", marginBottom: 6 }, children: "Some items in your cart can't be shipped" }),
98
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { margin: 0, paddingLeft: 20 }, children: [
99
- itemErrors.map((err) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
100
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: err.productTitle }),
101
- ": ",
102
- err.message
103
- ] }, err.variantId)),
104
- addressErrors.map((err, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: err.message }, `addr-${idx}`)),
105
- merchantErrors.map((err, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: err.message }, `merchant-${idx}`))
106
- ] })
107
- ]
108
- }
109
- );
110
- }
111
52
  // Annotate the CommonJS export names for ESM import in node:
112
53
  0 && (module.exports = {
113
- AliexpressCheckoutGuard,
114
54
  useAliexpressPreflight
115
55
  });
116
56
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts","../../src/client/use-preflight.ts","../../src/client/preflight-guard.tsx"],"sourcesContent":["export { useAliexpressPreflight, type UseAliexpressPreflightOptions } from './use-preflight'\nexport { AliexpressCheckoutGuard, type AliexpressCheckoutGuardProps } from './preflight-guard'\nexport type {\n PreflightResult,\n PreflightMerchantError,\n PreflightAddressError,\n PreflightItemError,\n} from '../types'\n","'use client'\n\nimport { useCallback, useState } from 'react'\nimport type { PreflightResult } from '../types'\n\nexport interface UseAliexpressPreflightOptions {\n baseUrl?: string\n publishableKey?: string\n}\n\nexport function useAliexpressPreflight(options: UseAliexpressPreflightOptions = {}) {\n const [isPending, setIsPending] = useState(false)\n const mutateAsync = useCallback(async ({ cartId }: { cartId: string }): Promise<PreflightResult> => {\n setIsPending(true)\n try {\n const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? ''\n const publishableKey = options.publishableKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? ''\n const response = await fetch(`${baseUrl.replace(/\\/$/, '')}/store/aliexpress-dropshipping/preflight-cart`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(publishableKey ? { 'x-publishable-api-key': publishableKey } : {}),\n },\n body: JSON.stringify({ cart_id: cartId }),\n })\n if (!response.ok) throw new Error(`Preflight check failed: ${response.status}`)\n return await response.json() as PreflightResult\n } finally {\n setIsPending(false)\n }\n }, [options.baseUrl, options.publishableKey])\n\n return { mutateAsync, isPending }\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport { useAliexpressPreflight, type UseAliexpressPreflightOptions } from './use-preflight'\nimport type { PreflightResult } from '../types'\n\nexport interface AliexpressCheckoutGuardProps {\n /**\n * Cart id to preflight. Passed by the storefront checkout page or the plugin slot's\n * `context.cartId`. If undefined (cart not yet loaded), the guard renders nothing.\n */\n cartId?: string\n /** Optional TanStack Query hook options (e.g. custom baseUrl for tests). */\n options?: UseAliexpressPreflightOptions\n /** Optional callback merchant storefronts can wire to react to the block state. */\n onBlock?: (result: PreflightResult) => void\n}\n\n/**\n * Standalone slot-compatible AliExpress preflight component.\n *\n * Rendered by the plugin registry at the `checkoutStart` slot. On mount + whenever\n * `cartId` changes, runs the pre-payment shipping/address/OAuth check. If any AE line\n * item is unfulfillable, renders an inline warning block above the checkout form.\n *\n * NOTE: this is a SOFT warning (not a hard block on the pay button). For hard blocking,\n * the merchant's checkout page should call `useAliexpressPreflight` directly and gate\n * its own `cart.complete()` on the result. See package README for both integration\n * patterns.\n *\n * Fail-open: if the preflight endpoint is unreachable, the guard renders nothing rather\n * than block checkout. Better to occasionally let through an unfulfillable order (which\n * downstream retry + refund handles) than to lock the merchant out of checkouts on a\n * transient outage.\n */\nexport function AliexpressCheckoutGuard({ cartId, options, onBlock }: AliexpressCheckoutGuardProps) {\n const preflight = useAliexpressPreflight(options)\n const [blockedResult, setBlockedResult] = useState<PreflightResult | null>(null)\n\n useEffect(() => {\n if (!cartId) return\n let cancelled = false\n void preflight\n .mutateAsync({ cartId })\n .then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setBlockedResult(result)\n onBlock?.(result)\n } else {\n setBlockedResult(null)\n }\n })\n .catch(() => {\n // Fail-open: don't block checkout if the preflight endpoint itself is unreachable.\n if (!cancelled) setBlockedResult(null)\n })\n return () => {\n cancelled = true\n }\n // Intentionally re-runs only on cartId change, not on mutation reference change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cartId])\n\n if (!blockedResult) return null\n\n const itemErrors = blockedResult.itemErrors ?? []\n const addressErrors = blockedResult.addressErrors ?? []\n const merchantErrors = blockedResult.merchantErrors ?? []\n\n return (\n <div\n role=\"alert\"\n style={{\n border: '1px solid #f5c2c7',\n background: '#f8d7da',\n color: '#842029',\n padding: '12px 16px',\n borderRadius: 8,\n marginBottom: 16,\n fontSize: 14,\n lineHeight: 1.4,\n }}\n >\n <strong style={{ display: 'block', marginBottom: 6 }}>\n Some items in your cart can&apos;t be shipped\n </strong>\n <ul style={{ margin: 0, paddingLeft: 20 }}>\n {itemErrors.map((err) => (\n <li key={err.variantId}>\n <strong>{err.productTitle}</strong>: {err.message}\n </li>\n ))}\n {addressErrors.map((err, idx) => (\n <li key={`addr-${idx}`}>{err.message}</li>\n ))}\n {merchantErrors.map((err, idx) => (\n <li key={`merchant-${idx}`}>{err.message}</li>\n ))}\n </ul>\n </div>\n )\n}\n"],"mappings":"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","names":["import_react"]}
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/use-preflight.ts"],"sourcesContent":["export { useAliexpressPreflight, type UseAliexpressPreflightOptions } from './use-preflight'\nexport type {\n PreflightResult,\n PreflightMerchantError,\n PreflightMerchantErrorCode,\n PreflightAddressErrorCode,\n PreflightRateLimitedResponse,\n PreflightAddressError,\n PreflightItemError,\n} from '../types'","'use client'\n\nimport { useCallback, useState } from 'react'\nimport type { PreflightResult } from '../types'\n\nexport interface UseAliexpressPreflightOptions {\n baseUrl?: string\n publishableKey?: string\n}\n\n/**\n * Calls `POST /store/aliexpress-dropshipping/preflight-cart`.\n *\n * Returns `{ mutateAsync, isPending }`, not a `PreflightResult`.\n * `mutateAsync({ cartId })` resolves to the `PreflightResult` and rejects on a\n * non-2xx response.\n */\nexport function useAliexpressPreflight(options: UseAliexpressPreflightOptions = {}) {\n const [isPending, setIsPending] = useState(false)\n const mutateAsync = useCallback(async ({ cartId }: { cartId: string }): Promise<PreflightResult> => {\n setIsPending(true)\n try {\n const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? ''\n const publishableKey = options.publishableKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? ''\n const response = await fetch(`${baseUrl.replace(/\\/$/, '')}/store/aliexpress-dropshipping/preflight-cart`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(publishableKey ? { 'x-publishable-api-key': publishableKey } : {}),\n },\n body: JSON.stringify({ cart_id: cartId }),\n })\n if (!response.ok) throw new Error(`Preflight check failed: ${response.status}`)\n return await response.json() as PreflightResult\n } finally {\n setIsPending(false)\n }\n }, [options.baseUrl, options.publishableKey])\n\n return { mutateAsync, isPending }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,mBAAsC;AAe/B,SAAS,uBAAuB,UAAyC,CAAC,GAAG;AAClF,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,kBAAc,0BAAY,OAAO,EAAE,OAAO,MAAoD;AAClG,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,UAAU,QAAQ,WAAW,QAAQ,IAAI,kCAAkC;AACjF,YAAM,iBAAiB,QAAQ,kBAAkB,QAAQ,IAAI,sCAAsC;AACnG,YAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,iDAAiD;AAAA,QACzG,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAI,iBAAiB,EAAE,yBAAyB,eAAe,IAAI,CAAC;AAAA,QACtE;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,CAAC;AAAA,MAC1C,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,2BAA2B,SAAS,MAAM,EAAE;AAC9E,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,QAAQ,cAAc,CAAC;AAE5C,SAAO,EAAE,aAAa,UAAU;AAClC;","names":[]}
@@ -1,9 +1,29 @@
1
- import {
2
- AliexpressCheckoutGuard,
3
- useAliexpressPreflight
4
- } from "../chunk-FPBGH4MH.mjs";
1
+ // src/client/use-preflight.ts
2
+ import { useCallback, useState } from "react";
3
+ function useAliexpressPreflight(options = {}) {
4
+ const [isPending, setIsPending] = useState(false);
5
+ const mutateAsync = useCallback(async ({ cartId }) => {
6
+ setIsPending(true);
7
+ try {
8
+ const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? "";
9
+ const publishableKey = options.publishableKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? "";
10
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/store/aliexpress-dropshipping/preflight-cart`, {
11
+ method: "POST",
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ ...publishableKey ? { "x-publishable-api-key": publishableKey } : {}
15
+ },
16
+ body: JSON.stringify({ cart_id: cartId })
17
+ });
18
+ if (!response.ok) throw new Error(`Preflight check failed: ${response.status}`);
19
+ return await response.json();
20
+ } finally {
21
+ setIsPending(false);
22
+ }
23
+ }, [options.baseUrl, options.publishableKey]);
24
+ return { mutateAsync, isPending };
25
+ }
5
26
  export {
6
- AliexpressCheckoutGuard,
7
27
  useAliexpressPreflight
8
28
  };
9
29
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../../src/client/use-preflight.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useState } from 'react'\nimport type { PreflightResult } from '../types'\n\nexport interface UseAliexpressPreflightOptions {\n baseUrl?: string\n publishableKey?: string\n}\n\n/**\n * Calls `POST /store/aliexpress-dropshipping/preflight-cart`.\n *\n * Returns `{ mutateAsync, isPending }`, not a `PreflightResult`.\n * `mutateAsync({ cartId })` resolves to the `PreflightResult` and rejects on a\n * non-2xx response.\n */\nexport function useAliexpressPreflight(options: UseAliexpressPreflightOptions = {}) {\n const [isPending, setIsPending] = useState(false)\n const mutateAsync = useCallback(async ({ cartId }: { cartId: string }): Promise<PreflightResult> => {\n setIsPending(true)\n try {\n const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? ''\n const publishableKey = options.publishableKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? ''\n const response = await fetch(`${baseUrl.replace(/\\/$/, '')}/store/aliexpress-dropshipping/preflight-cart`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(publishableKey ? { 'x-publishable-api-key': publishableKey } : {}),\n },\n body: JSON.stringify({ cart_id: cartId }),\n })\n if (!response.ok) throw new Error(`Preflight check failed: ${response.status}`)\n return await response.json() as PreflightResult\n } finally {\n setIsPending(false)\n }\n }, [options.baseUrl, options.publishableKey])\n\n return { mutateAsync, isPending }\n}\n"],"mappings":";AAEA,SAAS,aAAa,gBAAgB;AAe/B,SAAS,uBAAuB,UAAyC,CAAC,GAAG;AAClF,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,cAAc,YAAY,OAAO,EAAE,OAAO,MAAoD;AAClG,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,UAAU,QAAQ,WAAW,QAAQ,IAAI,kCAAkC;AACjF,YAAM,iBAAiB,QAAQ,kBAAkB,QAAQ,IAAI,sCAAsC;AACnG,YAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,iDAAiD;AAAA,QACzG,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAI,iBAAiB,EAAE,yBAAyB,eAAe,IAAI,CAAC;AAAA,QACtE;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,CAAC;AAAA,MAC1C,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,2BAA2B,SAAS,MAAM,EAAE;AAC9E,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,QAAQ,cAAc,CAAC;AAE5C,SAAO,EAAE,aAAa,UAAU;AAClC;","names":[]}