feat: Allow backorder on swaps (#404)

This commit is contained in:
Oliver Windall Juhl
2021-09-19 15:33:43 +02:00
committed by GitHub
parent 75b608330b
commit 00ab03f3a2
20 changed files with 1038 additions and 393 deletions
@@ -48,6 +48,9 @@ import { defaultFields, defaultRelations } from "./"
* no_notification:
* description: If set to true no notification will be send related to this Swap.
* type: boolean
* allow_backorder:
* description: If true, swaps can be completed with items out of stock
* type: boolean
* tags:
* - Order
* responses:
@@ -83,6 +86,7 @@ export default async (req, res) => {
quantity: Validator.number().required(),
}),
no_notification: Validator.boolean().optional(),
allow_backorder: Validator.boolean().default(true),
})
const { value, error } = schema.validate(req.body)
@@ -141,6 +145,7 @@ export default async (req, res) => {
{
idempotency_key: idempotencyKey.idempotency_key,
no_notification: value.no_notification,
allow_backorder: value.allow_backorder,
}
)
@@ -138,18 +138,36 @@ export default async (req, res) => {
// If cart is part of swap, we register swap as complete
switch (cart.type) {
case "swap": {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
try {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
}
} catch (error) {
if (
error &&
error.code === MedusaError.Codes.INSUFFICIENT_INVENTORY
) {
return {
response_code: 409,
response_body: {
message: error.message,
type: error.type,
code: error.code,
},
}
} else {
throw error
}
}
}
// case "payment_link":
@@ -0,0 +1,24 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class allowBackorderSwaps1630505790603 implements MigrationInterface {
name = 'allowBackorderSwaps1630505790603'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "swap" ADD "allow_backorder" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "cart" ADD "payment_authorized_at" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum" RENAME TO "swap_payment_status_enum_old"`);
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum" AS ENUM('not_paid', 'awaiting', 'captured', 'confirmed', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum" USING "payment_status"::"text"::"swap_payment_status_enum"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum_old"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum_old" AS ENUM('not_paid', 'awaiting', 'captured', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum_old" USING "payment_status"::"text"::"swap_payment_status_enum_old"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum_old" RENAME TO "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "payment_authorized_at"`);
await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "allow_backorder"`);
}
}
+3
View File
@@ -230,6 +230,9 @@ export class Cart {
@Column({ type: resolveDbType("timestamptz"), nullable: true })
completed_at: Date
@Column({ type: resolveDbType("timestamptz"), nullable: true })
payment_authorized_at: Date
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
+7
View File
@@ -38,6 +38,7 @@ export enum PaymentStatus {
NOT_PAID = "not_paid",
AWAITING = "awaiting",
CAPTURED = "captured",
CONFIRMED = "confirmed",
CANCELED = "canceled",
DIFFERENCE_REFUNDED = "difference_refunded",
PARTIALLY_REFUNDED = "partially_refunded",
@@ -137,6 +138,9 @@ export class Swap {
@Column({ type: "boolean", nullable: true })
no_notification: Boolean
@Column({ type: "boolean", default: false })
allow_backorder: Boolean
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
@@ -219,6 +223,9 @@ export class Swap {
* cart_id:
* description: "The id of the Cart that the Customer will use to confirm the Swap."
* type: string
* allow_backorder:
* description: "If true, swaps can be completed with items out of stock"
* type: boolean
* confirmed_at:
* description: "The date with timezone at which the Swap was confirmed by the Customer."
* type: string
@@ -203,6 +203,7 @@ describe("OrderService", () => {
}
orderService.cartService_.retrieve = jest.fn(() => Promise.resolve(cart))
orderService.cartService_.update = jest.fn(() => Promise.resolve())
await orderService.createFromCart("cart_id")
const order = {
@@ -305,6 +306,7 @@ describe("OrderService", () => {
orderService.cartService_.retrieve = () => {
return Promise.resolve(cart)
}
orderService.cartService_.update = () => Promise.resolve()
await orderService.createFromCart("cart_id")
const order = {
@@ -453,6 +455,7 @@ describe("OrderService", () => {
total: 100,
}
orderService.cartService_.retrieve = () => Promise.resolve(cart)
orderService.cartService_.update = () => Promise.resolve()
const res = orderService.createFromCart(cart)
await expect(res).rejects.toThrow(
"Variant with id: variant-1 does not have the required inventory"
@@ -741,6 +741,13 @@ describe("SwapService", () => {
},
}
const cartService = {
update: jest.fn(),
withTransaction: function() {
return this
},
}
const swapRepo = MockRepository({
findOneWithRelations: () => Promise.resolve(existing),
})
@@ -752,6 +759,7 @@ describe("SwapService", () => {
lineItemService,
eventBusService,
fulfillmentService,
cartService,
})
it("creates a shipment", async () => {
@@ -831,6 +839,15 @@ describe("SwapService", () => {
},
}
const cartService = {
update: () => {
return Promise.resolve()
},
withTransaction: function() {
return this
},
}
const paymentProviderService = {
getStatus: jest.fn(() => {
return Promise.resolve("authorized")
@@ -838,6 +855,9 @@ describe("SwapService", () => {
updatePayment: jest.fn(() => {
return Promise.resolve()
}),
cancelPayment: jest.fn(() => {
return Promise.resolve()
}),
withTransaction: function() {
return this
},
@@ -872,6 +892,7 @@ describe("SwapService", () => {
eventBusService,
swapRepository: swapRepo,
totalsService,
cartService,
paymentProviderService,
eventBusService,
shippingOptionService,
@@ -933,6 +954,7 @@ describe("SwapService", () => {
eventBusService,
swapRepository: swapRepo,
totalsService,
cartService,
paymentProviderService,
eventBusService,
shippingOptionService,
+18 -3
View File
@@ -682,6 +682,14 @@ class CartService extends BaseService {
}
}
if ("completed_at" in update) {
cart.completed_at = update.completed_at
}
if ("payment_authorized_at" in update) {
cart.payment_authorized_at = update.payment_authorized_at
}
const result = await cartRepo.save(cart)
if ("email" in update || "customer_id" in update) {
@@ -1027,7 +1035,7 @@ class CartService extends BaseService {
// If cart total is 0, we don't perform anything payment related
if (cart.total <= 0) {
cart.completed_at = new Date()
cart.payment_authorized_at = new Date()
return cartRepository.save(cart)
}
@@ -1046,7 +1054,7 @@ class CartService extends BaseService {
.createPayment(freshCart)
freshCart.payment = payment
freshCart.completed_at = new Date()
freshCart.payment_authorized_at = new Date()
}
const updated = await cartRepository.save(freshCart)
@@ -1352,7 +1360,7 @@ class CartService extends BaseService {
* @return {Promise} the result of the update operation
*/
async setRegion_(cart, regionId, countryCode) {
if (cart.completed_at) {
if (cart.completed_at || cart.payment_authorized_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Cannot change the region of a completed cart"
@@ -1494,6 +1502,13 @@ class CartService extends BaseService {
)
}
if (cart.payment_authorized_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't delete a cart with an authorized payment"
)
}
const cartRepo = manager.getCustomRepository(this.cartRepository_)
return cartRepo.remove(cartId)
})
+7
View File
@@ -477,6 +477,9 @@ class OrderService extends BaseService {
.withTransaction(manager)
.cancelPayment(payment)
}
await this.cartService_
.withTransaction(manager)
.update(cart.id, { payment_authorized_at: null })
throw err
}
}
@@ -595,6 +598,10 @@ class OrderService extends BaseService {
no_notification: result.no_notification,
})
await this.cartService_
.withTransaction(manager)
.update(cart.id, { completed_at: new Date() })
return result
})
}
@@ -328,7 +328,7 @@ class PaymentProviderService extends BaseService {
payment.canceled_at = now.toISOString()
const paymentRepo = manager.getCustomRepository(this.paymentRepository_)
return paymentRepo.save(payment)
return await paymentRepo.save(payment)
})
}
+24 -7
View File
@@ -665,20 +665,33 @@ class SwapService extends BaseService {
}
const cart = swap.cart
const { payment } = cart
const items = swap.cart.items
for (const item of items) {
await this.inventoryService_
.withTransaction(manager)
.confirmInventory(item.variant_id, item.quantity)
if (!swap.allow_backorder) {
for (const item of items) {
try {
await this.inventoryService_
.withTransaction(manager)
.confirmInventory(item.variant_id, item.quantity)
} catch (err) {
if (payment) {
await this.paymentProviderService_
.withTransaction(manager)
.cancelPayment(payment)
}
await this.cartService_
.withTransaction(manager)
.update(cart.id, { payment_authorized_at: null })
throw err
}
}
}
const total = await this.totalsService_.getTotal(cart)
if (total > 0) {
const { payment } = cart
if (!payment) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -717,7 +730,7 @@ class SwapService extends BaseService {
swap.shipping_address_id = cart.shipping_address_id
swap.shipping_methods = cart.shipping_methods
swap.confirmed_at = now.toISOString()
swap.payment_status = total === 0 ? "difference_refunded" : "awaiting"
swap.payment_status = total === 0 ? "confirmed" : "awaiting"
const swapRepo = manager.getCustomRepository(this.swapRepository_)
const result = await swapRepo.save(swap)
@@ -737,6 +750,10 @@ class SwapService extends BaseService {
no_notification: swap.no_notification,
})
await this.cartService_
.withTransaction(manager)
.update(cart.id, { completed_at: new Date() })
return result
})
}