feat(medusa,brightpearl,segment,webshipper): claims (#163)

* chore: create tests

* chore: models

* fix: passing initial tests

* test: adds integration test

* test: clean up integration implementation

* fix: claims

* fix: brightpearl + webshipper

* tests: passing

* fix: update claim items

* fix: adds gitignore

* fix: pr comments

* fix: single migration

* fix(medusa-plugin-segment): adds item claimed event to segment
This commit is contained in:
Sebastian Rindom
2021-02-03 09:49:12 +01:00
committed by GitHub
parent 0e0dfea571
commit 690d339667
60 changed files with 9429 additions and 189 deletions
@@ -5,7 +5,7 @@ export default () => {
const logger = req.scope.resolve("logger")
logger.error(err.message)
console.log(err)
console.error(err)
let statusCode = 500
switch (err.name) {
@@ -13,6 +13,15 @@ const defaultRelations = [
"returns",
"gift_cards",
"gift_card_transactions",
"claims",
"claims.return_order",
"claims.shipping_methods",
"claims.shipping_address",
"claims.additional_items",
"claims.fulfillments",
"claims.claim_items",
"claims.claim_items.images",
"claims.claim_items.tags",
"swaps",
"swaps.return_order",
"swaps.payment",
@@ -0,0 +1,245 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
type: Validator.string()
.valid("replace", "refund")
.required(),
claim_items: Validator.array()
.items({
item_id: Validator.string().required(),
quantity: Validator.number().required(),
note: Validator.string().optional(),
reason: Validator.string().valid(
"missing_item",
"wrong_item",
"production_failure",
"other"
),
tags: Validator.array().items(Validator.string()),
images: Validator.array().items(Validator.string()),
})
.required(),
return_shipping: Validator.object()
.keys({
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.optional(),
additional_items: Validator.array()
.items({
variant_id: Validator.string().required(),
quantity: Validator.number().required(),
})
.optional(),
shipping_methods: Validator.array()
.items({
id: Validator.string().optional(),
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.optional(),
refund_amount: Validator.number()
.integer()
.optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const idempotencyKeyService = req.scope.resolve("idempotencyKeyService")
const headerKey = req.get("Idempotency-Key") || ""
let idempotencyKey
try {
idempotencyKey = await idempotencyKeyService.initializeRequest(
headerKey,
req.method,
req.params,
req.path
)
} catch (error) {
res.status(409).send("Failed to create idempotency key")
return
}
res.setHeader("Access-Control-Expose-Headers", "Idempotency-Key")
res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key)
try {
const orderService = req.scope.resolve("orderService")
const claimService = req.scope.resolve("claimService")
const returnService = req.scope.resolve("returnService")
let inProgress = true
let err = false
while (inProgress) {
switch (idempotencyKey.recovery_point) {
case "started": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
const order = await orderService
.withTransaction(manager)
.retrieve(id, {
relations: ["items", "discounts"],
})
await claimService.withTransaction(manager).create({
idempotency_key: idempotencyKey.idempotency_key,
order,
type: value.type,
claim_items: value.claim_items,
return_shipping: value.return_shipping,
additional_items: value.additional_items,
shipping_methods: value.shipping_methods,
metadata: value.metadata,
})
return {
recovery_point: "claim_created",
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "claim_created": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
let claim = await claimService.withTransaction(manager).list({
idempotency_key: idempotencyKey.idempotency_key,
})
if (!claim.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Claim not found`
)
}
claim = claim[0]
if (claim.type === "refund") {
await claimService
.withTransaction(manager)
.processRefund(claim.id)
}
return {
recovery_point: "refund_handled",
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "refund_handled": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
let order = await orderService
.withTransaction(manager)
.retrieve(id, {
relations: ["items", "discounts"],
})
let claim = await claimService.withTransaction(manager).list(
{
idempotency_key: idempotencyKey.idempotency_key,
},
{
relations: ["return_order"],
}
)
if (!claim.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Claim not found`
)
}
claim = claim[0]
if (claim.return_order) {
await returnService
.withTransaction(manager)
.fulfill(claim.return_order.id)
}
order = await orderService.withTransaction(manager).retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
return {
response_code: 200,
response_body: { order },
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "finished": {
inProgress = false
break
}
default:
idempotencyKey = await idempotencyKeyService.update(
idempotencyKey.idempotency_key,
{
recovery_point: "finished",
response_code: 500,
response_body: { message: "Unknown recovery point" },
}
)
break
}
}
if (err) {
throw err
}
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
} catch (error) {
throw error
}
}
@@ -0,0 +1,36 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id, claim_id } = req.params
const schema = Validator.object().keys({
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const claimService = req.scope.resolve("claimService")
const entityManager = req.scope.resolve("manager")
await entityManager.transaction(async manager => {
await claimService
.withTransaction(manager)
.createFulfillment(claim_id, value.metadata)
})
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
throw err
}
}
@@ -140,6 +140,27 @@ export default app => {
middlewares.wrap(require("./process-swap-payment").default)
)
/**
* Creates a claim
*/
route.post("/:id/claims", middlewares.wrap(require("./create-claim").default))
/**
* Updates a claim
*/
route.post(
"/:id/claims/:claim_id",
middlewares.wrap(require("./update-claim").default)
)
/**
* Creates claim fulfillment
*/
route.post(
"/:id/claims/:claim_id/fulfillments",
middlewares.wrap(require("./fulfill-claim").default)
)
/**
* Delete metadata key / value pair.
*/
@@ -162,6 +183,15 @@ export const defaultRelations = [
"returns",
"gift_cards",
"gift_card_transactions",
"claims",
"claims.return_order",
"claims.shipping_methods",
"claims.shipping_address",
"claims.additional_items",
"claims.fulfillments",
"claims.claim_items",
"claims.claim_items.images",
"claims.claim_items.tags",
"swaps",
"swaps.return_order",
"swaps.payment",
@@ -234,6 +264,7 @@ export const allowedRelations = [
"payments",
"fulfillments",
"returns",
"claims",
"swaps",
"swaps.return_order",
"swaps.additional_items",
@@ -0,0 +1,56 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id, claim_id } = req.params
const schema = Validator.object().keys({
claim_items: Validator.array()
.items({
id: Validator.string().required(),
note: Validator.string().allow(null, ""),
reason: Validator.string().allow(null, ""),
images: Validator.array().items({
id: Validator.string().optional(),
url: Validator.string().optional(),
}),
tags: Validator.array().items({
id: Validator.string().optional(),
value: Validator.string().optional(),
}),
metadata: Validator.object().optional(),
})
.optional(),
shipping_methods: Validator.array()
.items({
id: Validator.string().optional(),
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const claimService = req.scope.resolve("claimService")
await claimService.update(claim_id, value)
const data = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order: data })
} catch (error) {
throw error
}
}