diff --git a/src/components/icons/payment-methods/payment-methods.tsx b/src/components/icons/payment-methods/payment-methods.tsx index 85193b0eb6..9169ecf2d9 100644 --- a/src/components/icons/payment-methods/payment-methods.tsx +++ b/src/components/icons/payment-methods/payment-methods.tsx @@ -94,6 +94,26 @@ const PaymentMethodIcon: React.FC<{ ); + case 'cashApp': + return ( + + + + + ); + case 'other': return ( diff --git a/src/navigation/services/buy-crypto/components/PaymentMethodModal.tsx b/src/navigation/services/buy-crypto/components/PaymentMethodModal.tsx index ca0d64f219..e518e4656a 100644 --- a/src/navigation/services/buy-crypto/components/PaymentMethodModal.tsx +++ b/src/navigation/services/buy-crypto/components/PaymentMethodModal.tsx @@ -40,6 +40,7 @@ import { getPaymentMethodIconByKey, } from '../constants/BuyCryptoConstants'; import {showBottomNotificationModal} from '../../../../store/app/app.actions'; +import {getCachedExternalServicesConfig} from '../../../../store/external-services/external-services.effects'; import {sleep} from '../../../../utils/helper-methods'; import {BottomSheetScrollView} from '@gorhom/bottom-sheet'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; @@ -155,6 +156,7 @@ const PaymentMethodsModal = ({ ); const getEnabledPaymentMethods = () => { + const externalServicesConfig = getCachedExternalServicesConfig()?.config; switch (context) { case 'buyCrypto': return getBuyEnabledPaymentMethods( @@ -163,6 +165,7 @@ const PaymentMethodsModal = ({ chain, locationData?.countryShortCode || 'US', preSetPartner as BuyCryptoExchangeKey, + externalServicesConfig?.buyCrypto, ); case 'sellCrypto': @@ -173,6 +176,7 @@ const PaymentMethodsModal = ({ locationData?.countryShortCode || 'US', user?.country, preSetPartner as SellCryptoExchangeKey, + externalServicesConfig?.sellCrypto, ); default: diff --git a/src/navigation/services/buy-crypto/constants/BuyCryptoConstants.tsx b/src/navigation/services/buy-crypto/constants/BuyCryptoConstants.tsx index 750a997184..179bbf6499 100644 --- a/src/navigation/services/buy-crypto/constants/BuyCryptoConstants.tsx +++ b/src/navigation/services/buy-crypto/constants/BuyCryptoConstants.tsx @@ -3,6 +3,7 @@ import {Platform} from 'react-native'; import PaymentMethodIcon from '../../../../components/icons/payment-methods/payment-methods'; import { countriesWithACH, + countriesWithCashApp, countriesWithPIX, countriesWithSEPA, } from '../../constants/PaymentMethodsConstants'; @@ -11,6 +12,7 @@ import {BuyCryptoExchangeKey} from '../utils/buy-crypto-utils'; export type PaymentMethodKey = | 'ach' | 'applePay' + | 'cashApp' | 'creditCard' | 'debitCard' | 'googlePay' @@ -110,6 +112,33 @@ export const PaymentMethodsAvailable: PaymentMethods = { }, enabled: Platform.OS === 'ios', }, + cashApp: { + order: 3.5, + label: 'Cash App Pay', + method: 'cashApp', + waitingTimeDescription: '< 10 mins', + imgLogo: ( + + ), + imgIcon: ( + + ), + supportedExchanges: { + banxa: false, + moonpay: true, + ramp: false, + sardine: false, + simplex: false, + transak: false, + }, + supportedCountries: countriesWithCashApp, + enabled: true, + }, creditCard: { order: 5, label: 'Credit Card', diff --git a/src/navigation/services/buy-crypto/utils/buy-crypto-utils.ts b/src/navigation/services/buy-crypto/utils/buy-crypto-utils.ts index 7c1b00f376..67293d3d04 100644 --- a/src/navigation/services/buy-crypto/utils/buy-crypto-utils.ts +++ b/src/navigation/services/buy-crypto/utils/buy-crypto-utils.ts @@ -29,7 +29,11 @@ import { } from './transak-utils'; import pickBy from 'lodash.pickby'; import {LocationData} from '../../../../store/location/location.models'; -import {getExternalServiceSymbol} from '../../utils/external-services-utils'; +import {BuyCryptoConfig} from '../../../../store/external-services/external-services.types'; +import { + getExternalServiceSymbol, + isPaymentMethodDisabledForPartner, +} from '../../utils/external-services-utils'; export type BuyCryptoExchangeKey = | 'banxa' @@ -54,10 +58,12 @@ export const getBuyEnabledPaymentMethods = ( chain?: string, country?: string, exchange?: BuyCryptoExchangeKey | undefined, + buyCryptoConfig?: BuyCryptoConfig, ): Partial => { if (!currency || !coin || !chain) { return {}; } + const paymentMethodsConfig = buyCryptoConfig?.paymentMethods; Object.values(PaymentMethodsAvailable).forEach(pm => { if ( pm.enabled && @@ -69,57 +75,18 @@ export const getBuyEnabledPaymentMethods = ( }); const EnabledPaymentMethods = pickBy(PaymentMethodsAvailable, method => { + if (!method.enabled || paymentMethodsConfig?.[method.method]?.disabled) { + return false; + } + const isSupportedByExchange = (exch: BuyCryptoExchangeKey) => + !isPaymentMethodDisabledForPartner( + buyCryptoConfig?.[exch], + method.method, + ) && + isPaymentMethodSupported(exch, method, coin, chain, currency, country); return exchange && BuyCryptoSupportedExchanges.includes(exchange) - ? method.enabled && - isPaymentMethodSupported( - exchange, - method, - coin, - chain, - currency, - country, - ) - : method.enabled && - (isPaymentMethodSupported( - 'banxa', - method, - coin, - chain, - currency, - country, - ) || - isPaymentMethodSupported( - 'moonpay', - method, - coin, - chain, - currency, - country, - ) || - isPaymentMethodSupported('ramp', method, coin, chain, currency) || - isPaymentMethodSupported( - 'sardine', - method, - coin, - chain, - currency, - country, - ) || - isPaymentMethodSupported( - 'simplex', - method, - coin, - chain, - currency, - ) || - isPaymentMethodSupported( - 'transak', - method, - coin, - chain, - currency, - country, - )); + ? isSupportedByExchange(exchange) + : BuyCryptoSupportedExchanges.some(isSupportedByExchange); }); return EnabledPaymentMethods; diff --git a/src/navigation/services/buy-crypto/utils/moonpay-utils.ts b/src/navigation/services/buy-crypto/utils/moonpay-utils.ts index 8abdfeb797..d6a92f7c62 100644 --- a/src/navigation/services/buy-crypto/utils/moonpay-utils.ts +++ b/src/navigation/services/buy-crypto/utils/moonpay-utils.ts @@ -291,6 +291,9 @@ export const getMoonpayPaymentMethodFormat = ( case 'venmo': moonpayPaymentMethod = 'venmo'; break; + case 'cashApp': + moonpayPaymentMethod = 'cash_app'; + break; default: moonpayPaymentMethod = undefined; break; diff --git a/src/navigation/services/components/externalServicesOfferSelector.tsx b/src/navigation/services/components/externalServicesOfferSelector.tsx index d3a8450f55..83ec10e502 100644 --- a/src/navigation/services/components/externalServicesOfferSelector.tsx +++ b/src/navigation/services/components/externalServicesOfferSelector.tsx @@ -121,7 +121,10 @@ import _ from 'lodash'; import SheetModal from '../../../components/modal/base/sheet/SheetModal'; import {TouchableOpacity} from '../../../components/base/TouchableOpacity'; import OfferSelectorModal from './OfferSelectorModal'; -import {getErrorMessage} from '../utils/external-services-utils'; +import { + getErrorMessage, + isPaymentMethodDisabledForPartner, +} from '../utils/external-services-utils'; import {BuyCryptoStateOpts} from '../../../store/buy-crypto/buy-crypto.reducer'; import { ExternalServicesContext, @@ -512,45 +515,79 @@ const ExternalServicesOfferSelector: React.FC< ({SELL_CRYPTO}) => SELL_CRYPTO.opts, ); - let _paymentMethod: PaymentMethod | WithdrawalMethod | undefined; - if (context === 'buyCrypto') { - if ( - preSetPaymentMethod && - PaymentMethodsAvailable[preSetPaymentMethod as PaymentMethodKey] - ) { - _paymentMethod = - PaymentMethodsAvailable[preSetPaymentMethod as PaymentMethodKey]; - } else if ( - buyCryptoOpts?.selectedPaymentMethod && - PaymentMethodsAvailable[buyCryptoOpts.selectedPaymentMethod] && - !preSetPartner - ) { - _paymentMethod = - PaymentMethodsAvailable[buyCryptoOpts.selectedPaymentMethod]; - } else { - _paymentMethod = - Platform.OS === 'ios' - ? PaymentMethodsAvailable.applePay - : PaymentMethodsAvailable.debitCard; - } - } else if (context === 'sellCrypto') { - if ( - preSetPaymentMethod && - WithdrawalMethodsAvailable[preSetPaymentMethod as WithdrawalMethodKey] - ) { - _paymentMethod = - WithdrawalMethodsAvailable[preSetPaymentMethod as WithdrawalMethodKey]; - } else if ( - sellCryptoOpts?.selectedWithdrawalMethod && - WithdrawalMethodsAvailable[sellCryptoOpts.selectedWithdrawalMethod] && - !preSetPartner - ) { - _paymentMethod = - WithdrawalMethodsAvailable[sellCryptoOpts.selectedWithdrawalMethod]; - } else { - _paymentMethod = getDefaultPaymentMethod(country); + const paymentMethodsConfig = + context === 'buyCrypto' + ? buyCryptoConfig?.paymentMethods + : sellCryptoConfig?.paymentMethods; + + const isPaymentMethodEnabledByConfig = ( + method: PaymentMethodKey | WithdrawalMethodKey | undefined, + ): boolean => !method || !paymentMethodsConfig?.[method]?.disabled; + + // Resolves the payment/withdrawal method to use, in order of priority: + // 1. preSetPaymentMethod (deeplink), 2. last method used (opts), 3. default. + // Each candidate is validated against the remote paymentMethods config. + const resolvePaymentMethod = (): + | PaymentMethod + | WithdrawalMethod + | undefined => { + if (context === 'buyCrypto') { + if ( + preSetPaymentMethod && + PaymentMethodsAvailable[preSetPaymentMethod as PaymentMethodKey] && + isPaymentMethodEnabledByConfig(preSetPaymentMethod) && + country && + PaymentMethodsAvailable[ + preSetPaymentMethod as PaymentMethodKey + ]?.supportedCountries?.includes(country) + ) { + return PaymentMethodsAvailable[preSetPaymentMethod as PaymentMethodKey]; + } + if ( + buyCryptoOpts?.selectedPaymentMethod && + PaymentMethodsAvailable[buyCryptoOpts.selectedPaymentMethod] && + isPaymentMethodEnabledByConfig(buyCryptoOpts.selectedPaymentMethod) && + !preSetPartner + ) { + return PaymentMethodsAvailable[buyCryptoOpts.selectedPaymentMethod]; + } + return Platform.OS === 'ios' && isPaymentMethodEnabledByConfig('applePay') + ? PaymentMethodsAvailable.applePay + : PaymentMethodsAvailable.debitCard; + } else if (context === 'sellCrypto') { + if ( + preSetPaymentMethod && + WithdrawalMethodsAvailable[ + preSetPaymentMethod as WithdrawalMethodKey + ] && + isPaymentMethodEnabledByConfig(preSetPaymentMethod) && + country && + WithdrawalMethodsAvailable[ + preSetPaymentMethod as WithdrawalMethodKey + ]?.supportedCountries?.includes(country) + ) { + return WithdrawalMethodsAvailable[ + preSetPaymentMethod as WithdrawalMethodKey + ]; + } + if ( + sellCryptoOpts?.selectedWithdrawalMethod && + WithdrawalMethodsAvailable[sellCryptoOpts.selectedWithdrawalMethod] && + isPaymentMethodEnabledByConfig( + sellCryptoOpts.selectedWithdrawalMethod, + ) && + !preSetPartner + ) { + return WithdrawalMethodsAvailable[ + sellCryptoOpts.selectedWithdrawalMethod + ]; + } + return getDefaultPaymentMethod(country, paymentMethodsConfig); } - } + return undefined; + }; + + const _paymentMethod = resolvePaymentMethod(); const [paymentMethod, setPaymentMethod] = useState( _paymentMethod as PaymentMethod, @@ -563,6 +600,33 @@ const ExternalServicesOfferSelector: React.FC< WithdrawalMethod | undefined >(_paymentMethod as WithdrawalMethod); + // The config props arrive asynchronously, so the initial useState value may + // hold a method that is disabled by config. Re-validate when config arrives, + // re-resolving with the same priority chain (preSet > last used > default). + useEffect(() => { + if (!paymentMethodsConfig) { + return; + } + if ( + context === 'buyCrypto' && + paymentMethod && + !isPaymentMethodEnabledByConfig(paymentMethod.method) + ) { + const fallback = resolvePaymentMethod() as PaymentMethod; + setPaymentMethod(fallback); + onSelectPaymentMethod?.(fallback); + } else if ( + context === 'sellCrypto' && + withdrawalMethod && + !isPaymentMethodEnabledByConfig(withdrawalMethod.method) + ) { + const fallback = resolvePaymentMethod() as WithdrawalMethod; + setWithdrawalMethod(fallback); + onSelectPaymentMethod?.(fallback); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [paymentMethodsConfig]); + if (context === 'buyCrypto') { BuyCryptoSupportedExchanges.forEach((exchange: BuyCryptoExchangeKey) => { if (offersDefault[exchange]) { @@ -587,6 +651,10 @@ const ExternalServicesOfferSelector: React.FC< exchangeFiatCurrency, country, ) && + !isPaymentMethodDisabledForPartner( + buyCryptoConfig?.[preSetPartner], + paymentMethod.method, + ) && (!buyCryptoConfig?.[preSetPartner] || !buyCryptoConfig?.[preSetPartner]?.removed) : false; @@ -600,6 +668,10 @@ const ExternalServicesOfferSelector: React.FC< exchangeFiatCurrency, country, ) && + !isPaymentMethodDisabledForPartner( + buyCryptoConfig?.[exchange], + paymentMethod.method, + ) && (!buyCryptoConfig?.[exchange] || !buyCryptoConfig?.[exchange]?.removed); } @@ -652,6 +724,10 @@ const ExternalServicesOfferSelector: React.FC< country, user?.country, ) && + !isPaymentMethodDisabledForPartner( + sellCryptoConfig?.[preSetPartner], + withdrawalMethod.method, + ) && (!sellCryptoConfig?.[preSetPartner] || !sellCryptoConfig?.[preSetPartner]?.removed) : false; @@ -666,6 +742,10 @@ const ExternalServicesOfferSelector: React.FC< country, user?.country, ) && + !isPaymentMethodDisabledForPartner( + sellCryptoConfig?.[exchange], + withdrawalMethod.method, + ) && (!sellCryptoConfig?.[exchange] || !sellCryptoConfig?.[exchange]?.removed); } @@ -957,6 +1037,9 @@ const ExternalServicesOfferSelector: React.FC< if (_paymentMethod === 'sepa_bank_transfer') { // Moonpay only accepts EUR as a base currency for SEPA payments offers.moonpay.fiatCurrency = 'EUR'; + } else if (_paymentMethod === 'cash_app') { + // Moonpay only accepts USD as a base currency for Cash App payments + offers.moonpay.fiatCurrency = 'USD'; } offers.moonpay.fiatAmount = diff --git a/src/navigation/services/constants/PaymentMethodsConstants.ts b/src/navigation/services/constants/PaymentMethodsConstants.ts index 74b7a95d61..9c1087b069 100644 --- a/src/navigation/services/constants/PaymentMethodsConstants.ts +++ b/src/navigation/services/constants/PaymentMethodsConstants.ts @@ -51,3 +51,7 @@ export const countriesWithGBPTransfer = [ export const countriesWithPIX = [ 'BR', // Brazil ]; + +export const countriesWithCashApp = [ + 'US', // United States +]; diff --git a/src/navigation/services/screens/BuyAndSellRoot.tsx b/src/navigation/services/screens/BuyAndSellRoot.tsx index 7352ab10ae..cb768b78ce 100644 --- a/src/navigation/services/screens/BuyAndSellRoot.tsx +++ b/src/navigation/services/screens/BuyAndSellRoot.tsx @@ -102,7 +102,10 @@ import {RootState} from '../../../store'; import {createWalletAddress} from '../../../store/wallet/effects/address/address'; import {Analytics} from '../../../store/analytics/analytics.effects'; import uuid from 'react-native-uuid'; -import {PaymentMethod} from '../buy-crypto/constants/BuyCryptoConstants'; +import { + PaymentMethod, + PaymentMethodKey, +} from '../buy-crypto/constants/BuyCryptoConstants'; import { BanxaCreateOrderData, BanxaCreateOrderRequestData, @@ -216,7 +219,10 @@ import {simplexGetCurrencies} from '../../../store/buy-crypto/effects/simplex/si import {TouchableOpacity} from '../../../components/base/TouchableOpacity'; import _ from 'lodash'; import {startUpdateWalletStatus} from '../../../store/wallet/effects/status/status'; -import {WithdrawalMethod} from '../sell-crypto/constants/SellCryptoConstants'; +import { + WithdrawalMethod, + WithdrawalMethodKey, +} from '../sell-crypto/constants/SellCryptoConstants'; import {SellCryptoActions} from '../../../store/sell-crypto'; import {GetProtocolPrefixAddress} from '../../../store/wallet/utils/wallet'; import {useTheme} from 'styled-components/native'; @@ -470,6 +476,7 @@ export interface BuyAndSellRootProps { currencyAbbreviation?: string; // used from charts and deeplinks. chain?: string; // used from charts and deeplinks. partner?: BuyCryptoExchangeKey | undefined; // used from deeplinks. + paymentMethod?: PaymentMethodKey | WithdrawalMethodKey | undefined; // used from deeplinks. fromDeeplink?: boolean; reduceTopGap?: boolean; } @@ -612,6 +619,10 @@ const BuyAndSellRoot = ({ const preSetPartner = route.params?.partner?.toLowerCase() as | BuyCryptoExchangeKey | undefined; + const preSetPaymentMethod = route.params?.paymentMethod as + | PaymentMethodKey + | WithdrawalMethodKey + | undefined; const fromDeeplink = route.params?.fromDeeplink; const reduceTopGap = route.params?.reduceTopGap; @@ -4175,6 +4186,7 @@ const BuyAndSellRoot = ({ chain={selectedWallet?.chain || ''} country={country} preSetPartner={preSetPartner} + preSetPaymentMethod={preSetPaymentMethod} buyCryptoConfig={externalServicesConfig?.buyCrypto} sellCryptoConfig={externalServicesConfig?.sellCrypto} preLoadSellPartnersData={sellCryptoExchangesDefault} diff --git a/src/navigation/services/sell-crypto/constants/SellCryptoConstants.tsx b/src/navigation/services/sell-crypto/constants/SellCryptoConstants.tsx index 94fbebe28b..7bb7f55a96 100644 --- a/src/navigation/services/sell-crypto/constants/SellCryptoConstants.tsx +++ b/src/navigation/services/sell-crypto/constants/SellCryptoConstants.tsx @@ -2,6 +2,7 @@ import React from 'react'; import PaymentMethodIcon from '../../../../components/icons/payment-methods/payment-methods'; import { countriesWithACH, + countriesWithCashApp, countriesWithGBPTransfer, countriesWithSEPA, } from '../../constants/PaymentMethodsConstants'; @@ -9,6 +10,7 @@ import {SellCryptoExchangeKey} from '../utils/sell-crypto-utils'; export type WithdrawalMethodKey = | 'ach' + | 'cashApp' | 'creditCard' | 'debitCard' | 'sepaBankTransfer' @@ -201,6 +203,30 @@ export const WithdrawalMethodsAvailable: WithdrawalMethods = { }, enabled: true, }, + cashApp: { + order: 2.5, + label: 'Cash App Pay', + method: 'cashApp', + waitingTimeDescription: '< 10 mins', + imgLogo: ( + + ), + imgIcon: ( + + ), + supportedExchanges: { + moonpay: true, + ramp: false, + simplex: false, + }, + supportedCountries: countriesWithCashApp, + enabled: true, + }, venmo: { order: 3, label: 'Venmo', diff --git a/src/navigation/services/sell-crypto/utils/moonpay-sell-utils.ts b/src/navigation/services/sell-crypto/utils/moonpay-sell-utils.ts index b042f30a0a..3b0ebddaee 100644 --- a/src/navigation/services/sell-crypto/utils/moonpay-sell-utils.ts +++ b/src/navigation/services/sell-crypto/utils/moonpay-sell-utils.ts @@ -320,6 +320,9 @@ export const getMoonpaySellPayoutMethodFormat = ( case 'venmo': formattedPaymentMethod = 'venmo'; break; + case 'cashApp': + formattedPaymentMethod = 'cash_app'; + break; default: formattedPaymentMethod = undefined; break; @@ -347,6 +350,9 @@ export const getPayoutMethodKeyFromMoonpayType = ( case 'gbp_bank_transfer': formattedPaymentMethod = 'gbpBankTransfer'; break; + case 'cash_app': + formattedPaymentMethod = 'cashApp'; + break; default: formattedPaymentMethod = undefined; break; @@ -360,6 +366,7 @@ export const getMoonpayFiatListByPayoutMethod = ( let fiatList: string[]; switch (method) { case 'ach': + case 'cashApp': fiatList = ['USD']; break; case 'creditCard': diff --git a/src/navigation/services/sell-crypto/utils/sell-crypto-utils.ts b/src/navigation/services/sell-crypto/utils/sell-crypto-utils.ts index 5876cf5be4..054d4aeeb4 100644 --- a/src/navigation/services/sell-crypto/utils/sell-crypto-utils.ts +++ b/src/navigation/services/sell-crypto/utils/sell-crypto-utils.ts @@ -15,7 +15,14 @@ import { } from './simplex-sell-utils'; import pickBy from 'lodash.pickby'; import {LocationData} from '../../../../store/location/location.models'; -import {getExternalServiceSymbol} from '../../utils/external-services-utils'; +import { + PaymentMethodsConfig, + SellCryptoConfig, +} from '../../../../store/external-services/external-services.types'; +import { + getExternalServiceSymbol, + isPaymentMethodDisabledForPartner, +} from '../../utils/external-services-utils'; import { getRampSellSupportedCurrencies, rampSellSupportedFiatCurrencies, @@ -36,11 +43,14 @@ export const getSellEnabledPaymentMethods = ( locationCountry?: string, userCountry?: string, exchange?: SellCryptoExchangeKey | undefined, + sellCryptoConfig?: SellCryptoConfig, ): Partial => { if (!currency || !coin || !chain) { return {}; } + const paymentMethodsConfig = sellCryptoConfig?.paymentMethods; + const isPaymentMethodEnabled = ( paymentMethod: WithdrawalMethod, locationCountry: string | undefined, @@ -71,6 +81,12 @@ export const getSellEnabledPaymentMethods = ( userCountry, ); + WithdrawalMethodsAvailable.cashApp.enabled = isPaymentMethodEnabled( + WithdrawalMethodsAvailable.cashApp, + locationCountry, + undefined, // CashApp is only supported for located US. users, so we don't check userCountry here + ); + // Helper function to check if a payment method is supported by a specific exchange const isSupportedByExchange = ( exchange: SellCryptoExchangeKey, @@ -81,14 +97,20 @@ export const getSellEnabledPaymentMethods = ( locationCountry: string | undefined, userCountry: string | undefined, ) => { - return isWithdrawalMethodSupported( - exchange, - method, - coin, - chain, - currency, - locationCountry, - userCountry, + return ( + !isPaymentMethodDisabledForPartner( + sellCryptoConfig?.[exchange], + method.method, + ) && + isWithdrawalMethodSupported( + exchange, + method, + coin, + chain, + currency, + locationCountry, + userCountry, + ) ); }; @@ -150,6 +172,9 @@ export const getSellEnabledPaymentMethods = ( const EnabledWithdrawalMethods = pickBy( WithdrawalMethodsAvailable, method => { + if (paymentMethodsConfig?.[method.method]?.disabled) { + return false; + } return ( method.enabled && getSupportedExchanges( @@ -168,20 +193,28 @@ export const getSellEnabledPaymentMethods = ( return EnabledWithdrawalMethods; }; -export const getDefaultPaymentMethod = (country?: string): WithdrawalMethod => { +export const getDefaultPaymentMethod = ( + country?: string, + paymentMethodsConfig?: PaymentMethodsConfig, +): WithdrawalMethod => { + const isEnabledByConfig = (key: WithdrawalMethodKey) => + !paymentMethodsConfig?.[key]?.disabled; if (!country) { return WithdrawalMethodsAvailable.debitCard; } else if ( + isEnabledByConfig('ach') && WithdrawalMethodsAvailable.ach.supportedCountries?.includes(country) ) { return WithdrawalMethodsAvailable.ach; } else if ( + isEnabledByConfig('gbpBankTransfer') && WithdrawalMethodsAvailable.gbpBankTransfer.supportedCountries?.includes( country, ) ) { return WithdrawalMethodsAvailable.gbpBankTransfer; } else if ( + isEnabledByConfig('sepaBankTransfer') && WithdrawalMethodsAvailable.sepaBankTransfer.supportedCountries?.includes( country, ) diff --git a/src/navigation/services/utils/external-services-utils.ts b/src/navigation/services/utils/external-services-utils.ts index 89b32f3c18..86e97fff30 100644 --- a/src/navigation/services/utils/external-services-utils.ts +++ b/src/navigation/services/utils/external-services-utils.ts @@ -7,6 +7,19 @@ import { getCurrencyAbbreviation, } from '../../../utils/helper-methods'; import {t} from 'i18next'; +import { + ConfigPaymentMethodKey, + ExchangeConfig, +} from '../../../store/external-services/external-services.types'; + +export const isPaymentMethodDisabledForPartner = ( + exchangeConfig: ExchangeConfig | undefined, + method: ConfigPaymentMethodKey | undefined, +): boolean => { + return !!( + method && exchangeConfig?.config?.paymentMethods?.[method]?.disabled + ); +}; export const getExternalServiceSymbol = ( coin: string, diff --git a/src/store/buy-crypto/buy-crypto.models.ts b/src/store/buy-crypto/buy-crypto.models.ts index f31fd03989..e3e815be8a 100644 --- a/src/store/buy-crypto/buy-crypto.models.ts +++ b/src/store/buy-crypto/buy-crypto.models.ts @@ -273,6 +273,7 @@ export interface MoonpayGetCurrencyLimitsRequestData { export type MoonpayPaymentType = | 'venmo' | 'paypal' + | 'cash_app' | 'apple_pay' // applePay embedded flow | 'mobile_wallet' // applePay | 'sepa_bank_transfer' diff --git a/src/store/external-services/external-services.types.ts b/src/store/external-services/external-services.types.ts index e44a78db15..1ad9f61956 100644 --- a/src/store/external-services/external-services.types.ts +++ b/src/store/external-services/external-services.types.ts @@ -3,13 +3,17 @@ export type ExchangeConfig = { removed?: boolean; disabledTitle?: string; disabledMessage?: string; - config?: any; + config?: { + paymentMethods?: PaymentMethodsConfig; + [key: string]: any; // Other partner-specific configuration properties can be added here + }; }; export type BuyCryptoConfig = { disabled?: boolean; disabledTitle?: string; disabledMessage?: string; + paymentMethods?: PaymentMethodsConfig; banxa?: ExchangeConfig; moonpay?: ExchangeConfig; ramp?: ExchangeConfig; @@ -23,6 +27,7 @@ export type SellCryptoConfig = { disabled?: boolean; disabledTitle?: string; disabledMessage?: string; + paymentMethods?: PaymentMethodsConfig; moonpay?: ExchangeConfig; ramp?: ExchangeConfig; simplex?: ExchangeConfig; @@ -36,6 +41,29 @@ export type SwapCryptoConfig = { thorswap?: ExchangeConfig; }; +export type ConfigPaymentMethodKey = + | 'ach' + | 'applePay' + | 'cashApp' + | 'creditCard' + | 'debitCard' + | 'googlePay' + | 'sepaBankTransfer' + | 'gbpBankTransfer' + | 'other' + | 'paypal' + | 'pisp' + | 'pix' + | 'venmo'; + +export type PaymentMethodConfig = { + disabled?: boolean; +}; + +export type PaymentMethodsConfig = { + [key in ConfigPaymentMethodKey]?: PaymentMethodConfig; +}; + export type ExternalServicesConfig = { buyCrypto?: BuyCryptoConfig; sellCrypto?: SellCryptoConfig; diff --git a/src/store/scan/scan.effects.ts b/src/store/scan/scan.effects.ts index e9bd3fff64..e610db7fdc 100644 --- a/src/store/scan/scan.effects.ts +++ b/src/store/scan/scan.effects.ts @@ -121,7 +121,14 @@ import {logManager} from '../../managers/LogManager'; import {ongoingProcessManager} from '../../managers/OngoingProcessManager'; import {ExternalServicesScreens} from '../../navigation/services/ExternalServicesGroup'; import {BuyAndSellRootProps} from '../../navigation/services/screens/BuyAndSellRoot'; -import {PaymentMethodKey} from '../../navigation/services/buy-crypto/constants/BuyCryptoConstants'; +import { + PaymentMethodKey, + PaymentMethodsAvailable, +} from '../../navigation/services/buy-crypto/constants/BuyCryptoConstants'; +import { + WithdrawalMethodKey, + WithdrawalMethodsAvailable, +} from '../../navigation/services/sell-crypto/constants/SellCryptoConstants'; export const incomingData = ( @@ -1454,6 +1461,9 @@ const handleBuyCryptoUri = const amount = getParameterByName('amount', res); let coin = getParameterByName('coin', res)?.toLowerCase(); let chain = getParameterByName('chain', res)?.toLowerCase(); + let paymentMethod = + getParameterByName('paymentMethod', res) || + getParameterByName('payment-method', res); let _amount: number | undefined; if (amount) { @@ -1475,6 +1485,12 @@ const handleBuyCryptoUri = } } + if (paymentMethod) { + paymentMethod = PaymentMethodsAvailable[paymentMethod as PaymentMethodKey] + ? (paymentMethod as PaymentMethodKey) + : undefined; + } + dispatch( Analytics.track('Clicked Buy Crypto', { context: 'DeepLink', @@ -1497,6 +1513,7 @@ const handleBuyCryptoUri = amount: _amount, currencyAbbreviation: coin, chain, + paymentMethod, fromDeeplink: true, context: 'buyCrypto', } as BuyAndSellRootProps, @@ -1515,6 +1532,9 @@ const handleSellCryptoUri = const amount = getParameterByName('amount', res); let coin = getParameterByName('coin', res)?.toLowerCase(); let chain = getParameterByName('chain', res)?.toLowerCase(); + let paymentMethod = + getParameterByName('paymentMethod', res) || + getParameterByName('payment-method', res); let _amount: number | undefined; if (amount) { @@ -1536,6 +1556,14 @@ const handleSellCryptoUri = } } + if (paymentMethod) { + paymentMethod = WithdrawalMethodsAvailable[ + paymentMethod as WithdrawalMethodKey + ] + ? (paymentMethod as WithdrawalMethodKey) + : undefined; + } + dispatch( Analytics.track('Clicked Sell Crypto', { context: 'DeepLink', @@ -1558,6 +1586,7 @@ const handleSellCryptoUri = amount: _amount, currencyAbbreviation: coin, chain, + paymentMethod, fromDeeplink: true, context: 'sellCrypto', } as BuyAndSellRootProps, diff --git a/src/store/sell-crypto/models/moonpay-sell.models.ts b/src/store/sell-crypto/models/moonpay-sell.models.ts index 74940a1a39..0c74a641b7 100644 --- a/src/store/sell-crypto/models/moonpay-sell.models.ts +++ b/src/store/sell-crypto/models/moonpay-sell.models.ts @@ -93,7 +93,8 @@ export type MoonpayPayoutMethodType = | 'sepa_bank_transfer' | 'gbp_bank_transfer' | 'paypal' - | 'venmo'; + | 'venmo' + | 'cash_app'; export interface MoonpayGetSellQuoteRequestData { env: 'sandbox' | 'production';