Skip to content
Merged
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
8 changes: 4 additions & 4 deletions examples/react/src/components/Connected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
},
Expand Down
4 changes: 0 additions & 4 deletions examples/react/src/components/CustomCheckout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
Expand Down
2 changes: 0 additions & 2 deletions examples/react/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/react/src/utils/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const checkoutPresets: Record<string, (recipientAddress: string) => Check
}
}
},
'forte-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => {
'forte-transak-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => {
const collectibles = [
{
tokenId: '1',
Expand Down
4 changes: 0 additions & 4 deletions packages/checkout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
Expand Down
61 changes: 61 additions & 0 deletions packages/checkout/src/api/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,6 @@ export const SequenceCheckoutProvider = ({ children, config }: SequenceCheckoutP
<EnvironmentContextProvider
value={{
marketplaceApiUrl: config?.env?.marketplaceApiUrl ?? 'https://marketplace-api.sequence.app',
transakApiUrl: config?.env?.transakApiUrl ?? 'https://global.transak.com',
transakApiKey: config?.env?.transakApiKey ?? '5911d9ec-46b5-48fa-a755-d59a715ff0cf',
forteWidgetUrl: config?.env?.forteWidgetUrl ?? 'https://payments.prod.lemmax.com/forte-payments-widget.js'
}}
>
Expand Down
2 changes: 0 additions & 2 deletions packages/checkout/src/contexts/CheckoutModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ interface OrderSummaryItem {
}

export interface TransakConfig {
apiKey?: string
contractId: string
callDataOverride?: string
}

Expand Down
2 changes: 0 additions & 2 deletions packages/checkout/src/contexts/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { createGenericContext } from './genericContext.js'

export interface EnvironmentOverrides {
marketplaceApiUrl: string
transakApiUrl: string
transakApiKey: string
forteWidgetUrl: string
}

Expand Down
164 changes: 100 additions & 64 deletions packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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
Expand All @@ -85,11 +89,66 @@ export const useCreditCardPayment = ({
const iframeRef = useRef<HTMLIFrameElement | null>(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: {
Expand All @@ -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: () => (
<div className="flex items-center justify-center" style={{ height: '770px' }}>
<iframe
id="transakIframe"
ref={iframeRef}
allow="camera;microphone;payment"
src={transakLink}
style={{
maxHeight: '650px',
height: '100%',
maxWidth: '380px',
width: '100%'
}}
referrerPolicy="strict-origin-when-cross-origin"
/>
</div>
),
EventListener: () => (
<TransakEventListener onSuccess={onSuccess} onError={onError} isLoading={isLoading} iframeRef={iframeRef} />
)
},
isLoading: isLoadingTransakLink
}
])

const transakNftData = encodeURIComponent(btoa(transakNftDataJson))

const estimatedGasLimit = '500000'

const partnerOrderId = `${recipientAddress}-${new Date().getTime()}`

// Note: the network name might not always line up with Transak. A conversion function might be necessary
const networkName = network?.name.toLowerCase()
const transakLink = `${transakApiUrl}?apiKey=${transakApiKey}&isNFT=true&calldata=${transakCallData}&contractId=${transakConfig?.contractId}&cryptoCurrencyCode=${currencySymbol}&estimatedGasLimit=${estimatedGasLimit}&nftData=${transakNftData}&walletAddress=${recipientAddress}&disableWalletAddressForm=true&partnerOrderId=${partnerOrderId}&network=${networkName}`
}

return {
error: null,
data: {
iframeId: TRANSAK_IFRAME_ID,
paymentUrl: transakLink,
CreditCardIframe: () => (
<div className="flex items-center justify-center" style={{ height: '770px' }}>
<iframe
id="transakIframe"
ref={iframeRef}
allow="camera;microphone;payment"
src={transakLink}
style={{
maxHeight: '650px',
height: '100%',
maxWidth: '380px',
width: '100%'
}}
referrerPolicy="strict-origin-when-cross-origin"
/>
</div>
),
EventListener: () => (
<TransakEventListener onSuccess={onSuccess} onError={onError} isLoading={isLoading} iframeRef={iframeRef} />
)
iframeId: '',
CreditCardIframe: () => null,
EventListener: () => null
},
isLoading: false
}
Expand Down
4 changes: 0 additions & 4 deletions packages/checkout/src/hooks/useSelectPaymentModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ type UseSelectPaymentModalReturnType = {
* currencyAddress,
* collectionAddress,
* creditCardProviders: ['transak'],
* transakConfig: {
* contractId: 'your-contract-id',
* apiKey: 'your-api-key'
* },
* copyrightText: 'ⓒ2024 Your Company',
* onSuccess: (txnHash: string) => {
* console.log('success!', txnHash)
Expand Down
16 changes: 16 additions & 0 deletions packages/checkout/src/hooks/useTransakWidgetUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from '@tanstack/react-query'
import { getTransakWidgetUrl, type TransakWidgetUrlArgs } from '../api/data.js'
import { useConfig } from '@0xsequence/hooks'

export const useTransakWidgetUrl = (args: TransakWidgetUrlArgs, disabled?: boolean) => {
const { env, projectAccessKey } = useConfig()

const apiUrl = env.apiUrl

return useQuery({
queryKey: ['transakWidgetUrl', args],
queryFn: () => getTransakWidgetUrl(apiUrl, projectAccessKey, args),
staleTime: 5 * 60 * 1000,
enabled: !disabled && args.walletAddress !== ''
})
}
2 changes: 1 addition & 1 deletion packages/checkout/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export { type TransactionStatusSettings } from './contexts/TransactionStatusModa
export { useTransactionStatusModal } from './hooks/useTransactionStatusModal.js'

// utils
export { fetchTransakSupportedCountries, getTransakLink } from './utils/transak.js'
export { fetchTransakSupportedCountries } from './utils/transak.js'

// OnRampProvider
export { TransactionOnRampProvider } from '@0xsequence/marketplace'
Loading