Skip to content
27 changes: 24 additions & 3 deletions src/app/(mobile-ui)/qr-pay/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,25 @@ export default function QRPayPage() {
return null
}
}, [qrType])
const targetMantecaCountry = useMemo(() => {
switch (qrType) {
case EQrType.PIX:
return 'BR'
case EQrType.MERCADO_PAGO:
case EQrType.ARGENTINA_QR3:
return 'AR'
default:
return undefined
}
}, [qrType])

// Check if this payment provider is under maintenance
const isProviderDisabled = useMemo(() => {
return paymentProcessor ? maintenanceConfig.disabledPaymentProviders.includes(paymentProcessor) : false
}, [paymentProcessor])

const { shouldBlockPay, kycGateState } = useQrKycGate(paymentProcessor)
const { isUserMantecaKycApproved, isUserSumsubKycApproved } = useKycStatus()
const { isUserSumsubKycApproved } = useKycStatus()
const sumsubFlow = useMultiPhaseKycFlow({})
const queryClient = useQueryClient()
const [isShaking, setIsShaking] = useState(false)
Expand Down Expand Up @@ -1158,7 +1169,12 @@ export default function QRPayPage() {
{
text: 'Verify now',
onClick: () =>
sumsubFlow.handleInitiateKyc('LATAM', undefined, isUserSumsubKycApproved || undefined),
sumsubFlow.handleInitiateKyc(
'LATAM',
undefined,
isUserSumsubKycApproved || undefined,
targetMantecaCountry
),
variant: 'purple',
shadowSize: '4',
icon: 'check-circle',
Expand All @@ -1176,7 +1192,12 @@ export default function QRPayPage() {
{
text: 'Continue verification',
onClick: () =>
sumsubFlow.handleInitiateKyc('LATAM', undefined, isUserSumsubKycApproved || undefined),
sumsubFlow.handleInitiateKyc(
'LATAM',
undefined,
isUserSumsubKycApproved || undefined,
targetMantecaCountry
),
variant: 'purple',
shadowSize: '4',
icon: 'check-circle',
Expand Down
24 changes: 12 additions & 12 deletions src/app/(mobile-ui)/withdraw/manteca/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import ValidatedInput from '@/components/Global/ValidatedInput'
import AmountInput from '@/components/Global/AmountInput'
import { formatUnits, parseUnits } from 'viem'
import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
import { useAuth } from '@/context/authContext'
import { useModalsContext } from '@/context/ModalsContext'
import Select from '@/components/Global/Select'
import { SoundPlayer } from '@/components/Global/SoundPlayer'
Expand Down Expand Up @@ -61,6 +60,7 @@ import { useSumsubActionFlow } from '@/hooks/useSumsubActionFlow'
import { initiateIncreaseLimits } from '@/app/actions/increase-limits'
import { SumsubKycWrapper } from '@/components/Kyc/SumsubKycWrapper'
import { useLimits } from '@/hooks/useLimits'
import { useIdentityVerification } from '@/hooks/useIdentityVerification'

type MantecaWithdrawStep = 'amountInput' | 'bankDetails' | 'review' | 'success' | 'failure'

Expand Down Expand Up @@ -88,10 +88,10 @@ export default function MantecaWithdrawFlow() {
const { sendMoney, balance } = useWallet()
const { signTransferUserOp } = useSignUserOp()
const { isLoading, loadingState, setLoadingState } = useContext(loadingStateContext)
const { user } = useAuth()
const { setIsSupportModalOpen, openSupportWithMessage } = useModalsContext()
const queryClient = useQueryClient()
const { isUserMantecaKycApproved, isUserSumsubKycApproved } = useKycStatus()
const { isUserSumsubKycApproved } = useKycStatus()
const { isVerifiedForCountry } = useIdentityVerification()
const { manteca: mantecaRejection } = useProviderRejectionStatus()
const { hasPendingTransactions } = usePendingTransactions()

Expand All @@ -105,19 +105,19 @@ export default function MantecaWithdrawFlow() {
// Get method and country from URL parameters
const selectedMethodType = searchParams.get('method') // mercadopago, pix, bank-transfer, etc.
const countryFromUrl = searchParams.get('country') // argentina, brazil, etc.

// Determine country and currency from URL params or context
const countryPath = countryFromUrl || 'argentina'
const countryPath = countryFromUrl

// Map country path to CountryData for KYC
const selectedCountry = useMemo(() => {
if (!countryPath) return undefined
return countryData.find((country) => country.type === 'country' && country.path === countryPath)
}, [countryPath])

const countryConfig = useMemo(() => {
if (!selectedCountry) return undefined
return MANTECA_COUNTRIES_CONFIG[selectedCountry.id]
}, [selectedCountry])
const isUserMantecaKycApprovedForCountry = selectedCountry ? isVerifiedForCountry(selectedCountry.id) : false

const {
code: currencyCode,
Expand Down Expand Up @@ -236,7 +236,7 @@ export default function MantecaWithdrawFlow() {
}
setErrorMessage(null)

if (!isUserMantecaKycApproved) {
if (!isUserMantecaKycApprovedForCountry) {
setShowKycModal(true)
return
}
Expand Down Expand Up @@ -281,7 +281,7 @@ export default function MantecaWithdrawFlow() {
usdAmount,
currencyCode,
currencyAmount,
isUserMantecaKycApproved,
isUserMantecaKycApprovedForCountry,
isLockingPrice,
handleOnboardingError,
])
Expand Down Expand Up @@ -444,12 +444,12 @@ export default function MantecaWithdrawFlow() {
}
}, [step, queryClient])

// redirect to withdraw page if country is not supported by manteca
// redirect to withdraw page if country is missing or not supported by manteca
useEffect(() => {
if (!selectedCountry || !MANTECA_COUNTRIES_CONFIG[selectedCountry.id]) {
if (!countryFromUrl || !selectedCountry || !MANTECA_COUNTRIES_CONFIG[selectedCountry.id]) {
router.replace('/withdraw')
}
}, [selectedCountry, router])
}, [countryFromUrl, selectedCountry, router])

if (isCurrencyLoading || !currencyPrice || !selectedCountry || !countryConfig) {
return <PeanutLoading />
Expand Down Expand Up @@ -537,7 +537,7 @@ export default function MantecaWithdrawFlow() {
if (hasRejection) {
await sumsubFlow.handleSelfHealResubmit('MANTECA')
} else {
await sumsubFlow.handleInitiateKyc('LATAM', undefined, true)
await sumsubFlow.handleInitiateKyc('LATAM', undefined, true, selectedCountry?.id)
}
setShowKycModal(false)
}}
Expand Down
2 changes: 2 additions & 0 deletions src/app/actions/sumsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const initiateSumsubKyc = async (params?: {
regionIntent?: KYCRegionIntent
levelName?: string
crossRegion?: boolean
targetCountry?: string
}): Promise<{ data?: InitiateSumsubKycResponse; error?: string }> => {
const jwtToken = (await getJWTCookie())?.value

Expand All @@ -23,6 +24,7 @@ export const initiateSumsubKyc = async (params?: {
regionIntent: params?.regionIntent,
levelName: params?.levelName,
crossRegion: params?.crossRegion,
targetCountry: params?.targetCountry,
}

try {
Expand Down
14 changes: 7 additions & 7 deletions src/components/AddMoney/components/MantecaAddMoney.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useParams } from 'next/navigation'
import { type CountryData, countryData } from '@/components/AddMoney/consts'
import { type MantecaDepositResponseData } from '@/types/manteca.types'
import { useCurrency } from '@/hooks/useCurrency'
import { useAuth } from '@/context/authContext'
import { mantecaApi } from '@/services/manteca'
import { parseUnits } from 'viem'
import { useQueryClient } from '@tanstack/react-query'
Expand All @@ -22,6 +21,7 @@ import { useQueryStates, parseAsString, parseAsStringEnum } from 'nuqs'
import { useLimitsValidation } from '@/features/limits/hooks/useLimitsValidation'
import posthog from 'posthog-js'
import { ANALYTICS_EVENTS } from '@/constants/analytics.consts'
import { useIdentityVerification } from '@/hooks/useIdentityVerification'

// Step type for URL state
type MantecaStep = 'inputAmount' | 'depositDetails'
Expand Down Expand Up @@ -64,16 +64,16 @@ const MantecaAddMoney: FC = () => {
const selectedCountry = useMemo(() => {
return countryData.find((country) => country.type === 'country' && country.path === selectedCountryPath)
}, [selectedCountryPath])
const { isUserMantecaKycApproved, isUserSumsubKycApproved } = useKycStatus()
const { isUserSumsubKycApproved } = useKycStatus()
const { isVerifiedForCountry } = useIdentityVerification()
const { manteca: mantecaRejection } = useProviderRejectionStatus()
const currencyData = useCurrency(selectedCountry?.currency ?? 'ARS')
const { user } = useAuth()

// inline sumsub kyc flow for manteca users who need LATAM verification
// regionIntent is NOT passed here to avoid creating a backend record on mount.
// intent is passed at call time: handleInitiateKyc('LATAM')
const sumsubFlow = useMultiPhaseKycFlow({})
const [showKycModal, setShowKycModal] = useState(false)
const isUserMantecaKycApprovedForCountry = selectedCountry ? isVerifiedForCountry(selectedCountry.id) : false

// validates deposit amount against user's limits
// currency comes from country config - hook normalizes it internally
Expand Down Expand Up @@ -144,7 +144,7 @@ const MantecaAddMoney: FC = () => {
if (!selectedCountry?.currency) return
if (isCreatingDeposit) return

if (!isUserMantecaKycApproved) {
if (!isUserMantecaKycApprovedForCountry) {
setShowKycModal(true)
return
}
Expand Down Expand Up @@ -200,7 +200,7 @@ const MantecaAddMoney: FC = () => {
currentDenomination,
selectedCountry,
displayedAmount,
isUserMantecaKycApproved,
isUserMantecaKycApprovedForCountry,
isCreatingDeposit,
setUrlState,
usdAmount,
Expand All @@ -227,7 +227,7 @@ const MantecaAddMoney: FC = () => {
if (hasRejection) {
await sumsubFlow.handleSelfHealResubmit('MANTECA')
} else {
await sumsubFlow.handleInitiateKyc('LATAM', undefined, true)
await sumsubFlow.handleInitiateKyc('LATAM', undefined, true, selectedCountry?.id)
}
setShowKycModal(false)
}}
Expand Down
16 changes: 3 additions & 13 deletions src/components/Global/TranslationSafeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
'use client'
import { useTranslationMutationHandler } from '@/hooks/useTranslationMutationHandler'
import { useRef } from 'react'

// wraps the app to handle google translate dom mutations globally
// prevents "Failed to execute 'insertBefore' on 'Node'" errors
// while still allowing translations to work properly
// patches dom methods globally to prevent translation extension crashes
export const TranslationSafeWrapper = ({ children }: { children: React.ReactNode }) => {
const wrapperRef = useRef<HTMLDivElement>(null)
// attach mutation observer to handle translation service dom changes
useTranslationMutationHandler(wrapperRef)

return (
<div ref={wrapperRef} className="contents">
{children}
</div>
)
useTranslationMutationHandler()
return <>{children}</>
}
10 changes: 9 additions & 1 deletion src/components/Kyc/InitiateKycModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface InitiateKycModalProps {
// for fresh KYC: "Verify your identity"
// for provider rejections: "We need extra documents"
// for blocked: "Verification issue — contact support"
// for cross-region: "Your identity is verified, we need a local ID"
// for cross-region: "Your identity is verified, submit a local ID"
export const InitiateKycModal = ({
visible,
onClose,
Expand All @@ -41,6 +41,7 @@ export const InitiateKycModal = ({
if (error) return 'Something went wrong'
if (isBlocked) return 'Verification issue'
if (isProviderRejection) return 'We need extra documents'
if (isCrossRegion) return 'Submit local ID'
return 'Verify your identity'
}

Expand Down Expand Up @@ -70,6 +71,13 @@ export const InitiateKycModal = ({
icon: 'upload' as IconName,
}
}
if (isCrossRegion) {
return {
text: isLoading ? 'Loading...' : 'Submit document',
onClick: onVerify,
icon: 'upload' as IconName,
}
}
return {
text: isLoading ? 'Loading...' : 'Start Verification',
onClick: onVerify,
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useMultiPhaseKycFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export const useMultiPhaseKycFlow = ({ onKycSuccess, onManualClose, regionIntent

// wrap handleInitiateKyc to reset state for new attempts
const handleInitiateKyc = useCallback(
async (overrideIntent?: KYCRegionIntent, levelName?: string, crossRegion?: boolean) => {
async (overrideIntent?: KYCRegionIntent, levelName?: string, crossRegion?: boolean, targetCountry?: string) => {
const intent = overrideIntent ?? regionIntent
posthog.capture(
intent === 'LATAM' ? ANALYTICS_EVENTS.MANTECA_KYC_INITIATED : ANALYTICS_EVENTS.KYC_INITIATED,
Expand All @@ -199,7 +199,7 @@ export const useMultiPhaseKycFlow = ({ onKycSuccess, onManualClose, regionIntent
isRealtimeFlowRef.current = false
clearPreparingTimer()

await originalHandleInitiateKyc(overrideIntent, levelName, crossRegion)
await originalHandleInitiateKyc(overrideIntent, levelName, crossRegion, targetCountry)
},
[originalHandleInitiateKyc, clearPreparingTimer, regionIntent, acquisitionSource]
)
Expand Down
8 changes: 7 additions & 1 deletion src/hooks/useSumsubKycFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
const regionIntentRef = useRef<KYCRegionIntent | undefined>(regionIntent)
// tracks the level name across initiate + refresh (e.g. 'peanut-additional-docs')
const levelNameRef = useRef<string | undefined>(undefined)
// tracks the selected target country across initiate + refresh for country-scoped Manteca actions
const targetCountryRef = useRef<string | undefined>(undefined)
// guards fetchCurrentStatus from running while handleInitiateKyc is in progress
const initiatingRef = useRef(false)
// guard: only fire onKycSuccess when the user initiated a kyc flow in this session.
Expand Down Expand Up @@ -120,6 +122,7 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
const response = await initiateSumsubKyc({
regionIntent: regionIntentRef.current,
levelName: levelNameRef.current,
targetCountry: targetCountryRef.current,
})
if (response.data?.status) {
setLiveKycStatus(response.data.status)
Expand All @@ -134,7 +137,7 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
}, [isVerificationProgressModalOpen])

const handleInitiateKyc = useCallback(
async (overrideIntent?: KYCRegionIntent, levelName?: string, crossRegion?: boolean) => {
async (overrideIntent?: KYCRegionIntent, levelName?: string, crossRegion?: boolean, targetCountry?: string) => {
userInitiatedRef.current = true
initiatingRef.current = true
selfHealProviderRef.current = null
Expand All @@ -154,6 +157,7 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
regionIntent: overrideIntent ?? regionIntent,
levelName,
crossRegion,
targetCountry,
})

if (response.error) {
Expand All @@ -174,6 +178,7 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
const effectiveIntent = overrideIntent ?? regionIntent
if (effectiveIntent) regionIntentRef.current = effectiveIntent
levelNameRef.current = levelName
targetCountryRef.current = targetCountry

// cross-region: bridge-direct means no SDK needed — backend is handling
// rail enrollment + submission. go straight to the post-approval flow.
Expand Down Expand Up @@ -246,6 +251,7 @@ export const useSumsubKycFlow = ({ onKycSuccess, onManualClose, regionIntent }:
const response = await initiateSumsubKyc({
regionIntent: regionIntentRef.current,
levelName: levelNameRef.current,
targetCountry: targetCountryRef.current,
})

if (response.error || !response.data?.token) {
Expand Down
Loading
Loading