Skip to content

Commit

Permalink
transak integration
Browse files Browse the repository at this point in the history
  • Loading branch information
SamueleA committed Dec 11, 2024
1 parent 0876a17 commit 58bf59f
Show file tree
Hide file tree
Showing 6 changed files with 462 additions and 50 deletions.
2 changes: 2 additions & 0 deletions packages/checkout/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"pako": "^2.1.0",
"qrcode.react": "^4.0.1",
"react-copy-to-clipboard": "^5.1.0",
"timeago-react": "^3.0.6"
Expand All @@ -55,6 +56,7 @@
"devDependencies": {
"@0xsequence/design-system": "^1.7.8",
"@0xsequence/kit": "workspace:*",
"@types/pako": "^2.0.3",
"@types/react-copy-to-clipboard": "^5.0.7",
"ethers": "^6.13.0",
"framer-motion": "^8.5.2",
Expand Down
6 changes: 6 additions & 0 deletions packages/checkout/src/contexts/CheckoutModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ interface OrderSummaryItem {
tokenId: string
}

export interface TransakConfig {
apiKey: string
contractId: string
}

export interface CreditCardCheckout {
chainId: number
contractAddress: string
Expand All @@ -30,6 +35,7 @@ export interface CreditCardCheckout {
nftDecimals?: string
calldata: string
provider?: 'sardine' | 'transak'
transakConfig?: TransakConfig
onSuccess?: (transactionHash: string, settings: CreditCardCheckout) => void
onError?: (error: Error, settings: CreditCardCheckout) => void
isDev?: boolean
Expand Down
2 changes: 2 additions & 0 deletions packages/checkout/src/contexts/SelectPaymentModal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Hex } from 'viem'

import { createGenericContext } from './genericContext'
import type { TransakConfig } from '../contexts/CheckoutModal'

export type CreditCardProviders = 'sardine' | 'transak'

Expand Down Expand Up @@ -30,6 +31,7 @@ export interface SelectPaymentSettings {
enableTransferFunds?: boolean
creditCardProviders?: string[]
copyrightText?: string
transakConfig?: TransakConfig
}

type SelectPaymentModalContext = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export const PayWithCreditCard = ({ settings, disableButtons }: PayWithCreditCar
isDev = false,
onSuccess = () => {},
onError = () => {},
creditCardProviders = []
creditCardProviders = [],
transakConfig
} = settings

const { address: userAddress } = useAccount()
Expand Down Expand Up @@ -97,6 +98,7 @@ export const PayWithCreditCard = ({ settings, disableButtons }: PayWithCreditCar
isDev,
provider: selectedPaymentProvider,
calldata: txData,
transakConfig,
approvedSpenderAddress: approvedSpenderAddress || targetContractAddress
}
}
Expand All @@ -109,41 +111,50 @@ export const PayWithCreditCard = ({ settings, disableButtons }: PayWithCreditCar
return (
<Box flexDirection="column" justifyContent="center" alignItems="center" gap="2" width="full">
{/* Only 1 option will be displayed, even if multiple providers are passed */}
{creditCardProviders.slice(0, 1).map(creditCardProvider => {
switch (creditCardProvider) {
case 'sardine':
case 'transak':
return (
<Card
key="sardine"
justifyContent="space-between"
alignItems="center"
padding="4"
onClick={() => {
setSelectedPaymentProvider(creditCardProvider)
}}
opacity={{
hover: '80',
base: '100'
}}
cursor="pointer"
disabled={disableButtons}
>
<Box flexDirection="row" gap="3" alignItems="center">
<PaymentsIcon color="white" />
<Text color="text100" variant="normal" fontWeight="bold">
Pay with credit or debit card
</Text>
</Box>
<Box style={{ transform: 'rotate(-45deg)' }}>
<ArrowRightIcon color="white" />
</Box>
</Card>
)
default:
return null
}
})}
{creditCardProviders
.slice(0, 1)
.filter(provider => {
// cannot display transak checkout if the settings aren't provided
if (provider === 'transak' && !settings.transakConfig) {
return false
}
return true
})
.map(creditCardProvider => {
switch (creditCardProvider) {
case 'sardine':
case 'transak':
return (
<Card
key="sardine"
justifyContent="space-between"
alignItems="center"
padding="4"
onClick={() => {
setSelectedPaymentProvider(creditCardProvider)
}}
opacity={{
hover: '80',
base: '100'
}}
cursor="pointer"
disabled={disableButtons}
>
<Box flexDirection="row" gap="3" alignItems="center">
<PaymentsIcon color="white" />
<Text color="text100" variant="normal" fontWeight="bold">
Pay with credit or debit card
</Text>
</Box>
<Box style={{ transform: 'rotate(-45deg)' }}>
<ArrowRightIcon color="white" />
</Box>
</Card>
)
default:
return null
}
})}
</Box>
)
}
Expand Down
169 changes: 167 additions & 2 deletions packages/checkout/src/views/PendingCreditCardTransaction.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Box, Spinner, Text } from '@0xsequence/design-system'
import { useProjectAccessKey, useTokenMetadata } from '@0xsequence/kit'
import { useProjectAccessKey, useContractInfo, useTokenMetadata } from '@0xsequence/kit'
import pako from 'pako'
import React, { useEffect } from 'react'
import { formatUnits, parseUnits } from 'viem'

import { fetchSardineOrderStatus } from '../api'
import { TransactionPendingNavigation } from '../contexts'
Expand All @@ -26,7 +28,170 @@ export const PendingCreditCardTransaction = () => {
}

export const PendingCreditCardTransactionTransak = () => {
return null
const { openTransactionStatusModal } = useTransactionStatusModal()
const nav = useNavigation()
const { settings, closeCheckout } = useCheckoutModal()

const {
params: { creditCardCheckout }
} = nav.navigation as TransactionPendingNavigation

const { setNavigation } = nav
const projectAccessKey = useProjectAccessKey()

const {
data: tokensMetadata,
isLoading: isLoadingTokenMetadata,
isError: isErrorTokenMetadata
} = useTokenMetadata(creditCardCheckout.chainId, creditCardCheckout.nftAddress, [creditCardCheckout.nftId])
const {
data: collectionInfo,
isLoading: isLoadingCollectionInfo,
isError: isErrorCollectionInfo
} = useContractInfo(creditCardCheckout.chainId, creditCardCheckout.nftAddress)

const tokenMetadata = tokensMetadata ? tokensMetadata[0] : undefined

const transakConfig = settings?.creditCardCheckout?.transakConfig

const baseUrl = creditCardCheckout.isDev ? 'https://global-stg.transak.com' : 'https://global.transak.com'

const pakoData = Array.from(pako.deflate(creditCardCheckout.calldata))

const transakCallData = encodeURIComponent(btoa(String.fromCharCode.apply(null, pakoData)))

const price = Number(formatUnits(BigInt(creditCardCheckout.currencyQuantity), Number(creditCardCheckout.currencyDecimals)))
const formattedQuantity = Number(creditCardCheckout.currencyQuantity)

const transakNftDataJson = JSON.stringify([
{
imageURL: tokenMetadata?.image || '',
nftName: tokenMetadata?.name || 'collectible',
collectionAddress: creditCardCheckout.nftAddress,
tokenID: [creditCardCheckout.nftId],
price: [price],
quantity: formattedQuantity,
nftType: collectionInfo?.type || 'ERC721'
}
])

const transakNftData = encodeURIComponent(btoa(transakNftDataJson))

const estimatedGasLimit = '500000'

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

const transakLink = `${baseUrl}?apiKey=${transakConfig?.apiKey}&isNFT=true&calldata=${transakCallData}&contractId=${transakConfig?.contractId}&cryptoCurrencyCode=${creditCardCheckout.currencySymbol}&estimatedGasLimit=${estimatedGasLimit}&nftData=${transakNftData}&walletAddress=${creditCardCheckout.recipientAddress}&disableWalletAddressForm=true&partnerOrderId=${partnerOrderId}`

const isLoading = isLoadingTokenMetadata
const isError = isErrorTokenMetadata

useEffect(() => {
const transakIframeElement = document.getElementById('transakIframe') as HTMLIFrameElement
const transakIframe = transakIframeElement.contentWindow

const readMessage = (message: any) => {
if (message.source !== transakIframe) return

if (message?.data?.event_id === 'TRANSAK_ORDER_SUCCESSFUL') {
console.log('Order Data: ', message?.data?.data)
const txHash = message?.data?.data?.transactionHash || ''

closeCheckout()
openTransactionStatusModal({
chainId: creditCardCheckout.chainId,
currencyAddress: creditCardCheckout.currencyAddress,
collectionAddress: creditCardCheckout.nftAddress,
txHash: txHash,
items: [
{
tokenId: creditCardCheckout.nftId,
quantity: creditCardCheckout.nftQuantity,
decimals: creditCardCheckout.nftDecimals === undefined ? undefined : Number(creditCardCheckout.nftDecimals),
price: creditCardCheckout.currencyQuantity
}
],
onSuccess: () => {
if (creditCardCheckout.onSuccess) {
creditCardCheckout.onSuccess(txHash, creditCardCheckout)
}
}
})
return
}

if (message?.data?.event_id === 'TRANSAK_ORDER_FAILED') {
setNavigation({
location: 'transaction-error',
params: {
error: new Error('Transak transaction failed')
}
})
}
}

window.addEventListener('message', readMessage)

return () => window.removeEventListener('message', readMessage)
}, [isLoading])

if (isError || !transakConfig) {
return (
<Box
flexDirection="column"
justifyContent="center"
alignItems="center"
gap="6"
style={{
height: '650px',
width: '380px'
}}
>
<Box>
{!transakConfig ? (
<Text color="text100">Error: No Transak configuration found</Text>
) : (
<Text color="text100">An error has occurred</Text>
)}
</Box>
</Box>
)
}

if (isLoading) {
return (
<Box
flexDirection="column"
justifyContent="center"
alignItems="center"
gap="6"
style={{
height: '650px',
width: '380px'
}}
>
<Box>
<Spinner size="lg" />
</Box>
</Box>
)
}

return (
<Box alignItems="center" justifyContent="center" style={{ height: '770px' }}>
<iframe
id="transakIframe"
allow="camera;microphone;payment"
src={transakLink}
style={{
maxHeight: '650px',
height: '100%',
maxWidth: '380px',
width: '100%'
}}
/>
</Box>
)
}

export const PendingCreditCardTransactionSardine = () => {
Expand Down
Loading

0 comments on commit 58bf59f

Please sign in to comment.