From 9dd4c06525ed83625a4761a0b07637d609356885 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:48:50 -0400 Subject: [PATCH 1/2] catch errors creating stripe session and cancel purchase --- packages/common/src/models/Analytics.ts | 8 ++++++ .../src/services/audius-backend/stripe.ts | 8 +++--- packages/common/src/store/buy-usdc/sagas.ts | 25 ++++++++++++++++--- packages/common/src/store/buy-usdc/slice.ts | 8 ++++-- .../common/src/store/ui/stripe-modal/sagas.ts | 24 ++++++++++++------ .../common/src/store/ui/stripe-modal/slice.ts | 8 +++--- .../common/src/store/ui/stripe-modal/types.ts | 1 + 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/common/src/models/Analytics.ts b/packages/common/src/models/Analytics.ts index 0ad9fe553bd..0998aeb0a34 100644 --- a/packages/common/src/models/Analytics.ts +++ b/packages/common/src/models/Analytics.ts @@ -321,6 +321,7 @@ export enum Name { // Buy USDC BUY_USDC_ON_RAMP_OPENED = 'Buy USDC: On Ramp Opened', BUY_USDC_ON_RAMP_CANCELED = 'Buy USDC: On Ramp Canceled', + BUY_USDC_ON_RAMP_FAILURE = 'Buy USDC: On Ramp Failed', BUY_USDC_ON_RAMP_SUCCESS = 'Buy USDC: On Ramp Success', BUY_USDC_SUCCESS = 'Buy USDC: Success', BUY_USDC_FAILURE = 'Buy USDC: Failure', @@ -1559,6 +1560,12 @@ type BuyUSDCOnRampCanceled = { provider: string } +type BuyUSDCOnRampFailed = { + eventName: Name.BUY_USDC_ON_RAMP_FAILURE + error: string + provider: string +} + type BuyUSDCOnRampSuccess = { eventName: Name.BUY_USDC_ON_RAMP_SUCCESS provider: string @@ -1933,6 +1940,7 @@ export type AllTrackingEvents = | BuyUSDCOnRampOpened | BuyUSDCOnRampSuccess | BuyUSDCOnRampCanceled + | BuyUSDCOnRampFailed | BuyUSDCSuccess | BuyUSDCFailure | BuyUSDCRecoveryInProgress diff --git a/packages/common/src/services/audius-backend/stripe.ts b/packages/common/src/services/audius-backend/stripe.ts index a44f3e12de8..3e987ff71fe 100644 --- a/packages/common/src/services/audius-backend/stripe.ts +++ b/packages/common/src/services/audius-backend/stripe.ts @@ -10,7 +10,9 @@ export const createStripeSession = async ( audiusBackendInstance: AudiusBackend, config: CreateStripeSessionArgs ) => { - return ( - await audiusBackendInstance.getAudiusLibs() - ).identityService?.createStripeSession(config) + const libs = await audiusBackendInstance.getAudiusLibsTyped() + if (!libs.identityService) { + throw new Error('createStripeSession: Unexpected missing identity service') + } + return libs.identityService.createStripeSession(config) } diff --git a/packages/common/src/store/buy-usdc/sagas.ts b/packages/common/src/store/buy-usdc/sagas.ts index 5ad2556852e..679e99c1145 100644 --- a/packages/common/src/store/buy-usdc/sagas.ts +++ b/packages/common/src/store/buy-usdc/sagas.ts @@ -26,6 +26,7 @@ import { buyUSDCFlowFailed, buyUSDCFlowSucceeded, onrampCanceled, + onrampFailed, onrampOpened, purchaseStarted, onrampSucceeded, @@ -90,6 +91,7 @@ function* purchaseStep({ // Wait for on ramp finish const result = yield* race({ + failure: take(onrampFailed), success: take(onrampSucceeded), canceled: take(onrampCanceled) }) @@ -101,6 +103,21 @@ function* purchaseStep({ make({ eventName: Name.BUY_USDC_ON_RAMP_CANCELED, provider }) ) return {} + } else if (result.failure) { + const error = result.failure.payload?.error + ? result.failure.payload.error + : new Error('Unknown error') + + yield* call( + track, + make({ + eventName: Name.BUY_USDC_ON_RAMP_FAILURE, + provider, + error: error.message + }) + ) + // Throw up to the flow above this + throw error } yield* call( track, @@ -223,6 +240,7 @@ function* doBuyUSDC({ destinationCurrency: 'usdc', destinationWallet: rootAccount.publicKey.toString(), onrampCanceled, + onrampFailed, onrampSucceeded }) ) @@ -273,19 +291,20 @@ function* doBuyUSDC({ }) ) } catch (e) { + const error = e as Error yield* call(reportToSentry, { level: ErrorLevel.Error, - error: e as Error, + error, additionalInfo: { userBank } }) - yield* put(buyUSDCFlowFailed()) + yield* put(buyUSDCFlowFailed({ error })) yield* call( track, make({ eventName: Name.BUY_USDC_FAILURE, provider, requestedAmount: desiredAmount, - error: (e as Error).message + error: error.message }) ) } diff --git a/packages/common/src/store/buy-usdc/slice.ts b/packages/common/src/store/buy-usdc/slice.ts index 8d837da25e1..8eee70eb991 100644 --- a/packages/common/src/store/buy-usdc/slice.ts +++ b/packages/common/src/store/buy-usdc/slice.ts @@ -59,8 +59,11 @@ const slice = createSlice({ onrampSucceeded: (state) => { state.stage = BuyUSDCStage.CONFIRMING_PURCHASE }, - buyUSDCFlowFailed: (state) => { - state.error = new Error('USDC purchase failed') + onrampFailed: (state, action: PayloadAction<{ error: Error }>) => { + state.error = new Error(`Stripe onramp failed: ${action.payload}`) + }, + buyUSDCFlowFailed: (state, action: PayloadAction<{ error: Error }>) => { + state.error = action.payload.error }, buyUSDCFlowSucceeded: (state) => { state.stage = BuyUSDCStage.FINISH @@ -91,6 +94,7 @@ export const { purchaseStarted, onrampSucceeded, onrampCanceled, + onrampFailed, stripeSessionStatusChanged, startRecoveryIfNecessary, recoveryStatusChanged, diff --git a/packages/common/src/store/ui/stripe-modal/sagas.ts b/packages/common/src/store/ui/stripe-modal/sagas.ts index 820d16c3a29..20c91343bb0 100644 --- a/packages/common/src/store/ui/stripe-modal/sagas.ts +++ b/packages/common/src/store/ui/stripe-modal/sagas.ts @@ -17,13 +17,23 @@ function* handleInitializeStripeModal({ payload: { amount, destinationCurrency, destinationWallet } }: ReturnType) { const audiusBackendInstance = yield* getContext('audiusBackendInstance') - const res = yield* call(createStripeSession, audiusBackendInstance, { - amount, - destinationCurrency, - destinationWallet - }) - // TODO: Need to handle errors here? - yield* put(stripeSessionCreated({ clientSecret: res.client_secret })) + const { onrampFailed } = yield* select(getStripeModalState) + try { + const res = yield* call(createStripeSession, audiusBackendInstance, { + amount, + destinationCurrency, + destinationWallet + }) + yield* put(stripeSessionCreated({ clientSecret: res.client_secret })) + } catch (e) { + // TODO: When we have better error messages from identity, we should extract them here so + // they make it into analytics. + // https://linear.app/audius/issue/PAY-2041/[usdc]-we-should-pipe-the-stripe-session-creation-error-back-from + if (onrampFailed) { + yield* put({ type: onrampFailed.type, payload: { error: e } }) + } + yield* put(setVisibility({ modal: 'StripeOnRamp', visible: 'closing' })) + } } function* handleStripeSessionChanged({ diff --git a/packages/common/src/store/ui/stripe-modal/slice.ts b/packages/common/src/store/ui/stripe-modal/slice.ts index 5cefbb1663b..99b52044f2a 100644 --- a/packages/common/src/store/ui/stripe-modal/slice.ts +++ b/packages/common/src/store/ui/stripe-modal/slice.ts @@ -1,4 +1,4 @@ -import { Action, createSlice, PayloadAction } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { StripeSessionStatus, @@ -10,8 +10,9 @@ type InitializeStripeModalPayload = { amount: string destinationCurrency: StripeDestinationCurrencyType destinationWallet: string - onrampSucceeded: Action - onrampCanceled: Action + onrampFailed: StripeModalState['onrampFailed'] + onrampSucceeded: StripeModalState['onrampSucceeded'] + onrampCanceled: StripeModalState['onrampCanceled'] } const initialState: StripeModalState = {} @@ -25,6 +26,7 @@ const slice = createSlice({ action: PayloadAction ) => { state.stripeSessionStatus = 'initialized' + state.onrampFailed = action.payload.onrampFailed state.onrampSucceeded = action.payload.onrampSucceeded state.onrampCanceled = action.payload.onrampCanceled }, diff --git a/packages/common/src/store/ui/stripe-modal/types.ts b/packages/common/src/store/ui/stripe-modal/types.ts index bc93a58ca53..0c4ec79253a 100644 --- a/packages/common/src/store/ui/stripe-modal/types.ts +++ b/packages/common/src/store/ui/stripe-modal/types.ts @@ -12,6 +12,7 @@ export type StripeDestinationCurrencyType = 'sol' | 'usdc' export type StripeModalState = { onrampSucceeded?: Action onrampCanceled?: Action + onrampFailed?: Action stripeSessionStatus?: StripeSessionStatus stripeClientSecret?: string } From 02e1f784de6b5a7c8a7cc6ea5510a05b5b2cae99 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:08:04 -0400 Subject: [PATCH 2/2] cancel audio flow if stripe onramp fails --- .../buy-audio-modal/components/StripeBuyAudioButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/components/buy-audio-modal/components/StripeBuyAudioButton.tsx b/packages/web/src/components/buy-audio-modal/components/StripeBuyAudioButton.tsx index d01604d506c..c3326635045 100644 --- a/packages/web/src/components/buy-audio-modal/components/StripeBuyAudioButton.tsx +++ b/packages/web/src/components/buy-audio-modal/components/StripeBuyAudioButton.tsx @@ -49,6 +49,7 @@ export const StripeBuyAudioButton = () => { amount, onrampSucceeded, onrampCanceled, + onrampFailed: onrampCanceled, destinationCurrency: 'sol', destinationWallet })