Skip to content

Commit

Permalink
[SSP-2840] gopay double spend issue (#3635)
Browse files Browse the repository at this point in the history
  • Loading branch information
RostislavKreisinger authored Dec 17, 2024
2 parents 3748511 + 8c9c278 commit 0a29048
Show file tree
Hide file tree
Showing 30 changed files with 200 additions and 89 deletions.
2 changes: 2 additions & 0 deletions app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,8 @@ type Order {
email: String!
"The customer's first name"
firstName: String
"Indicates whether order payment is still being processed with GoPay payment type"
hasPaymentInProcess: Boolean!
"Determines whether the customer agrees with sending satisfaction questionnaires within the Verified by Customers Heureka program"
heurekaAgreement: Boolean!
"Indicates whether the billing address is other than a delivery address"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import sentCartImage from '/public/images/sent-cart.svg';
import { Image } from 'components/Basic/Image/Image';
import { TIDs } from 'cypress/tids';
import { ReactElement } from 'react';

type ConfirmationPageContentProps = {
heading: string;
content?: string;
AdditionalContent?: ReactElement;
};

export const ConfirmationPageContent: FC<ConfirmationPageContentProps> = ({ heading, content, AdditionalContent }) => {
export const ConfirmationPageContent: FC<ConfirmationPageContentProps> = ({ heading, content, children }) => {
return (
<div className="mb-10 mt-16 flex flex-col items-center justify-center lg:mb-24 lg:mt-16 lg:flex-row lg:items-start">
<div className="mb-0 w-40 shrink-0 lg:mr-32">
Expand All @@ -24,7 +22,7 @@ export const ConfirmationPageContent: FC<ConfirmationPageContentProps> = ({ head
dangerouslySetInnerHTML={{ __html: content }}
tid={TIDs.order_confirmation_page_text_wrapper}
/>
{AdditionalContent}
{children}
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ type OrderDetailContentProps = {
export const OrderDetailContent: FC<OrderDetailContentProps> = ({ order }) => {
return (
<div>
<OrderPaymentStatusBar orderIsPaid={order.isPaid} orderPaymentType={order.payment.type} />
{order.payment.type === PaymentTypeEnum.GoPay && !order.isPaid && (
<OrderPaymentStatusBar
orderHasPaymentInProcess={order.hasPaymentInProcess}
orderIsPaid={order.isPaid}
orderPaymentType={order.payment.type}
/>
{order.payment.type === PaymentTypeEnum.GoPay && !order.isPaid && !order.hasPaymentInProcess && (
<div>
<PaymentsInOrderSelect
orderUuid={order.uuid}
Expand Down
6 changes: 5 additions & 1 deletion storefront/components/Pages/Customer/Orders/OrderItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export const OrderItem: FC<OrderItemProps> = ({ order, addOrderItemsToEmptyCart,

return (
<div className="flex flex-col gap-5 rounded-md bg-backgroundMore p-4 vl:p-6">
<OrderPaymentStatusBar orderIsPaid={order.isPaid} orderPaymentType={order.payment.type} />
<OrderPaymentStatusBar
orderHasPaymentInProcess={order.hasPaymentInProcess}
orderIsPaid={order.isPaid}
orderPaymentType={order.payment.type}
/>
<div className="flex flex-col gap-6 vl:flex-row vl:items-start vl:justify-between">
<div className="flex flex-col gap-5">
<div className="flex flex-wrap gap-x-8 gap-y-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,52 @@ import { twMergeCustom } from 'utils/twMerge';
type OrderPaymentStatusBarProps = {
orderPaymentType: string;
orderIsPaid: boolean;
orderHasPaymentInProcess: boolean;
};

export const OrderPaymentStatusBar: FC<OrderPaymentStatusBarProps> = ({ orderPaymentType, orderIsPaid, className }) => {
const OrderPaymentStatusContent: FC<{ title: string; iconClassName?: string }> = ({ title, iconClassName }) => (
<div className="flex items-center gap-2">
<InfoIconInCircle className={twMergeCustom('size-4 text-backgroundWarningMore', iconClassName)} />
{title}
</div>
);

const OrderPaymentStatus: FC<{
orderIsPaid: boolean;
orderHasPaymentInProcess: boolean;
}> = ({ orderIsPaid, orderHasPaymentInProcess }) => {
const { t } = useTranslation();

if (orderIsPaid) {
return <OrderPaymentStatusContent iconClassName="text-backgroundSuccessMore" title={t('The order was paid')} />;
}

if (orderHasPaymentInProcess) {
return <OrderPaymentStatusContent title={t('The order is awaiting payment verification.')} />;
}

return <OrderPaymentStatusContent title={t('The order has not been paid')} />;
};

export const OrderPaymentStatusBar: FC<OrderPaymentStatusBarProps> = ({
orderPaymentType,
orderIsPaid,
className,
orderHasPaymentInProcess,
}) => {
if (orderPaymentType !== PaymentTypeEnum.GoPay) {
return null;
}

return (
<>
{orderPaymentType === PaymentTypeEnum.GoPay && (
<div
className={twMergeCustom(
'flex gap-2 rounded-md p-2',
orderIsPaid ? 'bg-backgroundSuccess text-textInverted' : 'bg-backgroundWarning',
className,
)}
>
{orderIsPaid ? (
<>
<InfoIconInCircle className="w-4 text-backgroundSuccessMore" />
{t('The order was paid')}
</>
) : (
<>
<InfoIconInCircle className="w-4 text-backgroundWarningMore" />
{t('The order has not been paid')}
</>
)}
</div>
<div
className={twMergeCustom(
'flex gap-2 rounded-md p-2',
orderIsPaid ? 'bg-backgroundSuccess text-textInverted' : 'bg-backgroundWarning',
className,
)}
</>
>
<OrderPaymentStatus orderHasPaymentInProcess={orderHasPaymentInProcess} orderIsPaid={orderIsPaid} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,16 @@ export const PaymentFail: FC<PaymentFailProps> = ({
useGtmPageViewEvent(gtmStaticPageViewEvent);

return (
<ConfirmationPageContent
content={orderPaymentFailedContent}
heading={t('Your payment was not successful')}
AdditionalContent={
<>
{lastUsedOrderPaymentType === PaymentTypeEnum.GoPay && (
<PaymentsInOrderSelect
className="mt-6"
orderUuid={orderUuid}
paymentTransactionCount={paymentTransactionCount}
/>
)}
</>
}
/>
<ConfirmationPageContent content={orderPaymentFailedContent} heading={t('Your payment was not successful')}>
<>
{lastUsedOrderPaymentType === PaymentTypeEnum.GoPay && (
<PaymentsInOrderSelect
className="mt-6"
orderUuid={orderUuid}
paymentTransactionCount={paymentTransactionCount}
/>
)}
</>
</ConfirmationPageContent>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ExtendedNextLink } from 'components/Basic/ExtendedNextLink/ExtendedNextLink';
import { ConfirmationPageContent } from 'components/Blocks/ConfirmationPage/ConfirmationPageContent';
import { useDomainConfig } from 'components/providers/DomainConfigProvider';
import { GtmPageType } from 'gtm/enums/GtmPageType';
import { useGtmStaticPageViewEvent } from 'gtm/factories/useGtmStaticPageViewEvent';
import { useGtmPageViewEvent } from 'gtm/utils/pageViewEvents/useGtmPageViewEvent';
import useTranslation from 'next-translate/useTranslation';
import { getInternationalizedStaticUrls } from 'utils/staticUrls/getInternationalizedStaticUrls';

type PaymentInProcessProps = {
orderUrlHash: string;
};

export const PaymentInProcess: FC<PaymentInProcessProps> = ({ orderUrlHash }) => {
const { t } = useTranslation();
const { url } = useDomainConfig();
const [orderDetailUrl] = getInternationalizedStaticUrls(
[{ url: '/order-detail/:urlHash', param: orderUrlHash }],
url,
);
const gtmStaticPageViewEvent = useGtmStaticPageViewEvent(GtmPageType.payment_in_process);
useGtmPageViewEvent(gtmStaticPageViewEvent);

return (
<ConfirmationPageContent
content={t('You can check the status on the order detail page.')}
heading={t('The payment is being processed')}
>
<ExtendedNextLink href={orderDetailUrl} type="orderDetail">
{t('Show order detail')}
</ExtendedNextLink>
</ConfirmationPageContent>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PaymentFail } from './PaymentFail';
import { PaymentInProcess } from './PaymentInProcess';
import { PaymentSuccess } from './PaymentSuccess';
import { TypeUpdatePaymentStatusMutation } from 'graphql/requests/orders/mutations/UpdatePaymentStatusMutation.generated';
import { TypeOrderPaymentFailedContentQuery } from 'graphql/requests/orders/queries/OrderPaymentFailedContentQuery.generated';
import { TypeOrderPaymentSuccessfulContentQuery } from 'graphql/requests/orders/queries/OrderPaymentSuccessfulContentQuery.generated';

export const PaymentStatus: FC<{
paymentStatusData: TypeUpdatePaymentStatusMutation | undefined;
failedContentData: TypeOrderPaymentFailedContentQuery | undefined;
successContentData: TypeOrderPaymentSuccessfulContentQuery | undefined;
orderUuid: string;
}> = ({ paymentStatusData, failedContentData, successContentData, orderUuid }) => {
if (paymentStatusData?.UpdatePaymentStatus.isPaid) {
return successContentData ? (
<PaymentSuccess
orderPaymentSuccessfulContent={successContentData.orderPaymentSuccessfulContent}
orderUuid={orderUuid}
/>
) : null;
}

if (paymentStatusData?.UpdatePaymentStatus.hasPaymentInProcess) {
return <PaymentInProcess orderUrlHash={paymentStatusData.UpdatePaymentStatus.urlHash} />;
}

if (paymentStatusData && failedContentData) {
return (
<PaymentFail
lastUsedOrderPaymentType={paymentStatusData.UpdatePaymentStatus.payment.type}
orderPaymentFailedContent={failedContentData.orderPaymentFailedContent}
orderUuid={orderUuid}
paymentTransactionCount={paymentStatusData.UpdatePaymentStatus.paymentTransactionsCount}
/>
);
}

return null;
};
9 changes: 9 additions & 0 deletions storefront/graphql/docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -5959,6 +5959,15 @@ The customer's email address

The customer's first name

</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>hasPaymentInProcess</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>

Indicates whether order payment is still being processed with GoPay payment type

</td>
</tr>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import gql from 'graphql-tag';
import { CountryFragment } from '../../countries/fragments/CountryFragment.generated';
import { ComplaintItemFragment } from './ComplaintItemFragment.generated';
import { OrderDetailFragment } from '../../orders/fragments/OrderDetailFragment.generated';
export type TypeComplaintDetailFragment = { __typename?: 'Complaint', uuid: string, number: string, createdAt: any, deliveryFirstName: string, deliveryLastName: string, deliveryCompanyName: string | null, deliveryTelephone: string, deliveryStreet: string, deliveryCity: string, deliveryPostcode: string, status: string, deliveryCountry: { __typename: 'Country', name: string, code: string }, items: Array<{ __typename?: 'ComplaintItem', quantity: number, description: string, orderItem: { __typename: 'OrderItem', uuid: string, name: string, vatRate: string, quantity: number, unit: string | null, type: Types.TypeOrderItemTypeEnum, unitPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, order: { __typename?: 'Order', uuid: string, number: string, creationDate: any }, product: { __typename?: 'MainVariant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'RegularProduct', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'Variant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | null } | null, files: Array<{ __typename: 'File', anchorText: string, url: string }> | null }>, order: { __typename: 'Order', uuid: string, number: string, creationDate: any, status: string, firstName: string | null, lastName: string | null, email: string, telephone: string, companyName: string | null, companyNumber: string | null, companyTaxNumber: string | null, street: string, city: string, postcode: string, isDeliveryAddressDifferentFromBilling: boolean, deliveryFirstName: string | null, deliveryLastName: string | null, deliveryCompanyName: string | null, deliveryTelephone: string | null, deliveryStreet: string | null, deliveryCity: string | null, deliveryPostcode: string | null, note: string | null, urlHash: string, promoCode: string | null, trackingNumber: string | null, trackingUrl: string | null, paymentTransactionsCount: number, isPaid: boolean, items: Array<{ __typename: 'OrderItem', uuid: string, name: string, vatRate: string, quantity: number, unit: string | null, type: Types.TypeOrderItemTypeEnum, unitPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, order: { __typename?: 'Order', uuid: string, number: string, creationDate: any }, product: { __typename?: 'MainVariant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'RegularProduct', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'Variant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | null }>, transport: { __typename: 'Transport', name: string, isPersonalPickup: boolean, transportTypeCode: Types.TypeTransportTypeEnum, price: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, mainImage: { __typename?: 'Image', url: string } | null }, payment: { __typename: 'Payment', name: string, type: string, price: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, mainImage: { __typename?: 'Image', url: string } | null }, country: { __typename: 'Country', name: string }, deliveryCountry: { __typename: 'Country', name: string } | null, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string } } };
export type TypeComplaintDetailFragment = { __typename?: 'Complaint', uuid: string, number: string, createdAt: any, deliveryFirstName: string, deliveryLastName: string, deliveryCompanyName: string | null, deliveryTelephone: string, deliveryStreet: string, deliveryCity: string, deliveryPostcode: string, status: string, deliveryCountry: { __typename: 'Country', name: string, code: string }, items: Array<{ __typename?: 'ComplaintItem', quantity: number, description: string, orderItem: { __typename: 'OrderItem', uuid: string, name: string, vatRate: string, quantity: number, unit: string | null, type: Types.TypeOrderItemTypeEnum, unitPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, order: { __typename?: 'Order', uuid: string, number: string, creationDate: any }, product: { __typename?: 'MainVariant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'RegularProduct', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'Variant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | null } | null, files: Array<{ __typename: 'File', anchorText: string, url: string }> | null }>, order: { __typename: 'Order', uuid: string, number: string, creationDate: any, status: string, firstName: string | null, lastName: string | null, email: string, telephone: string, companyName: string | null, companyNumber: string | null, companyTaxNumber: string | null, street: string, city: string, postcode: string, isDeliveryAddressDifferentFromBilling: boolean, deliveryFirstName: string | null, deliveryLastName: string | null, deliveryCompanyName: string | null, deliveryTelephone: string | null, deliveryStreet: string | null, deliveryCity: string | null, deliveryPostcode: string | null, note: string | null, urlHash: string, promoCode: string | null, trackingNumber: string | null, trackingUrl: string | null, paymentTransactionsCount: number, isPaid: boolean, hasPaymentInProcess: boolean, items: Array<{ __typename: 'OrderItem', uuid: string, name: string, vatRate: string, quantity: number, unit: string | null, type: Types.TypeOrderItemTypeEnum, unitPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, order: { __typename?: 'Order', uuid: string, number: string, creationDate: any }, product: { __typename?: 'MainVariant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'RegularProduct', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | { __typename?: 'Variant', slug: string, isVisible: boolean, isSellingDenied: boolean, isInquiryType: boolean, mainImage: { __typename: 'Image', name: string | null, url: string } | null } | null }>, transport: { __typename: 'Transport', name: string, isPersonalPickup: boolean, transportTypeCode: Types.TypeTransportTypeEnum, price: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, mainImage: { __typename?: 'Image', url: string } | null }, payment: { __typename: 'Payment', name: string, type: string, price: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string }, mainImage: { __typename?: 'Image', url: string } | null }, country: { __typename: 'Country', name: string }, deliveryCountry: { __typename: 'Country', name: string } | null, totalPrice: { __typename: 'Price', priceWithVat: string, priceWithoutVat: string, vatAmount: string } } };


export interface PossibleTypesResultData {
Expand Down
Loading

0 comments on commit 0a29048

Please sign in to comment.