Skip to content
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

Add support for Dummy payment app #1090

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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 .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
8 changes: 7 additions & 1 deletion src/checkout/hooks/useCheckoutComplete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMemo } from "react";
import { useAlerts } from "./useAlerts";
import { useCheckoutCompleteMutation } from "@/checkout/graphql";
import { useCheckout } from "@/checkout/hooks/useCheckout";
import { useSubmit } from "@/checkout/hooks/useSubmit";
Expand All @@ -9,6 +10,7 @@ export const useCheckoutComplete = () => {
checkout: { id: checkoutId },
} = useCheckout();
const [{ fetching }, checkoutComplete] = useCheckoutCompleteMutation();
const { showCustomErrors } = useAlerts();

const onCheckoutComplete = useSubmit<{}, typeof checkoutComplete>(
useMemo(
Expand All @@ -30,8 +32,12 @@ export const useCheckoutComplete = () => {
window.location.href = newUrl;
}
},
onError: ({ errors }) => {
const error = errors.join(", ");
showCustomErrors([{ message: error }]);
},
}),
[checkoutComplete, checkoutId],
[checkoutComplete, checkoutId, showCustomErrors],
),
);
return { completingCheckout: fetching, onCheckoutComplete };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from "react";
import { type ParsedDummyGateway } from "../types";
import { apiErrorMessages } from "../errorMessages";
import {
useTransactionInitializeMutation,
type TransactionInitializeMutationVariables,
} from "@/checkout/graphql";
import { useCheckout } from "@/checkout/hooks/useCheckout";
import { useSubmit } from "@/checkout/hooks/useSubmit";
import { Button } from "@/checkout/components";
import { useCheckoutComplete } from "@/checkout/hooks/useCheckoutComplete";
import { useAlerts } from "@/checkout/hooks/useAlerts";
import { useErrorMessages } from "@/checkout/hooks/useErrorMessages";

type DummyDropinProps = {
config: ParsedDummyGateway;
};

const useDummyDropIn = (props: DummyDropinProps) => {
const { showCustomErrors } = useAlerts();
const { errorMessages: commonErrorMessages } = useErrorMessages(apiErrorMessages);

const { checkout } = useCheckout();
const { onCheckoutComplete, completingCheckout: isCompleteCheckoutLoading } = useCheckoutComplete();

const [{ fetching: isTransactionInitializeLoading }, transactionInitialize] =
useTransactionInitializeMutation();

const isLoading = isCompleteCheckoutLoading || isTransactionInitializeLoading;
const isValid = !!checkout.shippingAddress && !!checkout.email && !!checkout.billingAddress;

const isDisabled = isLoading || !isValid;

const onTransactionInitialize = useSubmit<
TransactionInitializeMutationVariables,
typeof transactionInitialize
>(
React.useMemo(
() => ({
onSubmit: transactionInitialize,
onError: (error) => {
console.error(error);
showCustomErrors([{ message: commonErrorMessages.somethingWentWrong }]);
},
onSuccess: async () => {
void onCheckoutComplete();
},
}),
[commonErrorMessages.somethingWentWrong, onCheckoutComplete, showCustomErrors, transactionInitialize],
),
);

const onSubmit = async () => {
await onTransactionInitialize({
checkoutId: checkout.id,
paymentGateway: {
id: props.config.id,
},
});
};

return { onSubmit, isLoading, isDisabled };
};

export const DummyDropIn = (props: DummyDropinProps) => {
const { onSubmit, isLoading, isDisabled } = useDummyDropIn(props);

return (
<div className="border-2 border-dashed p-4">
<h2 className="text-lg font-semibold text-gray-800">Dummy Payment Gateway</h2>
{/* <h3 className="text-sm font-semibold text-gray-600" data-testid="dummy-payment-gateway-name">
Stored payment methods
</h3> */}
<Button disabled={isDisabled} label={isLoading ? "Loading..." : "Pay with 🤪"} onClick={onSubmit} />
</div>
);
};
2 changes: 2 additions & 0 deletions src/checkout/sections/PaymentSection/DummyPayment/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const dummyGatewayId = "dummy.payment.saleor.app";
export type DummyGatewayId = typeof dummyGatewayId;
6 changes: 6 additions & 0 deletions src/checkout/sections/PaymentSection/PaymentMethods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export const PaymentMethods = () => {
<div className="gap-y-8">
{availablePaymentGateways.map((gateway) => {
const Component = paymentMethodToComponent[gateway.id];

if (!Component) {
console.warn(`No component found for gateway ${gateway.id}`);
return null;
}

return (
<Component
key={gateway.id}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { AdyenDropIn } from "./AdyenDropIn/AdyenDropIn";
import { adyenGatewayId } from "./AdyenDropIn/types";
import { DummyDropIn } from "./DummyPayment/DummyDropIn";
import { dummyGatewayId } from "./DummyPayment/types";
import { StripeComponent } from "./StripeElements/stripeComponent";
import { stripeGatewayId } from "./StripeElements/types";

export const paymentMethodToComponent = {
[adyenGatewayId]: AdyenDropIn,
[stripeGatewayId]: StripeComponent,
[dummyGatewayId]: DummyDropIn,
};
8 changes: 6 additions & 2 deletions src/checkout/sections/PaymentSection/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { type StripeGatewayId } from "./StripeElements/types";
import { type DummyGatewayId } from "./DummyPayment/types";
import { type PaymentGatewayConfig } from "@/checkout/graphql";
import {
type AdyenGatewayId,
type AdyenGatewayInitializePayload,
} from "@/checkout/sections/PaymentSection/AdyenDropIn/types";

export type PaymentGatewayId = AdyenGatewayId | StripeGatewayId;
export type PaymentGatewayId = AdyenGatewayId | StripeGatewayId | DummyGatewayId;

export type ParsedAdyenGateway = ParsedPaymentGateway<AdyenGatewayId, AdyenGatewayInitializePayload>;
export type ParsedStripeGateway = ParsedPaymentGateway<StripeGatewayId, {}>;
export type ParsedDummyGateway = ParsedPaymentGateway<DummyGatewayId, {}>;

export type ParsedPaymentGateways = ReadonlyArray<ParsedAdyenGateway | ParsedStripeGateway>;
export type ParsedPaymentGateways = ReadonlyArray<
ParsedAdyenGateway | ParsedStripeGateway | ParsedDummyGateway
>;

export interface ParsedPaymentGateway<ID extends string, TData extends Record<string, any>>
extends Omit<PaymentGatewayConfig, "data" | "id"> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import { getFilteredPaymentGateways } from "@/checkout/sections/PaymentSection/u

export const usePaymentGatewaysInitialize = () => {
const {
checkout: { billingAddress },
} = useCheckout();
const {
checkout: { id: checkoutId, availablePaymentGateways },
checkout: { id: checkoutId, availablePaymentGateways, billingAddress },
} = useCheckout();

const billingCountry = billingAddress?.country.code as MightNotExist<CountryCode>;
Expand Down