+
Skip to content

feat: promotions discounts v2 #6575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/reaction/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@reactioncommerce/api-plugin-products": "1.3.1",
"@reactioncommerce/api-plugin-promotions": "1.0.0",
"@reactioncommerce/api-plugin-promotions-coupons": "1.0.0",
"@reactioncommerce/api-plugin-promotions-discounts": "1.0.0",
"@reactioncommerce/api-plugin-promotions-offers": "1.0.0",
"@reactioncommerce/api-plugin-settings": "1.0.7",
"@reactioncommerce/api-plugin-shipments": "1.0.3",
Expand Down
5 changes: 3 additions & 2 deletions apps/reaction/plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"notifications": "@reactioncommerce/api-plugin-notifications",
"addressValidationTest": "@reactioncommerce/api-plugin-address-validation-test",
"promotions": "@reactioncommerce/api-plugin-promotions",
"promotionsOffers": "@reactioncommerce/api-plugin-promotions-offers",
"promotionsCoupons": "@reactioncommerce/api-plugin-promotions-coupons"
"promotionsCoupons": "@reactioncommerce/api-plugin-promotions-coupons",
"promotionsDiscounts": "@reactioncommerce/api-plugin-promotions-discounts",
"promotionsOffers": "@reactioncommerce/api-plugin-promotions-offers"
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ beforeAll(async () => {
catalogItem = Factory.Catalog.makeOne({
isDeleted: false,
product: Factory.CatalogProduct.makeOne({
title: "Test Product",
isDeleted: false,
isVisible: true,
variants: Factory.CatalogProductVariant.makeMany(1, {
Expand Down Expand Up @@ -79,7 +80,8 @@ beforeAll(async () => {
anonymousAccessToken: hashToken(cartToken),
shipping: null,
items: [],
workflow: null
workflow: null,
discounts: []
});
opaqueCartId = encodeOpaqueId("reaction/cart", mockCart._id);
await testApp.collections.Cart.insertOne(mockCart);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import decodeOpaqueIdForNamespace from "@reactioncommerce/api-utils/decodeOpaqueIdForNamespace.js";
import importAsString from "@reactioncommerce/api-utils/importAsString.js";
import Factory from "/tests/util/factory.js";
import getCommonData from "../checkout/checkoutTestsCommon.js";

const AnonymousCartByCartIdQuery = importAsString("../checkout/AnonymousCartByCartIdQuery.graphql");
const SetEmailOnAnonymousCart = importAsString("../checkout/SetEmailOnAnonymousCartMutation.graphql");

let anonymousCartByCartQuery;
let availablePaymentMethods;
let createCart;
let encodeProductOpaqueId;
let internalVariantIds;
let opaqueProductId;
let opaqueShopId;
let placeOrder;
let selectFulfillmentOptionForGroup;
let setEmailOnAnonymousCart;
let setShippingAddressOnCart;
let testApp;
let updateFulfillmentOptionsForGroup;
let mockPromotion;

beforeAll(async () => {
({
availablePaymentMethods,
createCart,
encodeProductOpaqueId,
internalVariantIds,
opaqueProductId,
opaqueShopId,
placeOrder,
selectFulfillmentOptionForGroup,
setShippingAddressOnCart,
testApp,
updateFulfillmentOptionsForGroup
} = getCommonData());

anonymousCartByCartQuery = testApp.mutate(AnonymousCartByCartIdQuery);
setEmailOnAnonymousCart = testApp.mutate(SetEmailOnAnonymousCart);

const now = new Date();
mockPromotion = Factory.Promotion.makeOne({
actions: [
{
actionKey: "discounts",
actionParameters: {
discountType: "order",
discountCalculationType: "percentage",
discountValue: 50
}
}
],
triggers: [
{
triggerKey: "offers",
triggerParameters: {
name: "50 percent off your entire order when you spend more then $100",
conditions: {
all: [
{
fact: "totalItemAmount",
operator: "greaterThanInclusive",
value: 100
}
]
}
}
}
],
triggerType: "implicit",
promotionType: "order-discount",
startDate: now,
endDate: new Date(now.getTime() + 1000 * 60 * 60 * 24 * 7),
enabled: true,
shopId: decodeOpaqueIdForNamespace("reaction/shop")(opaqueShopId)
});

await testApp.collections.Promotions.insertOne(mockPromotion);
});

// There is no need to delete any test data from collections because
// testApp.stop() will drop the entire test database. Each integration
// test file gets its own test database.
afterAll(() => testApp.stop());

describe("Promotions", () => {
let cartToken;
let opaqueCartId;
let opaqueCartProductVariantId;
let opaqueFulfillmentGroupId;
let opaqueFulfillmentMethodId;
let latestCartSummary;

beforeAll(async () => {
opaqueCartProductVariantId = encodeProductOpaqueId(internalVariantIds[1]);
await testApp.clearLoggedInUser();
});

const shippingAddress = {
address1: "12345 Drive Lane",
city: "The city",
country: "USA",
firstName: "FName",
fullName: "FName LName",
isBillingDefault: false,
isCommercial: false,
isShippingDefault: false,
lastName: "LName",
phone: "5555555555",
postal: "97878",
region: "CA"
};

test("create a new cart", async () => {
const result = await createCart({
createCartInput: {
shopId: opaqueShopId,
items: {
price: {
amount: 19.99,
currencyCode: "USD"
},
productConfiguration: {
productId: opaqueProductId,
productVariantId: opaqueCartProductVariantId
},
quantity: 6
}
}
});

cartToken = result.createCart.token;
opaqueCartId = result.createCart.cart._id;
});

test("set email on anonymous cart", async () => {
const result = await setEmailOnAnonymousCart({
input: {
cartId: opaqueCartId,
cartToken,
email: "test@email.com"
}
});

opaqueCartId = result.setEmailOnAnonymousCart.cart._id;
});

test("set shipping address on cart", async () => {
const result = await setShippingAddressOnCart({
input: {
cartId: opaqueCartId,
cartToken,
address: {
address1: "12345 Drive Lane",
city: "The city",
country: "USA",
firstName: "FName",
fullName: "FName LName",
lastName: "LName",
phone: "5555555555",
postal: "97878",
region: "CA"
}
}
});

opaqueFulfillmentGroupId = result.setShippingAddressOnCart.cart.checkout.fulfillmentGroups[0]._id;
});

test("get available fulfillment options", async () => {
const result = await updateFulfillmentOptionsForGroup({
input: {
cartId: opaqueCartId,
cartToken,
fulfillmentGroupId: opaqueFulfillmentGroupId
}
});

const option = result.updateFulfillmentOptionsForGroup.cart.checkout.fulfillmentGroups[0].availableFulfillmentOptions[0];
opaqueFulfillmentMethodId = option.fulfillmentMethod._id;
});

test("select the `Standard mockMethod` fulfillment option", async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are set up like separate tests but in fact there is only one test, which should describe what it's testing, e.g. "place an order with discount and get the correct values"

const result = await selectFulfillmentOptionForGroup({
input: {
cartId: opaqueCartId,
cartToken,
fulfillmentGroupId: opaqueFulfillmentGroupId,
fulfillmentMethodId: opaqueFulfillmentMethodId
}
});

latestCartSummary = result.selectFulfillmentOptionForGroup.cart.checkout.summary;
});

test("place an order with discount and get the correct values", async () => {
let result;

const paymentMethods = await availablePaymentMethods({
shopId: opaqueShopId
});

const paymentMethodName = paymentMethods.availablePaymentMethods[0].name;

const { anonymousCartByCartId: anonymousCart } = await anonymousCartByCartQuery({
cartId: opaqueCartId,
cartToken
});

try {
result = await placeOrder({
input: {
order: {
cartId: opaqueCartId,
currencyCode: "USD",
email: anonymousCart.email,
fulfillmentGroups: [
{
data: {
shippingAddress
},
items: [
{
price: 19.99,
productConfiguration: {
productId: opaqueProductId,
productVariantId: opaqueCartProductVariantId
},
quantity: 6
}
],
selectedFulfillmentMethodId: opaqueFulfillmentMethodId,
shopId: opaqueShopId,
type: "shipping",
totalPrice: latestCartSummary.total.amount
}
],
shopId: opaqueShopId
},
payments: [
{
amount: latestCartSummary.total.amount,
method: paymentMethodName
}
]
}
});
} catch (error) {
expect(error).toBeUndefined();
return;
}

const orderId = decodeOpaqueIdForNamespace("reaction/order")(result.placeOrder.orders[0]._id);
const newOrder = await testApp.collections.Orders.findOne({ _id: orderId });

expect(newOrder.shipping[0].invoice.total).toEqual(62.47);
expect(newOrder.shipping[0].invoice.discounts).toEqual(59.97);
expect(newOrder.shipping[0].invoice.subtotal).toEqual(119.94);

expect(newOrder.shipping[0].items[0].quantity).toEqual(6);
expect(newOrder.shipping[0].items[0].discounts).toHaveLength(1);
expect(newOrder.shipping[0].items[0].discount).toEqual(59.97);

expect(newOrder.appliedPromotions[0]._id).toEqual(mockPromotion._id);
expect(newOrder.discounts).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ beforeAll(async () => {
anonymousAccessToken: hashToken(cartToken),
shipping: null,
items: [],
workflow: null
workflow: null,
discounts: []
});

opaqueCartId = encodeOpaqueId("reaction/cart", mockCart._id);
Expand Down Expand Up @@ -69,6 +70,7 @@ test("anonymous cart query works after a related catalog product is hidden", asy
isDeleted: false,
isVisible: true,
product: Factory.CatalogProduct.makeOne({
title: "Test Product",
productId: "1",
isDeleted: false,
isVisible: true,
Expand Down Expand Up @@ -130,6 +132,7 @@ test("anonymous cart query works after a related catalog product is deleted", as
isDeleted: false,
isVisible: true,
product: Factory.CatalogProduct.makeOne({
title: "Test Product",
productId: "2",
isDeleted: false,
isVisible: true,
Expand Down
7 changes: 6 additions & 1 deletion apps/reaction/tests/util/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ import {
TaxRates
} from "@reactioncommerce/api-plugin-taxes-flat-rate/src/simpleSchemas.js";

import {
Promotion
} from "@reactioncommerce/api-plugin-promotions/src/simpleSchemas.js";


const schemasToAddToFactory = {
Account,
Expand Down Expand Up @@ -141,7 +145,8 @@ const schemasToAddToFactory = {
Sitemap,
Surcharge,
Tag,
TaxRates
TaxRates,
Promotion
};

// Extend before creating factories in case some of the added fields
Expand Down
7 changes: 5 additions & 2 deletions packages/api-plugin-carts/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import queries from "./queries/index.js";
import resolvers from "./resolvers/index.js";
import schemas from "./schemas/index.js";
import { registerPluginHandlerForCart } from "./registration.js";
import { Cart, CartItem } from "./simpleSchemas.js";
import { Cart, CartItem, Shipment, ShipmentQuote, ShippingMethod } from "./simpleSchemas.js";
import startup from "./startup.js";

/**
Expand Down Expand Up @@ -59,7 +59,10 @@ export default async function register(app) {
policies,
simpleSchemas: {
Cart,
CartItem
CartItem,
Shipment,
ShippingMethod,
ShipmentQuote
}
});
}
4 changes: 2 additions & 2 deletions packages/api-plugin-carts/src/simpleSchemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const ShippoShippingMethod = new SimpleSchema({
* @property {String} carrier optional
* @property {ShippoShippingMethod} settings optional
*/
const ShippingMethod = new SimpleSchema({
export const ShippingMethod = new SimpleSchema({
"_id": {
type: String,
label: "Shipment Method Id"
Expand Down Expand Up @@ -532,7 +532,7 @@ export const CartInvoice = new SimpleSchema({
* @property {String} customsLabelUrl For customs printable label
* @property {ShippoShipment} shippo For Shippo specific properties
*/
const Shipment = new SimpleSchema({
export const Shipment = new SimpleSchema({
"_id": {
type: String,
label: "Shipment Id"
Expand Down
2 changes: 1 addition & 1 deletion packages/api-plugin-carts/src/xforms/xformCartCheckout.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function xformCartFulfillmentGroup(fulfillmentGroup, cart) {
*/
export default async function xformCartCheckout(collections, cart) {
// itemTotal is qty * amount for each item, summed
const itemTotal = (cart.items || []).reduce((sum, item) => (sum + item.subtotal.amount), 0);
const itemTotal = (cart.items || []).reduce((sum, item) => (sum + (item.price.amount * item.quantity)), 0);

// shippingTotal is shipmentMethod.rate for each item, summed
// handlingTotal is shipmentMethod.handling for each item, summed
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载