@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/dist/index.d.mts +26 -19
- package/dist/index.d.ts +26 -19
- package/dist/index.js +50 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/dropshipper.test.ts +58 -0
- package/src/dropshipper.ts +34 -1
- package/src/recovery.ts +37 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thorprovider/medusa-extended",
|
|
3
|
-
"version": "1.13.
|
|
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.
|
|
28
|
+
"@thorprovider/types": "^5.7.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"msw": "^2.12.3",
|
package/src/dropshipper.test.ts
CHANGED
|
@@ -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(
|
package/src/dropshipper.ts
CHANGED
|
@@ -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/
|
|
13
|
-
* - `GET /
|
|
14
|
-
* - `POST /auth/
|
|
15
|
-
* - `GET /store/dropshipper/applications/activate/verify`(verify activation token, no consume)
|
|
16
|
-
* - `POST /store/dropshipper/applications/activate`
|
|
17
|
-
* - `POST /public/dropshipper/applications`
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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/
|
|
155
|
+
* `POST /dropshipper/auth/emailpass/reset-password`
|
|
167
156
|
*/
|
|
168
157
|
async requestUserPasswordReset(email: string): Promise<void> {
|
|
169
|
-
await this.request('POST', '/auth/
|
|
170
|
-
|
|
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 /
|
|
167
|
+
* `GET /dropshipper/auth/emailpass/reset-password/verify`
|
|
179
168
|
*/
|
|
180
169
|
async verifyUserPasswordResetToken(token: string): Promise<TokenVerifyResult> {
|
|
181
|
-
return this.request('GET', `/
|
|
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/
|
|
187
|
-
* propio JWT
|
|
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/
|
|
190
|
+
const response = await fetchBackendWithResilience(`${this.baseUrl}/dropshipper/auth/emailpass/update`, {
|
|
206
191
|
method: 'POST',
|
|
207
192
|
headers,
|
|
208
|
-
body: JSON.stringify({
|
|
193
|
+
body: JSON.stringify({ password }),
|
|
209
194
|
});
|
|
210
195
|
if (!response.ok) {
|
|
211
196
|
throw new ProviderAPIError(
|
|
212
|
-
`[recovery] POST /auth/
|
|
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,
|