@thorprovider/medusa-extended 1.3.0 → 1.5.0

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.
@@ -13,6 +13,7 @@ import { describe, expect, it } from 'vitest'
13
13
  import { http, HttpResponse } from 'msw'
14
14
  import { server } from '../stelorder/client.test-helpers'
15
15
  import { DropshipperClient } from './dropshipper'
16
+ import { ProviderAPIError } from '@thorprovider/adapters'
16
17
 
17
18
  const BASE = 'https://medusa.example.com'
18
19
  const TOKEN = 'test-jwt-token'
@@ -372,6 +373,241 @@ describe('DropshipperClient.createOrder', () => {
372
373
  })
373
374
  expect(result.order.id).toBe('ord_new')
374
375
  })
376
+
377
+ it('POST /admin/thor/dropshipper/orders with province and city (geozone match)', async () => {
378
+ server.use(
379
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async ({ request }) => {
380
+ requireBearerToken(request)
381
+ const body = await request.json() as any
382
+ expect(body.shipping_address.province).toBe('La Habana')
383
+ expect(body.shipping_address.city).toBe('Centro Habana')
384
+ return HttpResponse.json({
385
+ order: {
386
+ id: 'ord_new',
387
+ display_id: 43,
388
+ status: 'pending',
389
+ subtotal: 4000,
390
+ tax_total: 0,
391
+ shipping_total: 1000,
392
+ discount_total: 0,
393
+ total: 5000,
394
+ profit: 2000,
395
+ created_at: '2026-01-01T00:00:00Z',
396
+ shipping_method: null,
397
+ },
398
+ })
399
+ }),
400
+ )
401
+
402
+ const result = await client().createOrder({
403
+ customer_id: 'cus_1',
404
+ currency_code: 'usd',
405
+ items: [{ variant_id: 'var_1', quantity: 1 }],
406
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'Centro Habana', province: 'La Habana', postal_code: '10001', country_code: 'cu' },
407
+ payment_method_id: 'pm_1',
408
+ })
409
+ expect(result.order.id).toBe('ord_new')
410
+ })
411
+
412
+ it('POST /admin/thor/dropshipper/orders without province/city (geozone fallback)', async () => {
413
+ server.use(
414
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async ({ request }) => {
415
+ requireBearerToken(request)
416
+ const body = await request.json() as any
417
+ expect(body.shipping_address.province).toBeUndefined()
418
+ expect(body.shipping_address.city).toBe('Miami')
419
+ return HttpResponse.json({
420
+ order: {
421
+ id: 'ord_new',
422
+ display_id: 44,
423
+ status: 'pending',
424
+ subtotal: 3000,
425
+ tax_total: 0,
426
+ shipping_total: 500,
427
+ discount_total: 0,
428
+ total: 3500,
429
+ profit: 1500,
430
+ created_at: '2026-01-01T00:00:00Z',
431
+ shipping_method: null,
432
+ },
433
+ })
434
+ }),
435
+ )
436
+
437
+ const result = await client().createOrder({
438
+ customer_id: 'cus_2',
439
+ currency_code: 'usd',
440
+ items: [{ variant_id: 'var_2', quantity: 2 }],
441
+ shipping_address: { full_name: 'Jane', address_1: '456 Oak', city: 'Miami', postal_code: '33101', country_code: 'us' },
442
+ payment_method_id: 'pm_2',
443
+ })
444
+ expect(result.order.id).toBe('ord_new')
445
+ })
446
+
447
+ it('POST /admin/thor/dropshipper/orders with separate billing_address', async () => {
448
+ server.use(
449
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async ({ request }) => {
450
+ requireBearerToken(request)
451
+ const body = await request.json() as any
452
+ expect(body.billing_address).toBeDefined()
453
+ expect(body.billing_address.full_name).toBe('Jane Doe')
454
+ expect(body.billing_address.address_1).toBe('456 Main')
455
+ expect(body.billing_address.city).toBe('NY')
456
+ expect(body.billing_address.postal_code).toBe('10001')
457
+ expect(body.billing_address.country_code).toBe('us')
458
+ return HttpResponse.json({
459
+ order: {
460
+ id: 'ord_new',
461
+ display_id: 45,
462
+ status: 'pending',
463
+ subtotal: 5000,
464
+ tax_total: 0,
465
+ shipping_total: 1000,
466
+ discount_total: 0,
467
+ total: 6000,
468
+ profit: 2500,
469
+ created_at: '2026-01-01T00:00:00Z',
470
+ shipping_method: null,
471
+ },
472
+ })
473
+ }),
474
+ )
475
+
476
+ const result = await client().createOrder({
477
+ customer_id: 'cus_1',
478
+ currency_code: 'usd',
479
+ items: [{ variant_id: 'var_1', quantity: 1 }],
480
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
481
+ billing_address: { full_name: 'Jane Doe', address_1: '456 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
482
+ payment_method_id: 'pm_1',
483
+ })
484
+ expect(result.order.id).toBe('ord_new')
485
+ })
486
+
487
+ it('createOrder with dropshipper-collected payment method', async () => {
488
+ server.use(
489
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async ({ request }) => {
490
+ requireBearerToken(request)
491
+ const body = await request.json() as any
492
+ expect(body.payment_method_id).toBe('pm_dropshipper')
493
+ return HttpResponse.json({
494
+ order: {
495
+ id: 'ord_new',
496
+ display_id: 44,
497
+ status: 'pending',
498
+ subtotal: 4000,
499
+ tax_total: 0,
500
+ shipping_total: 1000,
501
+ discount_total: 0,
502
+ total: 5000,
503
+ profit: 2000,
504
+ created_at: '2026-01-01T00:00:00Z',
505
+ shipping_method: null,
506
+ },
507
+ })
508
+ }),
509
+ )
510
+
511
+ const result = await client().createOrder({
512
+ customer_id: 'cus_1',
513
+ currency_code: 'usd',
514
+ items: [{ variant_id: 'var_1', quantity: 1 }],
515
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
516
+ payment_method_id: 'pm_dropshipper',
517
+ })
518
+ expect(result.order.id).toBe('ord_new')
519
+ })
520
+
521
+ it('createOrder with provider-collected payment method', async () => {
522
+ server.use(
523
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async ({ request }) => {
524
+ requireBearerToken(request)
525
+ const body = await request.json() as any
526
+ expect(body.payment_method_id).toBe('pm_provider')
527
+ return HttpResponse.json({
528
+ order: {
529
+ id: 'ord_new',
530
+ display_id: 45,
531
+ status: 'pending',
532
+ subtotal: 4000,
533
+ tax_total: 0,
534
+ shipping_total: 1000,
535
+ discount_total: 0,
536
+ total: 5000,
537
+ profit: 2000,
538
+ created_at: '2026-01-01T00:00:00Z',
539
+ shipping_method: null,
540
+ },
541
+ })
542
+ }),
543
+ )
544
+
545
+ const result = await client().createOrder({
546
+ customer_id: 'cus_1',
547
+ currency_code: 'usd',
548
+ items: [{ variant_id: 'var_1', quantity: 1 }],
549
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
550
+ payment_method_id: 'pm_provider',
551
+ })
552
+ expect(result.order.id).toBe('ord_new')
553
+ })
554
+
555
+ it('throws ProviderAPIError on 422 for invalid variants', async () => {
556
+ server.use(
557
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async () => {
558
+ return HttpResponse.json(
559
+ { message: 'Invalid variants: var_invalid_1, var_invalid_2' },
560
+ { status: 422 },
561
+ )
562
+ }),
563
+ )
564
+
565
+ await expect(client().createOrder({
566
+ customer_id: 'cus_1',
567
+ currency_code: 'usd',
568
+ items: [{ variant_id: 'var_invalid_1', quantity: 1 }],
569
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
570
+ payment_method_id: 'pm_1',
571
+ })).rejects.toThrow(ProviderAPIError)
572
+ })
573
+
574
+ it('throws ProviderAPIError on 404 for customer not found', async () => {
575
+ server.use(
576
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async () => {
577
+ return HttpResponse.json(
578
+ { message: 'Customer not found' },
579
+ { status: 404 },
580
+ )
581
+ }),
582
+ )
583
+
584
+ await expect(client().createOrder({
585
+ customer_id: 'cus_missing',
586
+ currency_code: 'usd',
587
+ items: [{ variant_id: 'var_1', quantity: 1 }],
588
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
589
+ payment_method_id: 'pm_1',
590
+ })).rejects.toThrow(ProviderAPIError)
591
+ })
592
+
593
+ it('throws ProviderAPIError on 500 for server error', async () => {
594
+ server.use(
595
+ http.post(`${BASE}/admin/thor/dropshipper/orders`, async () => {
596
+ return HttpResponse.json(
597
+ { message: 'Internal server error' },
598
+ { status: 500 },
599
+ )
600
+ }),
601
+ )
602
+
603
+ await expect(client().createOrder({
604
+ customer_id: 'cus_1',
605
+ currency_code: 'usd',
606
+ items: [{ variant_id: 'var_1', quantity: 1 }],
607
+ shipping_address: { full_name: 'John', address_1: '123 Main', city: 'NY', province: null, postal_code: '10001', country_code: 'us' },
608
+ payment_method_id: 'pm_1',
609
+ })).rejects.toThrow(ProviderAPIError)
610
+ })
375
611
  })
376
612
 
377
613
  describe('DropshipperClient.getOrderShippingOptions', () => {
@@ -403,6 +639,42 @@ describe('DropshipperClient.getOrderShippingOptions', () => {
403
639
  expect(result.shipping_options).toHaveLength(1)
404
640
  expect(result.shipping_options[0].id).toBe('ship_1')
405
641
  })
642
+
643
+ it('forwards province and city as query params', async () => {
644
+ server.use(
645
+ http.get(`${BASE}/admin/thor/dropshipper/orders/shipping-options`, ({ request }) => {
646
+ requireBearerToken(request)
647
+ const url = new URL(request.url)
648
+ expect(url.searchParams.get('currency_code')).toBe('usd')
649
+ expect(url.searchParams.get('country_code')).toBe('cu')
650
+ expect(url.searchParams.get('province')).toBe('La Habana')
651
+ expect(url.searchParams.get('city')).toBe('Centro Habana')
652
+ return HttpResponse.json({
653
+ shipping_options: [
654
+ {
655
+ id: 'ship_1',
656
+ name: 'La Habana Express',
657
+ amount: 500,
658
+ currency_code: 'usd',
659
+ price_type: 'flat',
660
+ is_default: true,
661
+ type: null,
662
+ },
663
+ ],
664
+ count: 1,
665
+ })
666
+ }),
667
+ )
668
+
669
+ const result = await client().getOrderShippingOptions({
670
+ currency_code: 'usd',
671
+ country_code: 'cu',
672
+ province: 'La Habana',
673
+ city: 'Centro Habana',
674
+ })
675
+ expect(result.shipping_options).toHaveLength(1)
676
+ expect(result.shipping_options[0].id).toBe('ship_1')
677
+ })
406
678
  })
407
679
 
408
680
  describe('DropshipperClient.setPaymentCollector', () => {
@@ -133,6 +133,10 @@ import type {
133
133
  GetFinancialAnalysisResponse,
134
134
  RegisterPaymentBody,
135
135
  RegisterPaymentResponse,
136
+ // Geo-reference
137
+ GetGeoCountriesResponse,
138
+ GetGeoProvincesResponse,
139
+ GetGeoCitiesResponse,
136
140
  // Dashboard Capabilities
137
141
  DropshipperDashboardCapabilities,
138
142
  // Inline return types
@@ -1451,6 +1455,43 @@ export class DropshipperClient {
1451
1455
  '/store/thor/dropshipper-categories',
1452
1456
  );
1453
1457
  }
1458
+
1459
+ // -------------------------------------------------------------------------
1460
+ // Geo-reference
1461
+ // -------------------------------------------------------------------------
1462
+
1463
+ /**
1464
+ * List all geo-reference countries.
1465
+ *
1466
+ * `GET /admin/geo-reference`
1467
+ */
1468
+ async getGeoCountries(): Promise<GetGeoCountriesResponse> {
1469
+ return this.request<GetGeoCountriesResponse>('GET', '/admin/geo-reference');
1470
+ }
1471
+
1472
+ /**
1473
+ * List provinces/states for a country.
1474
+ *
1475
+ * `GET /admin/geo-reference/countries/:iso_2/provinces`
1476
+ */
1477
+ async getGeoProvinces(iso2: string): Promise<GetGeoProvincesResponse> {
1478
+ return this.request<GetGeoProvincesResponse>(
1479
+ 'GET',
1480
+ `/admin/geo-reference/countries/${iso2}/provinces`,
1481
+ );
1482
+ }
1483
+
1484
+ /**
1485
+ * List cities for a province/state.
1486
+ *
1487
+ * `GET /admin/geo-reference/provinces/:code/cities`
1488
+ */
1489
+ async getGeoCities(code: string): Promise<GetGeoCitiesResponse> {
1490
+ return this.request<GetGeoCitiesResponse>(
1491
+ 'GET',
1492
+ `/admin/geo-reference/provinces/${code}/cities`,
1493
+ );
1494
+ }
1454
1495
  }
1455
1496
 
1456
1497
  // Re-export types used by callers so they don't need to import from @thorprovider/types directly.