Skip to content
This repository has been archived by the owner on Jul 14, 2022. It is now read-only.

Require payment recreate when payment price is wrong #892

Merged
merged 5 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Update collection products query - #879 by @orzechdev
- Fix checkout refreshing - #865 by @orzechdev
- Add purchase availability to product details page - #878 by @orzechdev
- Require payment recreate when payment price is wrong - #892 by @orzechdev

## 2.10.4

Expand Down
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"@lhci/cli": "^0.4.1",
"@saleor/sdk": "^0.1.0",
"@saleor/sdk": "github:mirumee/saleor-sdk#93e31be",
"@sentry/apm": "^5.15.5",
"@sentry/browser": "^5.15.5",
"@stripe/react-stripe-js": "^1.1.2",
Expand Down
66 changes: 49 additions & 17 deletions src/@next/hooks/useCheckoutStepState.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { useEffect, useState } from "react";

import { IItems } from "@saleor/sdk/lib/api/Cart/types";
import { IItems, ITotalPrice } from "@saleor/sdk/lib/api/Cart/types";
import { ICheckout, IPayment } from "@saleor/sdk/lib/api/Checkout/types";
import { CheckoutStep } from "@temp/core/config";
import { checkIfShippingRequiredForProducts } from "@utils/core";
import { isPriceEqual } from "@utils/money";

interface StepState {
recommendedStep: CheckoutStep;
maxPossibleStep: CheckoutStep;
}

export const useCheckoutStepState = (
items?: IItems,
checkout?: ICheckout,
payment?: IPayment
): CheckoutStep => {
const isShippingRequiredForProducts =
items &&
items.some(
({ variant }) => variant.product?.productType.isShippingRequired
);

const getStep = () => {
payment?: IPayment,
totalPrice?: ITotalPrice
): StepState => {
const isShippingRequiredForProducts = checkIfShippingRequiredForProducts(
items
);
const isCheckoutPriceEqualPaymentPrice =
payment?.total &&
totalPrice?.gross &&
isPriceEqual(payment.total, totalPrice.gross);

const getMaxPossibleStep = () => {
if (!checkout?.id && items) {
// we are creating checkout during address set up
return CheckoutStep.Address;
Expand All @@ -26,7 +36,8 @@ export const useCheckoutStepState = (
const isBillingAddressSet = !!checkout?.billingAddress;
const isShippingMethodSet =
!isShippingRequiredForProducts || !!checkout?.shippingMethod;
const isPaymentMethodSet = !!payment?.id;
const isPaymentMethodSet =
!!payment?.id && isCheckoutPriceEqualPaymentPrice;

if (!isShippingAddressSet || !isBillingAddressSet) {
return CheckoutStep.Address;
Expand All @@ -40,14 +51,35 @@ export const useCheckoutStepState = (
return CheckoutStep.Review;
};

const [step, setStep] = useState(getStep());
const getRecommendedStep = (newMaxPossibleStep: CheckoutStep) => {
const isPaymentRecreateRequired =
newMaxPossibleStep > CheckoutStep.Shipping &&
!isCheckoutPriceEqualPaymentPrice;

if (isPaymentRecreateRequired && isShippingRequiredForProducts) {
return CheckoutStep.Shipping;
}
if (isPaymentRecreateRequired) {
return CheckoutStep.Payment;
}
return newMaxPossibleStep;
};

const [maxPossibleStep, setMaxPossibleStep] = useState(getMaxPossibleStep());
const [recommendedStep, setRecommendedStep] = useState(
getRecommendedStep(maxPossibleStep)
);

useEffect(() => {
const newStep = getStep();
if (step !== newStep) {
setStep(newStep);
const newMaxPossibleStep = getMaxPossibleStep();
const newRecommendedStep = getRecommendedStep(newMaxPossibleStep);
if (maxPossibleStep !== newMaxPossibleStep) {
setMaxPossibleStep(newMaxPossibleStep);
}
if (recommendedStep !== newRecommendedStep) {
setRecommendedStep(newRecommendedStep);
}
}, [checkout, items, payment]);
}, [checkout, items, payment, totalPrice]);

return step;
return { recommendedStep, maxPossibleStep };
};
1 change: 1 addition & 0 deletions src/@next/pages/CheckoutPage/CheckoutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ const CheckoutPage: React.FC<IProps> = ({}: IProps) => {
items={items}
checkout={checkout}
payment={payment}
totalPrice={totalPrice}
renderAddress={props => (
<CheckoutAddressSubpage
ref={checkoutAddressSubpageRef}
Expand Down
23 changes: 18 additions & 5 deletions src/@next/pages/CheckoutPage/CheckoutRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import {
} from "react-router-dom";

import { useCheckoutStepFromPath, useCheckoutStepState } from "@hooks";
import { IItems } from "@saleor/sdk/lib/api/Cart/types";
import { IItems, ITotalPrice } from "@saleor/sdk/lib/api/Cart/types";
import { ICheckout, IPayment } from "@saleor/sdk/lib/api/Checkout/types";
import { CHECKOUT_STEPS } from "@temp/core/config";
import { checkIfShippingRequiredForProducts } from "@utils/core";

interface IRouterProps {
items?: IItems;
checkout?: ICheckout;
payment?: IPayment;
totalPrice?: ITotalPrice;
renderAddress: (props: RouteComponentProps<any>) => React.ReactNode;
renderShipping: (props: RouteComponentProps<any>) => React.ReactNode;
renderPayment: (props: RouteComponentProps<any>) => React.ReactNode;
Expand All @@ -26,22 +28,33 @@ const CheckoutRouter: React.FC<IRouterProps> = ({
items,
checkout,
payment,
totalPrice,
renderAddress,
renderShipping,
renderPayment,
renderReview,
}: IRouterProps) => {
const { pathname } = useLocation();
const step = useCheckoutStepState(items, checkout, payment);
const { recommendedStep, maxPossibleStep } = useCheckoutStepState(
items,
checkout,
payment,
totalPrice
);
const stepFromPath = useCheckoutStepFromPath(pathname);

const isShippingRequiredForProducts = checkIfShippingRequiredForProducts(
items
);

const getStepLink = () =>
CHECKOUT_STEPS.find(stepObj => stepObj.step === step)?.link ||
CHECKOUT_STEPS.find(stepObj => stepObj.step === recommendedStep)?.link ||
CHECKOUT_STEPS[0].link;

if (
pathname !== CHECKOUT_STEPS[4].link &&
(!stepFromPath || (stepFromPath && step < stepFromPath))
(pathname !== CHECKOUT_STEPS[4].link &&
(!stepFromPath || (stepFromPath && maxPossibleStep < stepFromPath))) ||
(pathname === CHECKOUT_STEPS[1].link && !isShippingRequiredForProducts)
) {
return <Redirect to={getStepLink()} />;
}
Expand Down
15 changes: 7 additions & 8 deletions src/@next/types/ITaxedMoney.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
export interface IMoney {
amount: number;
currency: string;
}

export interface ITaxedMoney {
net: {
amount: number;
currency: string;
};
gross: {
amount: number;
currency: string;
};
net: IMoney;
gross: IMoney;
}
5 changes: 5 additions & 0 deletions src/@next/utils/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// @ts-ignore
import { Base64 } from "js-base64";

import { IItems } from "@saleor/sdk/lib/api/Cart/types";

export const slugify = (text: string | number): string =>
text
.toString()
Expand Down Expand Up @@ -36,3 +38,6 @@ export const generatePageUrl = (slug: string) => `/page/${slug}/`;

export const generateGuestOrderDetailsUrl = (token: string) =>
`/order-history/${token}/`;

export const checkIfShippingRequiredForProducts = (items?: IItems) =>
items?.some(({ variant }) => variant.product?.productType.isShippingRequired);
46 changes: 46 additions & 0 deletions src/@next/utils/money.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { IMoney } from "@types";

import { isPriceEqual } from "./money";

const firstPrice: IMoney = {
amount: 100,
currency: "USD",
};

describe("Comparing two prices", () => {
it("Returns true if amount and currency is same", () => {
const secondPrice: IMoney = {
amount: 100,
currency: "USD",
};

expect(isPriceEqual(firstPrice, secondPrice)).toBeTruthy();
});

it("Returns false if amount is same and currency is not", () => {
const secondPrice: IMoney = {
amount: 100,
currency: "PLN",
};

expect(isPriceEqual(firstPrice, secondPrice)).toBeFalsy();
});

it("Returns false if currency is same and amount is not", () => {
const secondPrice: IMoney = {
amount: 200,
currency: "USD",
};

expect(isPriceEqual(firstPrice, secondPrice)).toBeFalsy();
});

it("Returns false if amount and currency is not same", () => {
const secondPrice: IMoney = {
amount: 200,
currency: "PLN",
};

expect(isPriceEqual(firstPrice, secondPrice)).toBeFalsy();
});
});
4 changes: 4 additions & 0 deletions src/@next/utils/money.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { IMoney } from "@types";

export const isPriceEqual = (first: IMoney, second: IMoney) =>
first.amount === second.amount && first.currency === second.currency;