Skip to content

Commit

Permalink
Add support for Givex (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofzuraw authored Aug 29, 2024
1 parent fc1e17b commit 0985c0d
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 58 deletions.
4 changes: 2 additions & 2 deletions src/graphql-cache.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ declare module 'gql.tada' {
TadaDocumentNode<{ gross: { amount: number; currency: string; }; }, {}, { fragment: "TotalPrice"; on: "TaxedMoney"; masked: true; }>;
"\n query GetCheckoutTotalPrice($checkoutId: ID!) {\n checkout(id: $checkoutId) {\n totalPrice {\n ...TotalPrice\n }\n }\n }\n ":
TadaDocumentNode<{ checkout: { totalPrice: { [$tada.fragmentRefs]: { TotalPrice: "TaxedMoney"; }; }; } | null; }, { checkoutId: string; }, void>;
"\n mutation initalizePaymentGateway(\n $checkoutId: ID!\n $paymentGatewayId: String!\n $amount: PositiveDecimal!\n ) {\n paymentGatewayInitialize(\n id: $checkoutId\n paymentGateways: [{ id: $paymentGatewayId }]\n amount: $amount\n ) {\n gatewayConfigs {\n id\n data\n errors {\n field\n message\n code\n }\n }\n errors {\n field\n message\n code\n }\n }\n }\n":
TadaDocumentNode<{ paymentGatewayInitialize: { gatewayConfigs: { id: string; data: unknown; errors: { field: string | null; message: string | null; code: "GRAPHQL_ERROR" | "INVALID" | "NOT_FOUND"; }[] | null; }[] | null; errors: { field: string | null; message: string | null; code: "GRAPHQL_ERROR" | "INVALID" | "NOT_FOUND"; }[]; } | null; }, { amount: unknown; paymentGatewayId: string; checkoutId: string; }, void>;
"\n mutation initalizePaymentGateway(\n $checkoutId: ID!\n $paymentGatewayId: String!\n $amount: PositiveDecimal\n $data: JSON\n ) {\n paymentGatewayInitialize(\n id: $checkoutId\n paymentGateways: [{ id: $paymentGatewayId, data: $data }]\n amount: $amount\n ) {\n gatewayConfigs {\n id\n data\n errors {\n field\n message\n code\n }\n }\n errors {\n field\n message\n code\n }\n }\n }\n":
TadaDocumentNode<{ paymentGatewayInitialize: { gatewayConfigs: { id: string; data: unknown; errors: { field: string | null; message: string | null; code: "GRAPHQL_ERROR" | "INVALID" | "NOT_FOUND"; }[] | null; }[] | null; errors: { field: string | null; message: string | null; code: "GRAPHQL_ERROR" | "INVALID" | "NOT_FOUND"; }[]; } | null; }, { data?: unknown; amount?: unknown; paymentGatewayId: string; checkoutId: string; }, void>;
"\n mutation InitalizeTransaction(\n $checkoutId: ID!\n $data: JSON\n $idempotencyKey: String\n $paymentGatewayId: String!\n $amount: PositiveDecimal!\n ) {\n transactionInitialize(\n id: $checkoutId\n paymentGateway: { id: $paymentGatewayId, data: $data }\n amount: $amount\n idempotencyKey: $idempotencyKey\n ) {\n transaction {\n id\n }\n data\n errors {\n field\n message\n code\n }\n }\n }\n":
TadaDocumentNode<{ transactionInitialize: { transaction: { id: string; } | null; data: unknown; errors: { field: string | null; message: string | null; code: "GRAPHQL_ERROR" | "INVALID" | "NOT_FOUND" | "UNIQUE" | "CHECKOUT_COMPLETION_IN_PROGRESS"; }[]; } | null; }, { amount: unknown; paymentGatewayId: string; idempotencyKey?: string | null | undefined; data?: unknown; checkoutId: string; }, void>;
"\n mutation transactionProcess($transactionId: ID!, $data: JSON) {\n transactionProcess(id: $transactionId, data: $data) {\n transaction {\n id\n actions\n message\n pspReference\n events {\n type\n id\n createdAt\n message\n pspReference\n }\n }\n data\n errors {\n field\n message\n code\n }\n }\n }\n":
Expand Down
31 changes: 26 additions & 5 deletions src/modules/dropin/actions/initalize-payment-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ const InitalizePaymentGatewayMutation = graphql(`
mutation initalizePaymentGateway(
$checkoutId: ID!
$paymentGatewayId: String!
$amount: PositiveDecimal!
$amount: PositiveDecimal
$data: JSON
) {
paymentGatewayInitialize(
id: $checkoutId
paymentGateways: [{ id: $paymentGatewayId }]
paymentGateways: [{ id: $paymentGatewayId, data: $data }]
amount: $amount
) {
gatewayConfigs {
Expand All @@ -40,24 +41,44 @@ const InitalizePaymentGatewayMutation = graphql(`
}
`);

type InitalizePaymentGatewayDataInput =
| {
action: "checkBalance";
paymentMethod: {
type: "giftcard";
brand: string;
encryptedCardNumber: string;
encryptedSecurityCode: string;
};
}
| { action: "createOrder" }
| {
action: "cancelOrder";
pspReference: string;
orderData: string;
};

export const initalizePaymentGateway = async (props: {
envUrl: string;
checkoutId: string;
paymentGatewayId: string;
amount: number;
amount?: number;
data?: InitalizePaymentGatewayDataInput;
}): Promise<
| { type: "error"; name: string; message: string }
| { type: "success"; value: InitalizePaymentGatewaySchemaType }
> => {
const { envUrl, checkoutId, paymentGatewayId, amount } = props;
const { envUrl, checkoutId, paymentGatewayId, amount, data } = props;
try {
const response = await request(envUrl, InitalizePaymentGatewayMutation, {
checkoutId,
paymentGatewayId,
amount,
data,
});

const parsedResponse = InitalizePaymentGatewaySchema.safeParse(response);
const parsedResponse =
InitalizePaymentGatewaySchema.passthrough().safeParse(response);

if (parsedResponse.error) {
logger.error("Failed to parse initalize payment gateway response", {
Expand Down
195 changes: 175 additions & 20 deletions src/modules/dropin/adyen/dropin-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,53 @@ import { toast } from "@/components/ui/use-toast";
import { createLogger } from "@/lib/logger";

import {
initalizePaymentGateway,
initalizeTransaction,
processTransaction,
redirectToCheckoutSummary,
} from "../actions";
import { PaymentMethodsResponseSchema } from "../schemas";
import {
AdyenGiftCardBalanceResponse,
AdyenOrderCancelledResponse,
AdyenOrderCreateResponse,
} from "./gateway-config-responses";
import { AdyenPaymentDetailResponse } from "./payment-detail-response";
import { AdyenPaymentResponse } from "./payment-response";

const logger = createLogger("AdyenDropinConfig");
const logger = createLogger("getAdyenDropinConfig");

type CoreConfiguration = Parameters<typeof AdyenCheckout>[0];

type AdyenCheckout = Awaited<ReturnType<typeof AdyenCheckout>>;

export const getAdyenDropinConfig = (props: {
clientKey: string;
paymentMethodsResponse: z.infer<typeof PaymentMethodsResponseSchema>;
totalPriceAmount: number;
totalPriceCurrency: string;
paymentMethodsResponse:
| z.infer<typeof PaymentMethodsResponseSchema>
| undefined;
totalPriceAmount: number | undefined;
totalPriceCurrency: string | undefined;
envUrl: string;
checkoutId: string;
paymentGatewayId: string;
environment: string;
checkout: AdyenCheckout;
}): CoreConfiguration => {
const {
clientKey,
paymentMethodsResponse,
totalPriceAmount,
totalPriceCurrency,
totalPriceAmount = 0,
totalPriceCurrency = "",
envUrl,
checkoutId,
paymentGatewayId,
environment,
checkout,
} = props;

const paypalPaymentMethod = paymentMethodsResponse.paymentMethods.find(
const paypalPaymentMethod = paymentMethodsResponse?.paymentMethods.find(
(method) => method.type === "paypal",
);

return {
clientKey,
locale: "en-US",
environment,
paymentMethodsResponse,
paymentMethodsConfiguration: {
paypal: paypalPaymentMethod?.configuration,
Expand Down Expand Up @@ -81,9 +87,10 @@ export const getAdyenDropinConfig = (props: {
return;
}

const adyenPaymentDetailResponse = new AdyenPaymentDetailResponse(
transactionProcessResponse.value,
);
const adyenPaymentDetailResponse =
AdyenPaymentDetailResponse.createFromTransactionProcess(
transactionProcessResponse.value,
);

if (adyenPaymentDetailResponse.isRefused()) {
toast({
Expand Down Expand Up @@ -117,18 +124,20 @@ export const getAdyenDropinConfig = (props: {
});

if (transactionInitializeResponse.type === "error") {
dropin.setStatus("error");
toast({
title: transactionInitializeResponse.name,
variant: "destructive",
description: transactionInitializeResponse.message,
});
dropin.setStatus("error");

return;
}

const adyenPaymentResponse = new AdyenPaymentResponse(
transactionInitializeResponse.value,
);
const adyenPaymentResponse =
AdyenPaymentResponse.createFromTransactionInitalize(
transactionInitializeResponse.value,
);

if (adyenPaymentResponse.isRedirectOrAdditionalActionFlow()) {
dropin.setState({
Expand All @@ -144,10 +153,156 @@ export const getAdyenDropinConfig = (props: {
return;
}

if (adyenPaymentResponse.hasOrderWithRemainingAmount()) {
// @ts-expect-error Wrong types in Adyen Web SDK - handleOrder is defined
dropin.handleOrder(adyenPaymentResponse.getPaymentResponse());

return;
}

if (adyenPaymentResponse.isSuccessful()) {
dropin.setStatus("success");
await redirectToCheckoutSummary({ paymentGatewayId });
}

if (adyenPaymentResponse.isCancelled()) {
toast({
title: "Payment cancelled",
variant: "destructive",
description: "Your payment has been cancelled.",
});
dropin.setStatus("ready");

return;
}

if (adyenPaymentResponse.isError()) {
toast({
title: "Something went wrong with payment.",
description: "There was an error while paying for your order",
variant: "destructive",
});

return;
}

if (adyenPaymentResponse.isRefused()) {
toast({
title: "Payment refused",
description: "Your payment has been refused.",
variant: "destructive",
});

return;
}

toast({
title: "Payment not handled",
description: "Your payment request couldn't be processed",
variant: "destructive",
});

logger.warn("Unhandled payment response", {
paymentResponse: adyenPaymentResponse.getPaymentResponse(),
});
},
onBalanceCheck: async (resolve, _, data) => {
// https://docs.saleor.io/developer/app-store/apps/adyen/storefront#onbalancecheck
const initalizePaymentGatewayDataResponse = await initalizePaymentGateway(
{
envUrl,
checkoutId,
paymentGatewayId,
data: { action: "checkBalance", paymentMethod: data.paymentMethod },
},
);

if (initalizePaymentGatewayDataResponse.type === "error") {
toast({
title: initalizePaymentGatewayDataResponse.name,
description: initalizePaymentGatewayDataResponse.message,
variant: "destructive",
});

return;
}

const adyenBallanceCheckResponse =
AdyenGiftCardBalanceResponse.createFromInitializePaymentGateway(
initalizePaymentGatewayDataResponse.value,
);

void resolve(adyenBallanceCheckResponse.getResponse());
},
onOrderRequest: async (resolve) => {
// https://docs.saleor.io/developer/app-store/apps/adyen/storefront#onorderrequest
const initalizePaymentGatewayDataResponse = await initalizePaymentGateway(
{
envUrl,
checkoutId,
paymentGatewayId,
data: { action: "createOrder" },
},
);

if (initalizePaymentGatewayDataResponse.type === "error") {
toast({
title: initalizePaymentGatewayDataResponse.name,
description: initalizePaymentGatewayDataResponse.message,
variant: "destructive",
});

return;
}

const adyenOrderCreateResponse =
AdyenOrderCreateResponse.createFromInitializePaymentGateway(
initalizePaymentGatewayDataResponse.value,
);

void resolve(adyenOrderCreateResponse.getResponse());
},
// @ts-expect-error - onOrderCancel is not wrongly defined in the types
onOrderCancel: async ({ order }: { order: Order }) => {
// https://docs.saleor.io/developer/app-store/apps/adyen/storefront#onordercancel
const initalizePaymentGatewayDataResponse = await initalizePaymentGateway(
{
envUrl,
checkoutId,
paymentGatewayId,
data: {
action: "cancelOrder",
pspReference: order.pspReference,
orderData: order.orderData,
},
},
);

if (initalizePaymentGatewayDataResponse.type === "error") {
toast({
title: initalizePaymentGatewayDataResponse.name,
description: initalizePaymentGatewayDataResponse.message,
variant: "destructive",
});

return;
}

const adyenOrderCancelledResponse =
AdyenOrderCancelledResponse.createFromInitializePaymentGateway(
initalizePaymentGatewayDataResponse.value,
);

if (adyenOrderCancelledResponse.isOrderNotCancelled()) {
toast({
title: "Error while cancelling order",
description: "Order couldn't be cancelled",
variant: "destructive",
});
return;
}

await checkout.update({ order: undefined });
},
};
};
Loading

0 comments on commit 0985c0d

Please sign in to comment.