diff --git a/examples/react/src/components/Connected.tsx b/examples/react/src/components/Connected.tsx index 89f08b1b2..241a8de46 100644 --- a/examples/react/src/components/Connected.tsx +++ b/examples/react/src/components/Connected.tsx @@ -42,7 +42,10 @@ const searchParams = new URLSearchParams(location.search) const isDebugMode = searchParams.has('debug') const checkoutProvider = searchParams.get('checkoutProvider') const onRampProvider = searchParams.get('onRampProvider') -const checkoutPreset = searchParams.get('checkoutPreset') || 'forte-payment-erc1155-sale-native-token-testnet' +const checkoutPreset = searchParams.get('checkoutPreset') || 'forte-transak-payment-erc1155-sale-native-token-testnet' + +// @ts-ignore +const isDev = __SEQUENCE_WEB_SDK_IS_DEV__ export const Connected = () => { const { openTransactionStatusModal } = useTransactionStatusModal() @@ -421,9 +424,6 @@ export const Connected = () => { recipientAddress: address, creditCardProviders: [creditCardProvider], onRampProvider: onRampProvider ? (onRampProvider as TransactionOnRampProvider) : TransactionOnRampProvider.transak, - transakConfig: { - contractId: '674eb5613d739107bbd18ed2' - }, onSuccess: (txnHash?: string) => { console.log('success!', txnHash) }, diff --git a/examples/react/src/components/CustomCheckout/index.tsx b/examples/react/src/components/CustomCheckout/index.tsx index 44eb6e32d..1f058cee2 100644 --- a/examples/react/src/components/CustomCheckout/index.tsx +++ b/examples/react/src/components/CustomCheckout/index.tsx @@ -55,10 +55,6 @@ export const CustomCheckout = () => { currencyAddress, collectionAddress, creditCardProvider: 'transak' as CreditCardProviders, - transakConfig: { - contractId, - apiKey: '5911d9ec-46b5-48fa-a755-d59a715ff0cf' - }, onSuccess: (txnHash?: string) => { console.log('success!', txnHash) }, diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index 2f284acae..6ea37e618 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -183,8 +183,6 @@ export const getErc1155SaleContractConfig = (walletAddress: string) => ({ export const checkoutConfig: SequenceCheckoutConfig = { env: isDev ? { - transakApiUrl: 'https://global-stg.transak.com', - transakApiKey: 'c20f2a0e-fe6a-4133-8fa7-77e9f84edf98', forteWidgetUrl: 'https://payments.sandbox.lemmax.com/forte-payments-widget.js' } : undefined diff --git a/examples/react/src/utils/checkout.ts b/examples/react/src/utils/checkout.ts index 84170727a..e891c00b9 100644 --- a/examples/react/src/utils/checkout.ts +++ b/examples/react/src/utils/checkout.ts @@ -178,7 +178,7 @@ export const checkoutPresets: Record Check } } }, - 'forte-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => { + 'forte-transak-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => { const collectibles = [ { tokenId: '1', diff --git a/packages/checkout/README.md b/packages/checkout/README.md index 6bea538bf..ccd6e351d 100644 --- a/packages/checkout/README.md +++ b/packages/checkout/README.md @@ -274,10 +274,6 @@ const CustomCheckoutUI = () => { currencyAddress, collectionAddress, creditCardProvider: 'transak' as CreditCardProviders, - transakConfig: { - contractId, - apiKey: '5911d9ec-46b5-48fa-a755-d59a715ff0cf' - }, onSuccess: (txnHash?: string) => { console.log('success!', txnHash) }, diff --git a/packages/checkout/src/api/data.ts b/packages/checkout/src/api/data.ts index 3cd9ce5cd..89c6c1e95 100644 --- a/packages/checkout/src/api/data.ts +++ b/packages/checkout/src/api/data.ts @@ -269,3 +269,64 @@ export const fetchFortePaymentStatus = async ( status: (statuses[0]?.status as FortePaymentStatus) || '' } } + +export interface TransakNFTData { + imageURL: string + nftName: string + collectionAddress: string + tokenIDs: string[] + prices: number[] + quantity: number + nftType: string +} + +export interface TransakWidgetUrlArgs { + isNFT?: boolean + calldata?: string + targetContractAddress?: string + cryptoCurrencyCode?: string + estimatedGasLimit?: number + nftData?: TransakNFTData[] + walletAddress: string + disableWalletAddressForm?: boolean + partnerOrderId?: string + network?: string + referrerDomain: string + fiatAmount?: number + fiatCurrency?: string + defaultFiatAmount?: number + defaultCryptoCurrency?: string + cryptoCurrencyList?: string + networks?: string +} + +export const getTransakWidgetUrl = async ( + sequenceApiUrl: string, + projectAccessKey: string, + args: TransakWidgetUrlArgs +): Promise<{ url: string }> => { + const queryUrl = `${sequenceApiUrl}/rpc/API/TransakGetWidgetURL` + + const res = await fetch(queryUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Access-Key': projectAccessKey + }, + body: JSON.stringify({ + params: { + ...args + } + }) + }) + + if (!res.ok) { + throw new Error(`Transak API error: ${res.status} ${res.statusText}`) + } + + const { url } = await res.json() + + return { + url + } +} diff --git a/packages/checkout/src/components/SequenceCheckoutProvider/SequenceCheckoutProvider.tsx b/packages/checkout/src/components/SequenceCheckoutProvider/SequenceCheckoutProvider.tsx index e8290e5e8..fad8f80f8 100644 --- a/packages/checkout/src/components/SequenceCheckoutProvider/SequenceCheckoutProvider.tsx +++ b/packages/checkout/src/components/SequenceCheckoutProvider/SequenceCheckoutProvider.tsx @@ -246,8 +246,6 @@ export const SequenceCheckoutProvider = ({ children, config }: SequenceCheckoutP diff --git a/packages/checkout/src/contexts/CheckoutModal.ts b/packages/checkout/src/contexts/CheckoutModal.ts index a210fbda4..1b6977a88 100644 --- a/packages/checkout/src/contexts/CheckoutModal.ts +++ b/packages/checkout/src/contexts/CheckoutModal.ts @@ -19,8 +19,6 @@ interface OrderSummaryItem { } export interface TransakConfig { - apiKey?: string - contractId: string callDataOverride?: string } diff --git a/packages/checkout/src/contexts/Environment.ts b/packages/checkout/src/contexts/Environment.ts index 2b5d3a205..f3cb248bc 100644 --- a/packages/checkout/src/contexts/Environment.ts +++ b/packages/checkout/src/contexts/Environment.ts @@ -4,8 +4,6 @@ import { createGenericContext } from './genericContext.js' export interface EnvironmentOverrides { marketplaceApiUrl: string - transakApiUrl: string - transakApiKey: string forteWidgetUrl: string } diff --git a/packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx b/packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx index d43180dbe..3b3152af2 100644 --- a/packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx +++ b/packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx @@ -3,15 +3,14 @@ import { compareAddress } from '@0xsequence/connect' import { useConfig } from '@0xsequence/hooks' import type { ContractInfo, TokenMetadata } from '@0xsequence/metadata' import { findSupportedNetwork } from '@0xsequence/network' -import pako from 'pako' -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useMemo, useRef } from 'react' import { formatUnits, zeroAddress, type Hex } from 'viem' import type { TransakConfig } from '../../contexts/CheckoutModal.js' -import { useEnvironmentContext } from '../../contexts/Environment.js' import type { Collectible, CreditCardProviders } from '../../contexts/SelectPaymentModal.js' -import { TRANSAK_PROXY_ADDRESS } from '../../utils/transak.js' +import { TRANSAK_PROXY_ADDRESS, getCurrencyCode } from '../../utils/transak.js' +import { useTransakWidgetUrl } from '../useTransakWidgetUrl.js' const POLLING_TIME = 10 * 1000 const TRANSAK_IFRAME_ID = 'credit-card-payment-transak-iframe' @@ -75,7 +74,12 @@ export const useCreditCardPayment = ({ isLoadingCurrencyInfo, errorCurrencyInfo }: UseCreditCardPaymentArgs): UseCreditCardPaymentReturn => { - const { transakApiUrl, transakApiKey: transakGlobalApiKey } = useEnvironmentContext() + const projectAccessKey = useProjectAccessKey() + const { env } = useConfig() + + const disableTransakWidgetUrlFetch = + isLoadingTokenMetadatas || isLoadingCurrencyInfo || isLoadingCollectionInfo || creditCardProvider !== 'transak' + const network = findSupportedNetwork(chain) const error = errorCollectionInfo || errorTokenMetadata || errorCurrencyInfo const isLoading = isLoadingCollectionInfo || isLoadingTokenMetadatas || isLoadingCurrencyInfo @@ -85,11 +89,66 @@ export const useCreditCardPayment = ({ const iframeRef = useRef(null) const tokenMetadata = tokenMetadatas?.[0] + // Transak requires the recipient address to be the proxy address + // so we need to replace the recipient address with the proxy address in the calldata + // this is a weird hack so that credit card integrations are as simple as possible and should work 99% of the time + // If an issue arises, the user can override the calldata in the transak settings + + const calldataWithProxy = + transakConfig?.callDataOverride ?? + txData.replace(recipientAddress.toLowerCase().substring(2), TRANSAK_PROXY_ADDRESS.toLowerCase().substring(2)) + + const price = Number(formatUnits(BigInt(totalPriceRaw), Number(currencyDecimals || 18))) + + const transakNftData = [ + { + imageURL: tokenMetadata?.image || '', + nftName: tokenMetadata?.name || 'collectible', + collectionAddress: collectionAddress, + tokenIDs: [collectible.tokenId || ''], + prices: [price], + quantity: Number(collectible.quantity), + nftType: dataCollectionInfo?.type || 'ERC721' + } + ] + + const estimatedGasLimit = 500000 + + const partnerOrderId = useMemo(() => { + return `${recipientAddress}-${new Date().getTime()}` + }, [recipientAddress]) + + // Note: the network name might not always line up with Transak. A conversion function might be necessary + const networkName = network?.name.toLowerCase() + + const { + data: transakLinkData, + isLoading: isLoadingTransakLink, + error: errorTransakLink + } = useTransakWidgetUrl( + { + isNFT: true, + calldata: calldataWithProxy, + targetContractAddress, + cryptoCurrencyCode: getCurrencyCode({ + chainId: network?.chainId || 137, + currencyAddress, + defaultCurrencyCode: currencySymbol || 'ETH' + }), + estimatedGasLimit, + nftData: transakNftData, + walletAddress: recipientAddress, + disableWalletAddressForm: true, + partnerOrderId, + network: networkName, + referrerDomain: window.location.origin + }, + disableTransakWidgetUrlFetch + ) + const missingCreditCardProvider = !creditCardProvider - const missingTransakConfig = !transakConfig && creditCardProvider === 'transak' - const transakApiKey = transakConfig?.apiKey || transakGlobalApiKey - if (missingCreditCardProvider || missingTransakConfig) { + if (missingCreditCardProvider) { return { error: new Error('Missing credit card provider or transak config'), data: { @@ -113,68 +172,45 @@ export const useCreditCardPayment = ({ } } - // Transak requires the recipient address to be the proxy address - // so we need to replace the recipient address with the proxy address in the calldata - // this is a weird hack so that credit card integrations are as simple as possible and should work 99% of the time - // If an issue arises, the user can override the calldata in the transak settings - - const calldataWithProxy = - transakConfig?.callDataOverride ?? - txData.replace(recipientAddress.toLowerCase().substring(2), TRANSAK_PROXY_ADDRESS.toLowerCase().substring(2)) - - const pakoData = Array.from(pako.deflate(calldataWithProxy)) + if (creditCardProvider === 'transak') { + const transakLink = transakLinkData?.url - const transakCallData = encodeURIComponent(btoa(String.fromCharCode.apply(null, pakoData))) - - const price = Number(formatUnits(BigInt(totalPriceRaw), Number(currencyDecimals || 18))) - - const transakNftDataJson = JSON.stringify([ - { - imageURL: tokenMetadata?.image || '', - nftName: tokenMetadata?.name || 'collectible', - collectionAddress: collectionAddress, - tokenID: [collectible.tokenId], - price: [price], - quantity: Number(collectible.quantity), - nftType: dataCollectionInfo?.type || 'ERC721' + return { + error: errorTransakLink, + data: { + iframeId: TRANSAK_IFRAME_ID, + paymentUrl: transakLink, + CreditCardIframe: () => ( +
+