From 659819ae86de6780ca5f09c5807aa206249fda13 Mon Sep 17 00:00:00 2001 From: Roman Bodavsky Date: Wed, 18 Mar 2026 14:28:53 +0300 Subject: [PATCH] backend/fix: payment mode flag added in stripe api --- .../src/Kernel/External/Payment/Interface.hs | 4 +- .../External/Payment/Interface/Juspay.hs | 6 +- .../External/Payment/Interface/Stripe.hs | 107 +++++++++++++++++- .../External/Payment/Interface/Types.hs | 11 +- .../External/Payment/Stripe/Types/Customer.hs | 8 +- .../Payment/Stripe/Types/SetupIntent.hs | 3 +- 6 files changed, 123 insertions(+), 16 deletions(-) diff --git a/lib/mobility-core/src/Kernel/External/Payment/Interface.hs b/lib/mobility-core/src/Kernel/External/Payment/Interface.hs index 7c106ab84..8fbb1597a 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Interface.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Interface.hs @@ -262,7 +262,7 @@ getCustomer :: m CreateCustomerResp getCustomer config customerId = case config of JuspayConfig cfg -> Juspay.getCustomer cfg customerId - StripeConfig _ -> throwError $ InternalError "Stripe Get Customer not supported." + StripeConfig cfg -> Stripe.getCustomer cfg customerId PaytmEDCConfig _ -> throwError $ InternalError "PaytmEDC Get Customer not supported." createEphemeralKeys :: @@ -273,7 +273,7 @@ createEphemeralKeys :: ) => PaymentServiceConfig -> CustomerId -> - m Text + m CreateEphemeralKeysResp createEphemeralKeys config customerId = case config of JuspayConfig _ -> throwError $ InternalError "Juspay Create Ephemeral Keys not supported." StripeConfig cfg -> Stripe.createEphemeralKeys cfg customerId diff --git a/lib/mobility-core/src/Kernel/External/Payment/Interface/Juspay.hs b/lib/mobility-core/src/Kernel/External/Payment/Interface/Juspay.hs index 40c6c5569..c5b69df03 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Interface/Juspay.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Interface/Juspay.hs @@ -150,7 +150,8 @@ createCustomer config req = do CreateCustomerResp { customerId = object_reference_id, clientAuthToken = Customer.client_auth_token <$> juspay, - clientAuthTokenExpiry = Customer.client_auth_token_expiry <$> juspay + clientAuthTokenExpiry = Customer.client_auth_token_expiry <$> juspay, + isLiveMode = Nothing } getCustomer :: @@ -173,7 +174,8 @@ getCustomer config customerId = do CreateCustomerResp { customerId = object_reference_id, clientAuthToken = Customer.client_auth_token <$> juspay, - clientAuthTokenExpiry = Customer.client_auth_token_expiry <$> juspay + clientAuthTokenExpiry = Customer.client_auth_token_expiry <$> juspay, + isLiveMode = Nothing } mkGetCustomerReq = Juspay.GetCustomerReq diff --git a/lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs b/lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs index 44c33cc28..c1616234f 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs @@ -5,6 +5,7 @@ module Kernel.External.Payment.Interface.Stripe where import Control.Applicative ((<|>)) +import qualified Data.Text as T import Data.Time import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Kernel.External.Encryption @@ -35,6 +36,8 @@ createIndividualConnectAccount :: createIndividualConnectAccount config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createIndividualConnectAccount" apiKeyServiceMode config.serviceMode let accountReq = mkAccountReq accountResp <- Stripe.createAccount url apiKey accountReq let accountId = accountResp.id @@ -125,6 +128,8 @@ retryAccountLink :: retryAccountLink config accountId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "retryAccountLink" apiKeyServiceMode config.serviceMode let accountLinkReq = mkAccountLinkReq config accountId accountLinkResp <- Stripe.createAccountLink url apiKey accountLinkReq let accountUrlExpiry = posixSecondsToUTCTime accountLinkResp.expires_at @@ -143,6 +148,8 @@ getAccount :: getAccount config accountId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getAccount" apiKeyServiceMode config.serviceMode accountResp <- Stripe.getAccount url apiKey accountId let chargesEnabled = accountResp.charges_enabled let detailsSubmitted = accountResp.details_submitted @@ -160,11 +167,14 @@ createCustomer :: createCustomer config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createCustomer" apiKeyServiceMode config.serviceMode let customerReq = mkCustomerReq req customerResp <- Stripe.createCustomer url apiKey customerReq let customerId = customerResp.id - let clientAuthToken = Nothing - let clientAuthTokenExpiry = Nothing + clientAuthToken = Nothing + clientAuthTokenExpiry = Nothing + isLiveMode = customerResp.livemode return $ CreateCustomerResp {..} where mkCustomerReq :: CreateCustomerReq -> Stripe.CustomerReq @@ -177,6 +187,26 @@ createCustomer config req = do phone = phone } +getCustomer :: + ( Metrics.CoreMetrics m, + EncFlow m r, + HasRequestId r, + MonadReader r m + ) => + StripeCfg -> + CustomerId -> + m CreateCustomerResp +getCustomer config customerId = do + let url = config.url + apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getCustomer" apiKeyServiceMode config.serviceMode + customerResp <- Stripe.getCustomer url apiKey customerId + let clientAuthToken = Nothing + clientAuthTokenExpiry = Nothing + isLiveMode = customerResp.livemode + return $ CreateCustomerResp {customerId = customerResp.id, ..} + createEphemeralKeys :: ( Metrics.CoreMetrics m, EncFlow m r, @@ -185,13 +215,19 @@ createEphemeralKeys :: ) => StripeCfg -> CustomerId -> - m Text + m CreateEphemeralKeysResp createEphemeralKeys config customerId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createEphemeralKeys" apiKeyServiceMode config.serviceMode let ephemeralKeysReq = Stripe.EphemeralKeysReq {customer = customerId} ephemeralKeysResp <- Stripe.createEphemeralKeys url apiKey ephemeralKeysReq - return ephemeralKeysResp.secret + return + CreateEphemeralKeysResp + { ephemeralKeySecret = ephemeralKeysResp.secret, + isLiveMode = ephemeralKeysResp.livemode + } getCardList :: ( Metrics.CoreMetrics m, @@ -205,6 +241,8 @@ getCardList :: getCardList config customerId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getCardList" apiKeyServiceMode config.serviceMode paymentMethodListResp <- Stripe.getPaymentMethodList url apiKey customerId let cards = map mkCard paymentMethodListResp._data return cards @@ -232,6 +270,8 @@ deleteCard :: deleteCard config paymentMethodId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "deleteCard" apiKeyServiceMode config.serviceMode void $ Stripe.detachPaymentMethod url apiKey paymentMethodId createPaymentIntent :: @@ -246,6 +286,8 @@ createPaymentIntent :: createPaymentIntent config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createPaymentIntent" apiKeyServiceMode config.serviceMode case config.chargeDestination of -- Platform receives payment, transfers to driver (Destination Charges) Platform -> createPlatformCharge url apiKey req @@ -323,11 +365,14 @@ createSetupIntent :: createSetupIntent config customerId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createSetupIntent" apiKeyServiceMode config.serviceMode let setupIntentReq = mkSetupIntentReq setupIntentResp <- Stripe.createSetupIntent url apiKey setupIntentReq let setupIntentId = setupIntentResp.id - let clientSecret = setupIntentResp.client_secret - let status = setupIntentResp.status + clientSecret = setupIntentResp.client_secret + status = setupIntentResp.status + isLiveMode = setupIntentResp.livemode return $ CreateSetupIntentResp {..} where mkSetupIntentReq :: Stripe.SetupIntentReq @@ -353,6 +398,8 @@ cancelPaymentIntent :: cancelPaymentIntent config paymentIntentId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "cancelPaymentIntent" apiKeyServiceMode config.serviceMode paymentIntentResp <- Stripe.cancelPaymentIntent url apiKey paymentIntentId let clientSecret = paymentIntentResp.client_secret let status = paymentIntentResp.status @@ -371,6 +418,8 @@ updatePaymentMethodInIntent :: updatePaymentMethodInIntent config paymentIntentId paymentMethodId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "updatePaymentMethodInIntent" apiKeyServiceMode config.serviceMode let confirmPaymentIntentReq = Stripe.ConfirmPaymentIntentReq {payment_method = paymentMethodId} void $ Stripe.confirmPaymentIntent url apiKey paymentIntentId confirmPaymentIntentReq @@ -387,6 +436,8 @@ getCard :: getCard config paymentMethodId customerId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getCard" apiKeyServiceMode config.serviceMode cardObjectResp <- Stripe.getCard url apiKey customerId paymentMethodId let card = mkCard cardObjectResp return card @@ -414,6 +465,8 @@ getPaymentIntent :: getPaymentIntent config paymentIntentId = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getPaymentIntent" apiKeyServiceMode config.serviceMode paymentIntentResp <- Stripe.getPaymentIntent url apiKey paymentIntentId let clientSecret = paymentIntentResp.client_secret let status = paymentIntentResp.status @@ -433,6 +486,8 @@ capturePaymentIntent :: capturePaymentIntent config paymentIntentId amount applicationFeeAmount = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "capturePaymentIntent" apiKeyServiceMode config.serviceMode let amount_to_capture = usdToCents amount let application_fee_amount = usdToCents applicationFeeAmount let req = Stripe.CapturePaymentIntentReq {..} @@ -452,6 +507,8 @@ updateAmountInPaymentIntent :: updateAmountInPaymentIntent config paymentIntentId amount_ applicationFeeAmount = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "updateAmountInPaymentIntent" apiKeyServiceMode config.serviceMode let amount = usdToCents amount_ let application_fee_amount = usdToCents applicationFeeAmount let req = Stripe.IncrementAuthorizationReq {..} @@ -649,6 +706,8 @@ createRefund :: createRefund config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "createRefund" apiKeyServiceMode config.serviceMode case config.chargeDestination of -- Platform receives payment, transfers to driver (Destination Charges) Platform -> createPlatformRefund url apiKey @@ -702,6 +761,8 @@ getRefund :: getRefund config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "getRefund" apiKeyServiceMode config.serviceMode case config.chargeDestination of Platform -> mkGetRefundResp <$> Stripe.getRefund url apiKey Nothing req.id ConnectedAccount -> mkGetRefundResp <$> Stripe.getRefund url apiKey (Just req.driverAccountId) req.id @@ -718,6 +779,8 @@ cancelRefund :: cancelRefund config req = do let url = config.url apiKey <- decrypt config.apiKey + let apiKeyServiceMode = getServiceModeFromApiKey apiKey + logServiceModeMismatch "cancelRefund" apiKeyServiceMode config.serviceMode case config.chargeDestination of Platform -> mkGetRefundResp <$> Stripe.cancelRefund url apiKey Nothing req.id ConnectedAccount -> mkGetRefundResp <$> Stripe.cancelRefund url apiKey (Just req.driverAccountId) req.id @@ -736,3 +799,35 @@ mkGetRefundResp Stripe.RefundObject {..} = reverseTransferId = transfer_reversal, errorCode = failure_reason } + +-- Debug payment mode mistmatch (do not expose any credentials) +logServiceModeMismatch :: + Log m => + Text -> + Maybe ServiceMode -> + Maybe ServiceMode -> + m () +logServiceModeMismatch action apiKeyServiceMode cfgServiceMode = do + if (Just (fromMaybe Live cfgServiceMode) == apiKeyServiceMode) + then do + logInfo $ + "Stripe service api call:" + <> action + <> "; service mode: " + <> show apiKeyServiceMode + else do + logWarning $ + "Payment mode mismatch while Stripe service api call: " + <> action + <> "; api key service mode: " + <> show apiKeyServiceMode + <> "; cfg service mode: " + <> show cfgServiceMode + +getServiceModeFromApiKey :: Text -> Maybe ServiceMode +getServiceModeFromApiKey apiKey + | "sk_live_" `T.isPrefixOf` apiKey = Just Live + | "sk_test_" `T.isPrefixOf` apiKey = Just Test + | "rk_live_" `T.isPrefixOf` apiKey = Just Live -- restricted key + | "rk_test_" `T.isPrefixOf` apiKey = Just Test -- restricted key + | otherwise = Nothing diff --git a/lib/mobility-core/src/Kernel/External/Payment/Interface/Types.hs b/lib/mobility-core/src/Kernel/External/Payment/Interface/Types.hs index a547f3eb1..70b32a201 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Interface/Types.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Interface/Types.hs @@ -679,11 +679,17 @@ data CreateCustomerReq = CreateCustomerReq data CreateCustomerResp = CreateCustomerResp { customerId :: CustomerId, clientAuthToken :: Maybe Text, - clientAuthTokenExpiry :: Maybe UTCTime + clientAuthTokenExpiry :: Maybe UTCTime, + isLiveMode :: Maybe Bool } deriving stock (Show, Eq, Generic) deriving anyclass (FromJSON, ToJSON, ToSchema) +data CreateEphemeralKeysResp = CreateEphemeralKeysResp + { ephemeralKeySecret :: Text, + isLiveMode :: Maybe Bool + } + data OrderUpdateReq = OrderUpdateReq { amount :: HighPrecMoney, orderShortId :: Text, @@ -723,7 +729,8 @@ data CreatePaymentIntentResp = CreatePaymentIntentResp data CreateSetupIntentResp = CreateSetupIntentResp { setupIntentId :: SetupIntentId, clientSecret :: Text, - status :: PaymentIntentStatus + status :: PaymentIntentStatus, + isLiveMode :: Maybe Bool } deriving stock (Show, Eq, Generic) deriving anyclass (FromJSON, ToJSON, ToSchema) diff --git a/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/Customer.hs b/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/Customer.hs index 576dcde32..37a388bd9 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/Customer.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/Customer.hs @@ -70,7 +70,8 @@ data CustomerObject = CustomerObject email :: Maybe Text, name :: Maybe Text, default_source :: Maybe Text, - phone :: Maybe Text + phone :: Maybe Text, + livemode :: Maybe Bool } deriving stock (Show, Eq, Generic, Read) deriving anyclass (FromJSON, ToJSON, ToSchema) @@ -83,8 +84,9 @@ newtype EphemeralKeysReq = EphemeralKeysReq instance ToForm EphemeralKeysReq -newtype EphemeralKeysResp = EphemeralKeysResp - { secret :: Text +data EphemeralKeysResp = EphemeralKeysResp + { secret :: Text, + livemode :: Maybe Bool } deriving stock (Show, Eq, Generic, Read) deriving anyclass (FromJSON, ToJSON, ToSchema) diff --git a/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/SetupIntent.hs b/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/SetupIntent.hs index f557fe5f1..538aa7a50 100644 --- a/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/SetupIntent.hs +++ b/lib/mobility-core/src/Kernel/External/Payment/Stripe/Types/SetupIntent.hs @@ -58,7 +58,8 @@ data SetupIntentObject = SetupIntentObject confirm :: Maybe Bool, customer :: Maybe CustomerId, description :: Maybe Text, - payment_method :: Maybe Text + payment_method :: Maybe Text, + livemode :: Maybe Bool } deriving stock (Show, Eq, Generic, Read) deriving anyclass (FromJSON, ToJSON, ToSchema)