@thorprovider/medusa-extended 1.13.1 → 1.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thorprovider/medusa-extended",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "description": "Thor Commerce multi-tenant admin extensions for Medusa v2",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@thorprovider/adapters": "^5.0.8",
28
- "@thorprovider/types": "^5.5.1"
28
+ "@thorprovider/types": "^5.7.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "msw": "^2.12.3",
@@ -1011,6 +1011,64 @@ describe('DropshipperClient.getAccount', () => {
1011
1011
  })
1012
1012
  })
1013
1013
 
1014
+ describe('DropshipperClient.getBusinessPolicy', () => {
1015
+ it('GET /admin/thor/dropshipper/account/policies', async () => {
1016
+ server.use(
1017
+ http.get(`${BASE}/admin/thor/dropshipper/account/policies`, ({ request }) => {
1018
+ requireBearerToken(request)
1019
+ return HttpResponse.json({ business_policy: 'Devoluciones dentro de 30 días.' })
1020
+ }),
1021
+ )
1022
+
1023
+ const result = await client().getBusinessPolicy()
1024
+ expect(result.business_policy).toBe('Devoluciones dentro de 30 días.')
1025
+ })
1026
+
1027
+ it('returns an empty string when no policy is set', async () => {
1028
+ server.use(
1029
+ http.get(`${BASE}/admin/thor/dropshipper/account/policies`, ({ request }) => {
1030
+ requireBearerToken(request)
1031
+ return HttpResponse.json({ business_policy: '' })
1032
+ }),
1033
+ )
1034
+
1035
+ const result = await client().getBusinessPolicy()
1036
+ expect(result.business_policy).toBe('')
1037
+ })
1038
+ })
1039
+
1040
+ describe('DropshipperClient.updateBusinessPolicy', () => {
1041
+ it('PATCH /admin/thor/dropshipper/account/policies', async () => {
1042
+ server.use(
1043
+ http.patch(`${BASE}/admin/thor/dropshipper/account/policies`, async ({ request }) => {
1044
+ requireBearerToken(request)
1045
+ const body = await request.json() as any
1046
+ expect(body.business_policy).toBe('Devoluciones dentro de 30 días.')
1047
+ return HttpResponse.json({ business_policy: 'Devoluciones dentro de 30 días.' })
1048
+ }),
1049
+ )
1050
+
1051
+ const result = await client().updateBusinessPolicy({
1052
+ business_policy: 'Devoluciones dentro de 30 días.',
1053
+ })
1054
+ expect(result.business_policy).toBe('Devoluciones dentro de 30 días.')
1055
+ })
1056
+
1057
+ it('returns the trimmed value persisted by the server', async () => {
1058
+ server.use(
1059
+ http.patch(`${BASE}/admin/thor/dropshipper/account/policies`, async ({ request }) => {
1060
+ requireBearerToken(request)
1061
+ const body = await request.json() as any
1062
+ expect(body.business_policy).toBe(' Política ')
1063
+ return HttpResponse.json({ business_policy: 'Política' })
1064
+ }),
1065
+ )
1066
+
1067
+ const result = await client().updateBusinessPolicy({ business_policy: ' Política ' })
1068
+ expect(result.business_policy).toBe('Política')
1069
+ })
1070
+ })
1071
+
1014
1072
  describe('DropshipperClient.getSettlements', () => {
1015
1073
  it('GET /admin/thor/dropshipper/settlements with defaults', async () => {
1016
1074
  server.use(
@@ -171,6 +171,10 @@ import type {
171
171
  DropshipperNotificationPreference,
172
172
  UpdateNotificationPreferencesBody,
173
173
  GetNotificationPreferencesResponse,
174
+ // Dropshipper business policy
175
+ GetBusinessPolicyResponse,
176
+ UpdateBusinessPolicyBody,
177
+ UpdateBusinessPolicyResponse,
174
178
  // Inline return types
175
179
  DropshipperCategory,
176
180
  DropshipperPromotion,
@@ -1327,6 +1331,35 @@ export class DropshipperClient {
1327
1331
  );
1328
1332
  }
1329
1333
 
1334
+ /**
1335
+ * Get the authenticated dropshipper's business policy text.
1336
+ * Returns an empty string when no policy is set.
1337
+ *
1338
+ * `GET /admin/thor/dropshipper/account/policies`
1339
+ */
1340
+ async getBusinessPolicy(): Promise<GetBusinessPolicyResponse> {
1341
+ return this.request<GetBusinessPolicyResponse>(
1342
+ 'GET',
1343
+ '/admin/thor/dropshipper/account/policies',
1344
+ );
1345
+ }
1346
+
1347
+ /**
1348
+ * Update the authenticated dropshipper's business policy text.
1349
+ * The value is trimmed server-side before being persisted.
1350
+ *
1351
+ * `PATCH /admin/thor/dropshipper/account/policies`
1352
+ */
1353
+ async updateBusinessPolicy(
1354
+ body: UpdateBusinessPolicyBody,
1355
+ ): Promise<UpdateBusinessPolicyResponse> {
1356
+ return this.request<UpdateBusinessPolicyResponse>(
1357
+ 'PATCH',
1358
+ '/admin/thor/dropshipper/account/policies',
1359
+ body,
1360
+ );
1361
+ }
1362
+
1330
1363
  /**
1331
1364
  * Orders where Y owes money to X (Y collected payment, needs to pay costs).
1332
1365
  *
@@ -2033,7 +2066,7 @@ export class DropshipperClient {
2033
2066
  }
2034
2067
 
2035
2068
  // Re-export types used by callers so they don't need to import from @thorprovider/types directly.
2036
- export type { DropshipperCategory, DropshipperPromotion, NotificationData, PriceUpdatedNotificationData, ProductAssignedNotificationData, StockChangeNotificationData, ManualAdminNotificationData, RawNotification, GetNotificationsOptions, GetNotificationsResponse, DropshipperNotificationEventType, DropshipperNotificationPreference, UpdateNotificationPreferencesBody, GetNotificationPreferencesResponse, ChatRequest, ChatResponse, ChatMessage, ChatIntent, ChatSource, ChatSourceVariant } from '@thorprovider/types';
2069
+ export type { DropshipperCategory, DropshipperPromotion, NotificationData, PriceUpdatedNotificationData, ProductAssignedNotificationData, StockChangeNotificationData, ManualAdminNotificationData, RawNotification, GetNotificationsOptions, GetNotificationsResponse, DropshipperNotificationEventType, DropshipperNotificationPreference, UpdateNotificationPreferencesBody, GetNotificationPreferencesResponse, GetBusinessPolicyResponse, UpdateBusinessPolicyBody, UpdateBusinessPolicyResponse, ChatRequest, ChatResponse, ChatMessage, ChatIntent, ChatSource, ChatSourceVariant } from '@thorprovider/types';
2037
2070
 
2038
2071
  // ---------------------------------------------------------------------------
2039
2072
  // AI Assistant — streaming helpers
package/src/recovery.ts CHANGED
@@ -9,12 +9,12 @@
9
9
  * logged in. Backend URLs are owned here; the dashboard never builds them.
10
10
  *
11
11
  * Endpoints:
12
- * - `POST /auth/user/emailpass/reset-password` (request reset, Medusa native)
13
- * - `GET /store/dropshipper/auth/reset-password/verify` (verify reset token, no consume)
14
- * - `POST /auth/user/emailpass/update` (confirm reset, consumes token)
15
- * - `GET /store/dropshipper/applications/activate/verify`(verify activation token, no consume)
16
- * - `POST /store/dropshipper/applications/activate` (confirm activation, consumes token)
17
- * - `POST /public/dropshipper/applications` (submit application, no auth)
12
+ * - `POST /dropshipper/auth/emailpass/reset-password` (request reset, dropshipper)
13
+ * - `GET /dropshipper/auth/emailpass/reset-password/verify` (verify reset token, no consume)
14
+ * - `POST /dropshipper/auth/emailpass/update` (confirm reset, consumes token)
15
+ * - `GET /store/dropshipper/applications/activate/verify` (verify activation token, no consume)
16
+ * - `POST /store/dropshipper/applications/activate` (confirm activation, consumes token)
17
+ * - `POST /public/dropshipper/applications` (submit application, no auth)
18
18
  *
19
19
  * Confirm methods follow the two-phase contract: verify (no consume) first,
20
20
  * then confirm (atomic consume). A re-verify after a failed confirm maps the
@@ -127,31 +127,20 @@ export class RecoveryClient {
127
127
  );
128
128
  }
129
129
 
130
- return response.json() as Promise<T>;
131
- }
132
-
133
- // -------------------------------------------------------------------------
134
- // Private — reset token helpers
135
- // -------------------------------------------------------------------------
136
-
137
- /**
138
- * Decodes the `entity_id` (email) claim from a Medusa reset-password JWT.
139
- *
140
- * The JWT is unsigned-trust for this claim: the email is later sent to
141
- * `POST /auth/user/emailpass/update`, where Medusa itself re-validates the
142
- * token signature and payload before applying the change.
143
- */
144
- private decodeResetTokenEmail(token: string): string | null {
145
- const parts = token.split('.');
146
- if (parts.length !== 3) return null;
147
- try {
148
- const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
149
- return typeof payload.entity_id === 'string' && payload.entity_id.length > 0
150
- ? payload.entity_id
151
- : null;
152
- } catch {
153
- return null;
130
+ // 2xx success with a JSON body: parse and return it.
131
+ if ((response.headers.get('content-type') ?? '').includes('application/json')) {
132
+ try {
133
+ return (await response.json()) as T;
134
+ } catch {
135
+ // A 2xx that claims JSON but fails to parse should not crash the
136
+ // caller as a raw SyntaxError; the stub lets the caller handle it.
137
+ return {} as T;
138
+ }
154
139
  }
140
+
141
+ // 2xx success without a JSON body (204 No Content, or plain-text bodies
142
+ // like `201 Created` from native endpoints). No value to return.
143
+ return {} as T;
155
144
  }
156
145
 
157
146
  // -------------------------------------------------------------------------
@@ -163,11 +152,11 @@ export class RecoveryClient {
163
152
  * (dropshippers). El backend responde de forma uniforme exista o no la
164
153
  * cuenta (anti enumeración).
165
154
  *
166
- * `POST /auth/user/emailpass/reset-password`
155
+ * `POST /dropshipper/auth/emailpass/reset-password`
167
156
  */
168
157
  async requestUserPasswordReset(email: string): Promise<void> {
169
- await this.request('POST', '/auth/user/emailpass/reset-password', {
170
- identifier: email,
158
+ await this.request('POST', '/dropshipper/auth/emailpass/reset-password', {
159
+ email,
171
160
  });
172
161
  }
173
162
 
@@ -175,16 +164,17 @@ export class RecoveryClient {
175
164
  * Verifica un token de reset de contraseña sin consumirlo. Devuelve el
176
165
  * email enmascarado (p. ej. `j***@ejemplo.com`) solo cuando `status === 'valid'`.
177
166
  *
178
- * `GET /store/dropshipper/auth/reset-password/verify`
167
+ * `GET /dropshipper/auth/emailpass/reset-password/verify`
179
168
  */
180
169
  async verifyUserPasswordResetToken(token: string): Promise<TokenVerifyResult> {
181
- return this.request('GET', `/store/dropshipper/auth/reset-password/verify?token=${encodeURIComponent(token)}`);
170
+ return this.request('GET', `/dropshipper/auth/emailpass/reset-password/verify?token=${encodeURIComponent(token)}`);
182
171
  }
183
172
 
184
173
  /**
185
174
  * Confirma el reset: re-verifica el token (sin consumir), luego consume el
186
- * token vía `POST /auth/user/emailpass/update`. El email se deriva del
187
- * propio JWT. Nunca devuelve auto-login: al éxito, la UI redirige a /login.
175
+ * token vía `POST /dropshipper/auth/emailpass/update`. El email se deriva del
176
+ * propio JWT en el backend; aquí solo se envía la nueva contraseña. Nunca
177
+ * devuelve auto-login: al éxito, la UI redirige a /login.
188
178
  */
189
179
  async confirmUserPasswordReset(token: string, password: string): Promise<RecoveryConfirmResult> {
190
180
  const verify = await this.verifyUserPasswordResetToken(token);
@@ -192,24 +182,19 @@ export class RecoveryClient {
192
182
  return { ok: false, status: verify.status };
193
183
  }
194
184
 
195
- const email = this.decodeResetTokenEmail(token);
196
- if (!email) {
197
- return { ok: false, status: 'invalid' };
198
- }
199
-
200
185
  try {
201
186
  const headers: Record<string, string> = {
202
187
  Authorization: `Bearer ${token}`,
203
188
  'Content-Type': 'application/json',
204
189
  };
205
- const response = await fetchBackendWithResilience(`${this.baseUrl}/auth/user/emailpass/update`, {
190
+ const response = await fetchBackendWithResilience(`${this.baseUrl}/dropshipper/auth/emailpass/update`, {
206
191
  method: 'POST',
207
192
  headers,
208
- body: JSON.stringify({ email, password }),
193
+ body: JSON.stringify({ password }),
209
194
  });
210
195
  if (!response.ok) {
211
196
  throw new ProviderAPIError(
212
- `[recovery] POST /auth/user/emailpass/update failed: HTTP ${response.status}`,
197
+ `[recovery] POST /dropshipper/auth/emailpass/update failed: HTTP ${response.status}`,
213
198
  'RECOVERY_API_' + response.status,
214
199
  response.status,
215
200
  );
@@ -314,6 +299,13 @@ export class RecoveryClient {
314
299
  retryAfterSeconds: details.retry_after_seconds,
315
300
  };
316
301
  }
302
+ if (error.status === 400 && details?.type === 'challenge_failed') {
303
+ return {
304
+ ok: false,
305
+ reason: 'challenge_failed',
306
+ ...(typeof details.message === 'string' ? { message: details.message } : {}),
307
+ };
308
+ }
317
309
  if (error.status === 400) {
318
310
  return {
319
311
  ok: false,