From 3b2960003011d3b6d7acdac554a1d5e10c76df7a Mon Sep 17 00:00:00 2001 From: blushi Date: Tue, 10 Dec 2024 11:37:51 +0100 Subject: [PATCH] feat: add stripe receipt --- .../organisms/Order/Order.stories.tsx | 1 + .../components/organisms/Order/Order.test.tsx | 1 + .../src/components/organisms/Order/Order.tsx | 26 ++++++++++----- .../organisms/Order/Order.types.tsx | 1 + web-marketplace/src/lib/i18n/locales/en.po | 8 +++-- web-marketplace/src/lib/i18n/locales/es.po | 11 ++++--- .../getPaymentIntentQuery.constants.ts} | 0 .../getPaymentIntentQuery.ts | 29 ++++++++++++++++ .../getPaymentIntentQuery.types.ts | 13 ++++++++ .../getPaymentMethodForPaymentIntentQuery.ts | 29 ---------------- ...aymentMethodForPaymentIntentQuery.types.ts | 13 -------- .../src/pages/Orders/Orders.types.ts | 12 +++++++ .../src/pages/Orders/hooks/useOrders.tsx | 33 +++++++++---------- 13 files changed, 103 insertions(+), 74 deletions(-) rename web-marketplace/src/lib/queries/react-query/registry-server/{getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.constants.ts => getPaymentIntentQuery/getPaymentIntentQuery.constants.ts} (100%) create mode 100644 web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.ts create mode 100644 web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.types.ts delete mode 100644 web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.ts delete mode 100644 web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.types.ts create mode 100644 web-marketplace/src/pages/Orders/Orders.types.ts diff --git a/web-marketplace/src/components/organisms/Order/Order.stories.tsx b/web-marketplace/src/components/organisms/Order/Order.stories.tsx index a394941c85..20dcc98e07 100644 --- a/web-marketplace/src/components/organisms/Order/Order.stories.tsx +++ b/web-marketplace/src/components/organisms/Order/Order.stories.tsx @@ -30,6 +30,7 @@ const orderData = { blockchainDetails, credits, paymentInfo, + receiptUrl: 'http://lorem.ipsum', }, }; diff --git a/web-marketplace/src/components/organisms/Order/Order.test.tsx b/web-marketplace/src/components/organisms/Order/Order.test.tsx index bf45d38da7..809e74d62c 100644 --- a/web-marketplace/src/components/organisms/Order/Order.test.tsx +++ b/web-marketplace/src/components/organisms/Order/Order.test.tsx @@ -36,6 +36,7 @@ describe('Order Component', () => { credits, paymentInfo, status: ORDER_STATUS.pending, + receiptUrl: 'http://lorem.ipsum', }, }; diff --git a/web-marketplace/src/components/organisms/Order/Order.tsx b/web-marketplace/src/components/organisms/Order/Order.tsx index 0b22f8610b..f8c45986ca 100644 --- a/web-marketplace/src/components/organisms/Order/Order.tsx +++ b/web-marketplace/src/components/organisms/Order/Order.tsx @@ -5,7 +5,7 @@ import { CardContent, CardHeader, useTheme } from '@mui/material'; import OutlinedButton from 'web-components/src/components/buttons/OutlinedButton'; import Card from 'web-components/src/components/cards/Card'; import CertifiedDocumentIcon from 'web-components/src/components/icons/CertifiedDocumentIcon'; -// import ReceiptIcon from 'web-components/src/components/icons/ReceiptIcon'; +import ReceiptIcon from 'web-components/src/components/icons/ReceiptIcon'; import { Image } from 'web-components/src/components/image'; import ProjectPlaceInfo from 'web-components/src/components/place/ProjectPlaceInfo'; import { PrefinanceTag } from 'web-components/src/components/PrefinanceTag/PrefinanceTag'; @@ -28,8 +28,14 @@ export const Order = ({ orderData, allowedDenoms, className }: OrderProps) => { const { _ } = useLingui(); const { project } = orderData; - const { retirementInfo, blockchainDetails, credits, paymentInfo, status } = - orderData.order; + const { + retirementInfo, + blockchainDetails, + credits, + paymentInfo, + status, + receiptUrl, + } = orderData.order; const isPrefinanceProject = project.projectPrefinancing?.isPrefinanceProject; const projectHref = `/project/${project.slug ?? project.id}`; @@ -70,16 +76,20 @@ export const Order = ({ orderData, allowedDenoms, className }: OrderProps) => {
{retirementInfo.retiredCredits && retirementInfo.certificateNodeId && ( - + certificate )} - {/* TODO - implement View receipt */} - {/* - View Receipt - */} + {receiptUrl && ( + + + + View Receipt + + + )}
} title={ diff --git a/web-marketplace/src/components/organisms/Order/Order.types.tsx b/web-marketplace/src/components/organisms/Order/Order.types.tsx index 8d5a1ade72..2e1364a116 100644 --- a/web-marketplace/src/components/organisms/Order/Order.types.tsx +++ b/web-marketplace/src/components/organisms/Order/Order.types.tsx @@ -20,6 +20,7 @@ export interface OrderData { blockchainDetails: BlockchainDetailsData; credits: CreditsData; paymentInfo: PaymentInfoData; + receiptUrl?: string; }; } diff --git a/web-marketplace/src/lib/i18n/locales/en.po b/web-marketplace/src/lib/i18n/locales/en.po index 490ca8938b..4f7fca303f 100644 --- a/web-marketplace/src/lib/i18n/locales/en.po +++ b/web-marketplace/src/lib/i18n/locales/en.po @@ -920,7 +920,7 @@ msgstr "" msgid "Categories of the IUCN Red List of Ecosystems assess ecosystem health and risk at a global scale. Tebu promotes protecting the most threatened ecosystems by giving a higher score to those at higher risk." msgstr "" -#: src/components/organisms/Order/Order.tsx:75 +#: src/components/organisms/Order/Order.tsx:81 msgid "certificate" msgstr "" @@ -1872,7 +1872,7 @@ msgstr "" msgid "Examples of a retirement reason include: “company travel 2025”, “offsetting my personal footprint”, or the name of a specific person or organization." msgstr "" -#: src/components/organisms/Order/Order.tsx:115 +#: src/components/organisms/Order/Order.tsx:125 msgid "Expected delivery date" msgstr "" @@ -4593,6 +4593,10 @@ msgstr "" msgid "view project" msgstr "" +#: src/components/organisms/Order/Order.tsx:89 +msgid "View Receipt" +msgstr "" + #: src/components/atoms/WrappedResourcesCard.tsx:33 msgid "view resource" msgstr "" diff --git a/web-marketplace/src/lib/i18n/locales/es.po b/web-marketplace/src/lib/i18n/locales/es.po index b0c9d2d2ed..9e6ef9ef60 100644 --- a/web-marketplace/src/lib/i18n/locales/es.po +++ b/web-marketplace/src/lib/i18n/locales/es.po @@ -22,9 +22,6 @@ msgstr "" #: src/components/organisms/CreditActivityTable/CreditActivityTable.tsx:65 #: src/components/organisms/Documentation/Documentation.constants.ts:11 #: src/pages/CreditClassDetails/CreditClassDetailsWithContent/CreditClassDetailsWithContent.constants.ts:35 -msgid "" -msgstr "" - #: src/lib/constants/shared.constants.tsx:96 msgid "- see less" msgstr "- ver menos" @@ -926,7 +923,7 @@ msgstr "Caribe" msgid "Categories of the IUCN Red List of Ecosystems assess ecosystem health and risk at a global scale. Tebu promotes protecting the most threatened ecosystems by giving a higher score to those at higher risk." msgstr "Las categorías de la Lista Roja de Ecosistemas de la UICN evalúan la salud y el riesgo de los ecosistemas a escala global. Tebu promueve la protección de los ecosistemas más amenazados otorgando una puntuación más alta a aquellos que presentan un riesgo mayor." -#: src/components/organisms/Order/Order.tsx:75 +#: src/components/organisms/Order/Order.tsx:81 msgid "certificate" msgstr "certificado" @@ -1890,7 +1887,7 @@ msgstr "emisión estimada" msgid "Examples of a retirement reason include: “company travel 2025”, “offsetting my personal footprint”, or the name of a specific person or organization." msgstr "Algunos ejemplos de motivos de jubilación incluyen: “viajes de empresa en 2025”, “compensar mi huella personal” o el nombre de una persona u organización específica." -#: src/components/organisms/Order/Order.tsx:115 +#: src/components/organisms/Order/Order.tsx:125 msgid "Expected delivery date" msgstr "Fecha de entrega prevista" @@ -4650,6 +4647,10 @@ msgstr "Ver perfil" msgid "view project" msgstr "ver proyecto" +#: src/components/organisms/Order/Order.tsx:89 +msgid "View Receipt" +msgstr "Ver recibo" + #: src/components/atoms/WrappedResourcesCard.tsx:33 msgid "view resource" msgstr "ver recurso" diff --git a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.constants.ts b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.constants.ts similarity index 100% rename from web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.constants.ts rename to web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.constants.ts diff --git a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.ts b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.ts new file mode 100644 index 0000000000..4dfdd47f2f --- /dev/null +++ b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.ts @@ -0,0 +1,29 @@ +import { apiUri } from 'lib/apiUri'; + +import { GET_PAYMENT_METHOD_QUERY_KEY } from './getPaymentIntentQuery.constants'; +import { + ReactQueryGetPaymentIntentQueryParams, + ReactQueryGetPaymentIntentQueryResponse, +} from './getPaymentIntentQuery.types'; + +export const getPaymentIntentQuery = ({ + paymentMethodId, + ...params +}: ReactQueryGetPaymentIntentQueryParams): ReactQueryGetPaymentIntentQueryResponse => ({ + queryKey: [GET_PAYMENT_METHOD_QUERY_KEY], + queryFn: async () => { + try { + const resp = await fetch( + `${apiUri}/marketplace/v1/stripe/payment-intents/${paymentMethodId}`, + { + method: 'GET', + credentials: 'include', + }, + ); + return await resp.json(); + } catch (e) { + return null; + } + }, + ...params, +}); diff --git a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.types.ts b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.types.ts new file mode 100644 index 0000000000..f1d6fc90aa --- /dev/null +++ b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery.types.ts @@ -0,0 +1,13 @@ +import { PaymentMethod } from '@stripe/stripe-js'; +import { QueryObserverOptions } from '@tanstack/react-query'; + +import { ReactQueryBuilderResponse } from '../../types/react-query.types'; + +export type ReactQueryGetPaymentIntentQueryResponse = QueryObserverOptions<{ + paymentMethod?: PaymentMethod | null; + receiptUrl?: string | null; +}>; + +export type ReactQueryGetPaymentIntentQueryParams = { + paymentMethodId: string; +} & ReactQueryBuilderResponse; diff --git a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.ts b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.ts deleted file mode 100644 index 4e2a0c8a94..0000000000 --- a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { apiUri } from 'lib/apiUri'; - -import { GET_PAYMENT_METHOD_QUERY_KEY } from './getPaymentMethodForPaymentIntentQuery.constants'; -import { - ReactQueryGetPaymentMethodForPaymentIntentQueryParams, - ReactQueryGetPaymentMethodForPaymentIntentQueryResponse, -} from './getPaymentMethodForPaymentIntentQuery.types'; - -export const getPaymentMethodForPaymentIntentQuery = ({ - paymentMethodId, - ...params -}: ReactQueryGetPaymentMethodForPaymentIntentQueryParams): ReactQueryGetPaymentMethodForPaymentIntentQueryResponse => ({ - queryKey: [GET_PAYMENT_METHOD_QUERY_KEY], - queryFn: async () => { - try { - const resp = await fetch( - `${apiUri}/marketplace/v1/stripe/payment-intents/${paymentMethodId}/payment-method`, - { - method: 'GET', - credentials: 'include', - }, - ); - return await resp.json(); - } catch (e) { - return null; - } - }, - ...params, -}); diff --git a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.types.ts b/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.types.ts deleted file mode 100644 index c2ccab1803..0000000000 --- a/web-marketplace/src/lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery.types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PaymentMethod } from '@stripe/stripe-js'; -import { QueryObserverOptions } from '@tanstack/react-query'; - -import { ReactQueryBuilderResponse } from '../../types/react-query.types'; - -export type ReactQueryGetPaymentMethodForPaymentIntentQueryResponse = - QueryObserverOptions<{ - paymentMethod?: PaymentMethod | null; - }>; - -export type ReactQueryGetPaymentMethodForPaymentIntentQueryParams = { - paymentMethodId: string; -} & ReactQueryBuilderResponse; diff --git a/web-marketplace/src/pages/Orders/Orders.types.ts b/web-marketplace/src/pages/Orders/Orders.types.ts new file mode 100644 index 0000000000..946a11aa31 --- /dev/null +++ b/web-marketplace/src/pages/Orders/Orders.types.ts @@ -0,0 +1,12 @@ +export type Order = { + txHash: string; + projectId: string; + askDenom: string; + timestamp: string; + creditsAmount: string | number; + retiredCredits: boolean; + totalPrice: string | number; + cardLast4?: string; + cardBrand?: string; + receiptUrl?: string; +}; diff --git a/web-marketplace/src/pages/Orders/hooks/useOrders.tsx b/web-marketplace/src/pages/Orders/hooks/useOrders.tsx index e826f1b4e1..8243ee82e9 100644 --- a/web-marketplace/src/pages/Orders/hooks/useOrders.tsx +++ b/web-marketplace/src/pages/Orders/hooks/useOrders.tsx @@ -18,7 +18,7 @@ import { normalizeProjectsWithOrderData } from 'lib/normalizers/projects/normali import { getProjectQuery } from 'lib/queries/react-query/ecocredit/getProjectQuery/getProjectQuery'; import { getDenomTraceByHashesQuery } from 'lib/queries/react-query/ibc/transfer/getDenomTraceByHashesQuery/getDenomTraceByHashesQuery'; import { getMetadataQuery } from 'lib/queries/react-query/registry-server/getMetadataQuery/getMetadataQuery'; -import { getPaymentMethodForPaymentIntentQuery } from 'lib/queries/react-query/registry-server/getPaymentMethodForPaymentIntentQuery/getPaymentMethodForPaymentIntentQuery'; +import { getPaymentIntentQuery } from 'lib/queries/react-query/registry-server/getPaymentIntentQuery/getPaymentIntentQuery'; import { getProjectByOnChainIdQuery } from 'lib/queries/react-query/registry-server/graphql/getProjectByOnChainIdQuery/getProjectByOnChainIdQuery'; import { getOrdersByBuyerAddressQuery } from 'lib/queries/react-query/registry-server/graphql/indexer/getOrdersByBuyerAddress/getOrdersByBuyerAddress'; import { getRetirementByTxHash } from 'lib/queries/react-query/registry-server/graphql/indexer/getRetirementByTxHash/getRetirementByTxHash'; @@ -27,6 +27,8 @@ import { useWallet } from 'lib/wallet/wallet'; import { ORDER_STATUS } from 'components/organisms/Order/Order.constants'; import { IBC_DENOM_PREFIX } from 'hooks/useQuerySellOrders'; +import { Order } from '../Orders.types'; + export const useOrders = () => { const apolloClient = useApolloClient() as ApolloClient; const { ecocreditClient, dataClient } = useLedger(); @@ -47,25 +49,31 @@ export const useOrders = () => { const paymentMethodResults = useQueries({ queries: fiatOrders?.map(order => - getPaymentMethodForPaymentIntentQuery({ + getPaymentIntentQuery({ enabled: !!order?.stripePaymentIntentId, paymentMethodId: order?.stripePaymentIntentId as string, }), ) || [], }); + const paymentMethods = paymentMethodResults.map( queryResult => queryResult.data?.paymentMethod, ); + const receiptUrls = paymentMethodResults.map( + queryResult => queryResult.data?.receiptUrl, + ); + const paymentMethodsLoading = paymentMethodResults.some(res => res.isLoading); - const fiatOrdersWithPaymentMethods = useMemo( + const fiatOrdersWithStripeInfo = useMemo( () => fiatOrders?.map((order, index) => ({ ...order, cardLast4: paymentMethods?.[index]?.card?.last4, cardBrand: paymentMethods?.[index]?.card?.brand, + receiptUrl: receiptUrls?.[index], })), - [fiatOrders, paymentMethods], + [fiatOrders, paymentMethods, receiptUrls], ); const { data, isLoading: cryptoOrdersLoading } = useQuery( @@ -95,23 +103,13 @@ export const useOrders = () => { const sortedOrders = useMemo( () => - [...(fiatOrdersWithPaymentMethods || []), ...(cryptoOrders || [])].sort( + [...(fiatOrdersWithStripeInfo || []), ...(cryptoOrders || [])].sort( (a, b) => new Date(b?.timestamp).getTime() - new Date(a?.timestamp as string).getTime(), ), - [cryptoOrders, fiatOrdersWithPaymentMethods], - ) as Array<{ - cardLast4?: string; - cardBrand: string; - txHash: string; - projectId: string; - askDenom: string; - timestamp: string; - creditsAmount: string | number; - retiredCredits: boolean; - totalPrice: string | number; - }>; + [cryptoOrders, fiatOrdersWithStripeInfo], + ) as Array; const retirementResults = useQueries({ queries: sortedOrders.map(order => @@ -246,6 +244,7 @@ export const useOrders = () => { askDenom: order?.askDenom || '', askBaseDenom, }, + receiptUrl: order?.receiptUrl, }, }; }),