Skip to content

Commit 9fa4bd9

Browse files
authored
feat(fulfillment, core-flows, types, utils, medusa): support custom delivery address + pass additional data to createFulfillment (#16139)
1 parent c13aaa9 commit 9fa4bd9

17 files changed

Lines changed: 182 additions & 19 deletions

File tree

.changeset/wet-moose-relax.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@medusajs/fulfillment": patch
3+
"@medusajs/core-flows": patch
4+
"@medusajs/types": patch
5+
"@medusajs/utils": patch
6+
"@medusajs/medusa": patch
7+
---
8+
9+
feat(fulfillment, core-flows, types, utils, medusa): support custom delivery address + pass additional data to createFulfillment

integration-tests/http/__tests__/order/admin/order.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,41 @@ medusaIntegrationTestRunner({
18291829
expect(iitem.reserved_quantity).toBe(0)
18301830
})
18311831

1832+
it("should override the fulfillment's delivery address with the request's delivery_address", async () => {
1833+
const orderItemId = order.items.find(
1834+
(i) => i.variant_id === productOverride3.variants[0].id
1835+
).id
1836+
1837+
const {
1838+
data: { order: fulfillableOrder },
1839+
} = await api.post(
1840+
`/admin/orders/${order.id}/fulfillments?fields=fulfillments.id,fulfillments.delivery_address.*`,
1841+
{
1842+
shipping_option_id: seeder.shippingOption.id,
1843+
location_id: seeder.stockLocation.id,
1844+
items: [{ id: orderItemId, quantity: 1 }],
1845+
delivery_address: {
1846+
first_name: "Nova",
1847+
last_name: "Poshta",
1848+
},
1849+
},
1850+
adminHeaders
1851+
)
1852+
1853+
expect(fulfillableOrder.fulfillments).toHaveLength(1)
1854+
expect(fulfillableOrder.fulfillments[0].delivery_address).toEqual(
1855+
expect.objectContaining({
1856+
// overridden by the request
1857+
first_name: "Nova",
1858+
last_name: "Poshta",
1859+
// untouched fields are still taken from the order's shipping address
1860+
address_1: order.shipping_address.address_1,
1861+
city: order.shipping_address.city,
1862+
country_code: order.shipping_address.country_code,
1863+
})
1864+
)
1865+
})
1866+
18321867
it("should throw if trying to fulfillment more items than it is reserved", async () => {
18331868
const orderItemId = order.items.find(
18341869
(i) => i.variant_id === productOverride3.variants[0].id

packages/core/core-flows/src/fulfillment/workflows/create-fulfillment.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,10 @@ export const createFulfillmentWorkflow = createWorkflow(
9696
}
9797
})
9898

99-
// When we have support for hooks with a return this would be a great
100-
// place to put a hook for people to collect additional data they would
101-
// like to pass down to the provider.
102-
//
103-
// const providerDataHook = createHook("getProviderData", stepInput)
104-
//
105-
// The collected provider data would be passed to createFulfillment in a
106-
// additional_provider_data: Record<string, unknown> field.
99+
// Callers can pass an `additional_data` field on the input, which is
100+
// forwarded as-is to the provider's `createFulfillment` method (without
101+
// being persisted on the fulfillment). This lets custom data reach the
102+
// provider before it talks to the third-party service.
107103

108104
const result = createFulfillmentStep(stepInput)
109105

packages/core/core-flows/src/order/workflows/create-fulfillment.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function prepareFulfillmentData({
149149
itemsList,
150150
}: {
151151
order: OrderDTO
152-
input: OrderWorkflow.CreateOrderFulfillmentWorkflowInput
152+
input: OrderWorkflow.CreateOrderFulfillmentWorkflowInput & AdditionalData
153153
shippingOption: {
154154
id: string
155155
provider_id: string
@@ -247,6 +247,14 @@ function prepareFulfillmentData({
247247
const shippingAddress = order.shipping_address ?? { id: undefined }
248248
delete shippingAddress.id
249249

250+
// Merge any recipient overrides passed in the request over the order's
251+
// shipping address, so the fulfillment provider receives the resolved
252+
// delivery address without mutating the order itself.
253+
const deliveryAddress = {
254+
...shippingAddress,
255+
...(input.delivery_address ?? {}),
256+
}
257+
250258
return {
251259
input: {
252260
location_id: locationId,
@@ -257,10 +265,11 @@ function prepareFulfillmentData({
257265
items: fulfillmentItems,
258266
requires_shipping: someItemsRequireShipping,
259267
labels: input.labels ?? [],
260-
delivery_address: shippingAddress as any,
268+
delivery_address: deliveryAddress as any,
261269
created_by: input.created_by,
262270
packed_at: new Date(),
263271
metadata: input.metadata,
272+
additional_data: input.additional_data,
264273
},
265274
}
266275
}

packages/core/types/src/fulfillment/mutations/fulfillment.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ export interface CreateFulfillmentDTO {
8787
* The associated order to be sent to the provider.
8888
*/
8989
order?: Partial<OrderDTO>
90+
91+
/**
92+
* Custom key-value pairs to pass to the fulfillment provider's `createFulfillment`
93+
* method. Unlike `data`, this isn't persisted on the fulfillment; it's only
94+
* forwarded to the provider at creation time.
95+
*/
96+
additional_data?: Record<string, unknown>
9097
}
9198

9299
/**

packages/core/types/src/fulfillment/provider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ export interface IFulfillmentProvider {
124124
data: Record<string, unknown>,
125125
items: Partial<Omit<FulfillmentItemDTO, "fulfillment">>[],
126126
order: Partial<FulfillmentOrderDTO> | undefined,
127-
fulfillment: Partial<Omit<FulfillmentDTO, "provider_id" | "data" | "items">>
127+
fulfillment: Partial<
128+
Omit<FulfillmentDTO, "provider_id" | "data" | "items">
129+
>,
130+
additionalData?: Record<string, unknown>
128131
): Promise<CreateFulfillmentResult>
129132
/**
130133
*

packages/core/types/src/http/order/admin/payloads.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export interface AdminCreateOrderFulfillment {
5353
* Whether to notify the customer about this change.
5454
*/
5555
no_notification?: boolean
56+
/**
57+
* The recipient address to use for the fulfillment. It's merged over the
58+
* order's shipping address, allowing you to override recipient details
59+
* (such as the first and last name) sent to the fulfillment provider.
60+
*/
61+
delivery_address?: OrderAddress
5662
/**
5763
* Key-value pairs of custom data.
5864
*/

packages/core/types/src/workflow/fulfillment/create-fulfillment.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,11 @@ export type CreateFulfillmentWorkflowInput = {
188188
* The associated fulfillment order to be sent to the provider.
189189
*/
190190
order?: CreateFulfillmentOrderWorkflowDTO
191+
192+
/**
193+
* Custom key-value pairs to pass to the fulfillment provider's `createFulfillment`
194+
* method. Unlike `data`, this isn't persisted on the fulfillment; it's only
195+
* forwarded to the provider at creation time.
196+
*/
197+
additional_data?: Record<string, unknown>
191198
}

packages/core/types/src/workflow/order/create-fulfillment.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { OrderLineItemDTO } from "../../order"
22
import { BigNumberInput } from "../../totals"
3-
import { CreateFulfillmentLabelWorkflowDTO } from "../fulfillment/create-fulfillment"
3+
import {
4+
CreateFulfillmentAddressWorkflowDTO,
5+
CreateFulfillmentLabelWorkflowDTO,
6+
} from "../fulfillment/create-fulfillment"
47

58
/**
69
* The details of an item in the fulfillment.
@@ -67,6 +70,13 @@ export interface CreateOrderFulfillmentWorkflowInput {
6770
*/
6871
shipping_option_id?: string
6972

73+
/**
74+
* The recipient address to use for the fulfillment. It's merged over the
75+
* order's shipping address, allowing you to override recipient details
76+
* (such as the first and last name) sent to the fulfillment provider.
77+
*/
78+
delivery_address?: CreateFulfillmentAddressWorkflowDTO
79+
7080
/**
7181
* Whether the fulfillment should be shipped.
7282
*/

packages/core/utils/src/fulfillment/provider.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ export class AbstractFulfillmentProviderService
296296
* @param items - The items in the fulfillment.
297297
* @param order - The order this fulfillment is created for.
298298
* @param fulfillment - The fulfillment's details.
299+
* @param additionalData - Custom key-value pairs forwarded from the workflow that
300+
* created the fulfillment (for example, the `additional_data` of the Create Order
301+
* Fulfillment API route). It isn't persisted on the fulfillment.
299302
* @returns An object whose `data` property is stored in the fulfillment's `data` property.
300303
*
301304
* @example
@@ -305,7 +308,8 @@ export class AbstractFulfillmentProviderService
305308
* data: Record<string, unknown>,
306309
* items: Partial<Omit<FulfillmentItemDTO, "fulfillment">>[],
307310
* order: Partial<FulfillmentOrderDTO> | undefined,
308-
* fulfillment: Partial<Omit<FulfillmentDTO, "provider_id" | "data" | "items">>
311+
* fulfillment: Partial<Omit<FulfillmentDTO, "provider_id" | "data" | "items">>,
312+
* additionalData?: Record<string, unknown>
309313
* ): Promise<CreateFulfillmentResult> {
310314
* // assuming the client creates a fulfillment
311315
* // in the third-party service
@@ -327,7 +331,10 @@ export class AbstractFulfillmentProviderService
327331
data: Record<string, unknown>,
328332
items: Partial<Omit<FulfillmentItemDTO, "fulfillment">>[],
329333
order: Partial<FulfillmentOrderDTO> | undefined,
330-
fulfillment: Partial<Omit<FulfillmentDTO, "provider_id" | "data" | "items">>
334+
fulfillment: Partial<
335+
Omit<FulfillmentDTO, "provider_id" | "data" | "items">
336+
>,
337+
additionalData?: Record<string, unknown>
331338
): Promise<CreateFulfillmentResult> {
332339
throw Error("createFulfillment must be overridden by the child class")
333340
}

0 commit comments

Comments
 (0)