From a658a62c5ded9465b01dd0e22f44dcbc699b06a3 Mon Sep 17 00:00:00 2001 From: Iveta Date: Mon, 20 Nov 2023 16:32:23 -0500 Subject: [PATCH 1/3] SEP-6: update KYC flow --- .../src/components/Sep6/Sep6Deposit.tsx | 77 ++++++----- .../src/ducks/sep6DepositAsset.ts | 129 +++++++++++++++--- .../demo-wallet-client/src/types/types.ts | 3 + packages/demo-wallet-shared/types/types.ts | 1 + 4 files changed, 157 insertions(+), 53 deletions(-) diff --git a/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx b/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx index e2d29b6f..6271f90f 100644 --- a/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx +++ b/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx @@ -167,6 +167,52 @@ export const Sep6Deposit = () => { return `Min: ${minAmount} | Max: ${maxAmount}`; }; + if (sep6DepositAsset.status === ActionStatus.NEEDS_KYC) { + return ( + + SEP-6 Customer Info + + {Object.keys(sep6DepositAsset.data.customerFields).length ? ( + + + These are the fields the receiving anchor requires. The + sending client obtains them from the /customer endpoint.{" "} + + Learn more + + + } + isInline + tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM} + > + <>SEP-12 Required Info + + + ) : null} +
+ {Object.entries(sep6DepositAsset.data.customerFields || {}).map( + ([id, input]) => ( + + ), + )} +
+
+ + + + +
+ ); + } + if (sep6DepositAsset.status === ActionStatus.NEEDS_INPUT) { if ( sep6DepositAsset.data.requiredCustomerInfoUpdates && @@ -279,37 +325,6 @@ export const Sep6Deposit = () => { ), )} - - {Object.keys(sep6DepositAsset.data.customerFields).length ? ( - - - These are the fields the receiving anchor requires. The - sending client obtains them from the /customer endpoint.{" "} - - Learn more - - - } - isInline - tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM} - > - <>SEP-12 Required Info - - - ) : null} -
- {Object.entries(sep6DepositAsset.data.customerFields || {}).map( - ([id, input]) => ( - - ), - )} -
diff --git a/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts b/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts index 12206482..865033d5 100644 --- a/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts +++ b/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts @@ -142,29 +142,11 @@ export const initiateDepositAction = createAsyncThunk< signedChallengeTransaction, }); - // Get SEP-12 fields - log.instruction({ - title: "Making GET `/customer` request for user", - }); - - const sep12Fields = await collectSep12Fields({ - publicKey, - token, - kycServer: webAuthTomlResponse.KYC_SERVER, - }); - payload = { ...payload, kycServer: webAuthTomlResponse.KYC_SERVER, token, }; - - if (sep12Fields) { - payload = { - ...payload, - customerFields: { ...payload.customerFields, ...sep12Fields }, - }; - } } return payload; @@ -183,6 +165,48 @@ export const initiateDepositAction = createAsyncThunk< }, ); +export const getKycFieldsAction = createAsyncThunk< + { [key: string]: AnyObject }, + undefined, + { rejectValue: RejectMessage; state: RootState } +>( + "sep6DepositAsset/getKycFieldsAction", + async (_, { rejectWithValue, getState }) => { + const { data } = accountSelector(getState()); + const publicKey = data?.id; + const { token, kycServer } = sep6DepositSelector(getState()).data; + + // This is unlikely + if (!publicKey) { + throw new Error("Something is wrong with Account, no public key."); + } + + try { + // Get SEP-12 fields + log.instruction({ + title: "Making GET `/customer` request for user", + }); + + return await collectSep12Fields({ + publicKey, + token, + kycServer, + }); + } catch (error) { + const errorMessage = getErrorMessage(error); + + log.error({ + title: "SEP-6 KYC fields failed", + body: errorMessage, + }); + + return rejectWithValue({ + errorString: errorMessage, + }); + } + }, +); + export const submitSep6DepositFields = createAsyncThunk< { status: ActionStatus; depositResponse: Sep6DepositResponse }, { @@ -196,12 +220,12 @@ export const submitSep6DepositFields = createAsyncThunk< "sep6DepositAsset/submitSep6DepositFields", async ( { amount, depositType, customerFields, infoFields }, - { rejectWithValue, getState }, + { rejectWithValue, getState, dispatch }, ) => { try { const { data } = accountSelector(getState()); - const { claimableBalanceSupported } = settingsSelector(getState()); const publicKey = data?.id || ""; + const { claimableBalanceSupported } = settingsSelector(getState()); const { secretKey } = accountSelector(getState()); const { data: sep6Data } = sep6DepositSelector(getState()); @@ -227,6 +251,22 @@ export const submitSep6DepositFields = createAsyncThunk< claimableBalanceSupported, })) as Sep6DepositResponse; + if ( + depositResponse.type === + TransactionStatus.NON_INTERACTIVE_CUSTOMER_INFO_NEEDED + ) { + log.instruction({ + title: "Anchor requires additional customer information (KYC)", + }); + + dispatch(getKycFieldsAction()); + + return { + status: ActionStatus.NEEDS_KYC, + depositResponse, + }; + } + return { status: ActionStatus.CAN_PROCEED, depositResponse, @@ -290,6 +330,7 @@ export const sep6DepositAction = createAsyncThunk< status: ActionStatus; trustedAssetAdded: string; requiredCustomerInfoUpdates: string[] | undefined; + customerFields?: { [key: string]: AnyObject }; }, undefined, { rejectValue: RejectMessage; state: RootState } @@ -297,7 +338,7 @@ export const sep6DepositAction = createAsyncThunk< "sep6DepositAsset/sep6DepositAction", async (_, { rejectWithValue, getState, dispatch }) => { try { - const { secretKey } = accountSelector(getState()); + const { secretKey, data } = accountSelector(getState()); const networkConfig = getNetworkConfig(); const { data: sep6Data } = sep6DepositSelector(getState()); @@ -307,6 +348,7 @@ export const sep6DepositAction = createAsyncThunk< depositResponse, transferServerUrl, token, + kycServer, } = sep6Data; const trustAssetCallback = async () => { @@ -340,6 +382,22 @@ export const sep6DepositAction = createAsyncThunk< dispatch(updateInstructionsAction(instructions)), }); + let customerFields; + + // Need to get KYC fields to get field info + if (currentStatus === TransactionStatus.PENDING_CUSTOMER_INFO_UPDATE) { + // Get SEP-12 fields + log.instruction({ + title: "Making GET `/customer` request for user", + }); + + customerFields = await collectSep12Fields({ + publicKey: data?.id!, + token, + kycServer, + }); + } + return { currentStatus, status: @@ -348,6 +406,7 @@ export const sep6DepositAction = createAsyncThunk< : ActionStatus.SUCCESS, trustedAssetAdded, requiredCustomerInfoUpdates, + customerFields, }; } catch (error) { const errorMessage = getErrorMessage(error); @@ -425,6 +484,23 @@ const sep6DepositAssetSlice = createSlice({ state.status = ActionStatus.ERROR; }); + builder.addCase(getKycFieldsAction.pending, (state) => { + state.errorString = undefined; + state.status = ActionStatus.PENDING; + }); + builder.addCase(getKycFieldsAction.fulfilled, (state, action) => { + state.data.customerFields = action.payload.customerFields; + + // Trigger KYC field input modal + if (action.payload.requestKycInput) { + state.status = ActionStatus.NEEDS_KYC; + } + }); + builder.addCase(getKycFieldsAction.rejected, (state, action) => { + state.errorString = action.payload?.errorString; + state.status = ActionStatus.ERROR; + }); + builder.addCase(submitSep6CustomerInfoFields.pending, (state) => { state.errorString = undefined; state.status = ActionStatus.PENDING; @@ -444,11 +520,20 @@ const sep6DepositAssetSlice = createSlice({ state.status = action.payload.status; state.data.currentStatus = action.payload.currentStatus; state.data.trustedAssetAdded = action.payload.trustedAssetAdded; + + const customerFields = Object.keys(state.data.customerFields).length + ? state.data.customerFields + : action.payload.customerFields; + state.data.requiredCustomerInfoUpdates = action.payload.requiredCustomerInfoUpdates?.map((field) => ({ - ...state.data.customerFields[field], + ...customerFields?.[field], id: field, })); + + if (action.payload.customerFields) { + state.data.customerFields = action.payload.customerFields; + } }); builder.addCase(sep6DepositAction.rejected, (state, action) => { state.errorString = action.payload?.errorString; diff --git a/packages/demo-wallet-client/src/types/types.ts b/packages/demo-wallet-client/src/types/types.ts index bddd2257..1f49fd6a 100644 --- a/packages/demo-wallet-client/src/types/types.ts +++ b/packages/demo-wallet-client/src/types/types.ts @@ -197,6 +197,7 @@ export interface Sep6DepositResponse { fee_fixed?: number; fee_percent?: number; extra_info?: { message?: string }; + type?: string; /* eslint-enable camelcase */ } @@ -390,6 +391,7 @@ export enum ActionStatus { PENDING = "PENDING", SUCCESS = "SUCCESS", NEEDS_INPUT = "NEEDS_INPUT", + NEEDS_KYC = "NEEDS_KYC", CAN_PROCEED = "CAN_PROCEED", ANCHOR_QUOTES = "ANCHOR_QUOTES", } @@ -449,6 +451,7 @@ export enum TransactionStatus { COMPLETED = "completed", ERROR = "error", INCOMPLETE = "incomplete", + NON_INTERACTIVE_CUSTOMER_INFO_NEEDED = "non_interactive_customer_info_needed", PENDING_ANCHOR = "pending_anchor", PENDING_CUSTOMER_INFO_UPDATE = "pending_customer_info_update", PENDING_EXTERNAL = "pending_external", diff --git a/packages/demo-wallet-shared/types/types.ts b/packages/demo-wallet-shared/types/types.ts index 665b0801..0b87bab9 100644 --- a/packages/demo-wallet-shared/types/types.ts +++ b/packages/demo-wallet-shared/types/types.ts @@ -439,6 +439,7 @@ export enum TransactionStatus { COMPLETED = "completed", ERROR = "error", INCOMPLETE = "incomplete", + NON_INTERACTIVE_CUSTOMER_INFO_NEEDED = "non_interactive_customer_info_needed", PENDING_ANCHOR = "pending_anchor", PENDING_CUSTOMER_INFO_UPDATE = "pending_customer_info_update", PENDING_EXTERNAL = "pending_external", From 87baace3d4a7b7e76177961041d76fcaae2fa33c Mon Sep 17 00:00:00 2001 From: Iveta Date: Mon, 20 Nov 2023 17:13:51 -0500 Subject: [PATCH 2/3] Cleanup --- .../src/ducks/sep6DepositAsset.ts | 94 ++++++------------- 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts b/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts index 865033d5..62b1f99d 100644 --- a/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts +++ b/packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts @@ -165,50 +165,12 @@ export const initiateDepositAction = createAsyncThunk< }, ); -export const getKycFieldsAction = createAsyncThunk< - { [key: string]: AnyObject }, - undefined, - { rejectValue: RejectMessage; state: RootState } ->( - "sep6DepositAsset/getKycFieldsAction", - async (_, { rejectWithValue, getState }) => { - const { data } = accountSelector(getState()); - const publicKey = data?.id; - const { token, kycServer } = sep6DepositSelector(getState()).data; - - // This is unlikely - if (!publicKey) { - throw new Error("Something is wrong with Account, no public key."); - } - - try { - // Get SEP-12 fields - log.instruction({ - title: "Making GET `/customer` request for user", - }); - - return await collectSep12Fields({ - publicKey, - token, - kycServer, - }); - } catch (error) { - const errorMessage = getErrorMessage(error); - - log.error({ - title: "SEP-6 KYC fields failed", - body: errorMessage, - }); - - return rejectWithValue({ - errorString: errorMessage, - }); - } - }, -); - export const submitSep6DepositFields = createAsyncThunk< - { status: ActionStatus; depositResponse: Sep6DepositResponse }, + { + status: ActionStatus; + depositResponse: Sep6DepositResponse; + customerFields?: AnyObject; + }, { amount?: string; depositType: AnyObject; @@ -220,7 +182,7 @@ export const submitSep6DepositFields = createAsyncThunk< "sep6DepositAsset/submitSep6DepositFields", async ( { amount, depositType, customerFields, infoFields }, - { rejectWithValue, getState, dispatch }, + { rejectWithValue, getState }, ) => { try { const { data } = accountSelector(getState()); @@ -259,11 +221,21 @@ export const submitSep6DepositFields = createAsyncThunk< title: "Anchor requires additional customer information (KYC)", }); - dispatch(getKycFieldsAction()); + // Get SEP-12 fields + log.instruction({ + title: "Making GET `/customer` request for user", + }); + + const customerFields = await collectSep12Fields({ + publicKey: data?.id!, + token, + kycServer, + }); return { status: ActionStatus.NEEDS_KYC, depositResponse, + customerFields, }; } @@ -478,29 +450,16 @@ const sep6DepositAssetSlice = createSlice({ builder.addCase(submitSep6DepositFields.fulfilled, (state, action) => { state.status = action.payload.status; state.data.depositResponse = action.payload.depositResponse; + state.data.customerFields = { + ...state.data.customerFields, + ...action.payload.customerFields, + }; }); builder.addCase(submitSep6DepositFields.rejected, (state, action) => { state.errorString = action.payload?.errorString; state.status = ActionStatus.ERROR; }); - builder.addCase(getKycFieldsAction.pending, (state) => { - state.errorString = undefined; - state.status = ActionStatus.PENDING; - }); - builder.addCase(getKycFieldsAction.fulfilled, (state, action) => { - state.data.customerFields = action.payload.customerFields; - - // Trigger KYC field input modal - if (action.payload.requestKycInput) { - state.status = ActionStatus.NEEDS_KYC; - } - }); - builder.addCase(getKycFieldsAction.rejected, (state, action) => { - state.errorString = action.payload?.errorString; - state.status = ActionStatus.ERROR; - }); - builder.addCase(submitSep6CustomerInfoFields.pending, (state) => { state.errorString = undefined; state.status = ActionStatus.PENDING; @@ -521,18 +480,19 @@ const sep6DepositAssetSlice = createSlice({ state.data.currentStatus = action.payload.currentStatus; state.data.trustedAssetAdded = action.payload.trustedAssetAdded; - const customerFields = Object.keys(state.data.customerFields).length - ? state.data.customerFields - : action.payload.customerFields; + const customerFields = { + ...state.data.customerFields, + ...action.payload.customerFields, + }; state.data.requiredCustomerInfoUpdates = action.payload.requiredCustomerInfoUpdates?.map((field) => ({ - ...customerFields?.[field], + ...customerFields[field], id: field, })); if (action.payload.customerFields) { - state.data.customerFields = action.payload.customerFields; + state.data.customerFields = customerFields; } }); builder.addCase(sep6DepositAction.rejected, (state, action) => { From 68819c9014a547883718ebb4277da97ab18d17f1 Mon Sep 17 00:00:00 2001 From: Iveta Date: Tue, 21 Nov 2023 14:21:52 -0500 Subject: [PATCH 3/3] Withdrawal flow updated --- .../src/components/Sep6/Sep6Withdraw.tsx | 79 ++++++++++------- .../src/ducks/sep6WithdrawAsset.ts | 87 ++++++++++++++----- .../demo-wallet-client/src/types/types.ts | 1 + .../methods/sep6/programmaticWithdrawFlow.ts | 23 +++-- 4 files changed, 128 insertions(+), 62 deletions(-) diff --git a/packages/demo-wallet-client/src/components/Sep6/Sep6Withdraw.tsx b/packages/demo-wallet-client/src/components/Sep6/Sep6Withdraw.tsx index 815f72d8..134a8ee5 100644 --- a/packages/demo-wallet-client/src/components/Sep6/Sep6Withdraw.tsx +++ b/packages/demo-wallet-client/src/components/Sep6/Sep6Withdraw.tsx @@ -164,6 +164,53 @@ export const Sep6Withdraw = () => { dispatch(submitSep6WithdrawCustomerInfoFields(formData.customerFields)); }; + if (sep6WithdrawAsset.status === ActionStatus.NEEDS_KYC) { + return ( + + SEP-6 Customer Info + + {Object.keys(sep6WithdrawAsset.data.fields).length ? ( + + + These are the fields the receiving anchor requires. The + sending client obtains them from the /customer endpoint.{" "} + + Learn more + + + } + isInline + tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM} + > + <>SEP-12 Required Info + + + ) : null} + +
+ {Object.entries(sep6WithdrawAsset.data.fields || {}).map( + ([field, fieldInfo]) => ( + + ), + )} +
+
+ + + + +
+ ); + } + if (sep6WithdrawAsset.status === ActionStatus.NEEDS_INPUT) { if ( sep6WithdrawAsset.data.requiredCustomerInfoUpdates && @@ -243,38 +290,6 @@ export const Sep6Withdraw = () => { /> ))} - {Object.keys(sep6WithdrawAsset.data.fields).length ? ( - - - These are the fields the receiving anchor requires. The - sending client obtains them from the /customer endpoint.{" "} - - Learn more - - - } - isInline - tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM} - > - <>SEP-12 Required Info - - - ) : null} - -
- {Object.entries(sep6WithdrawAsset.data.fields || {}).map( - ([field, fieldInfo]) => ( - - ), - )} -
- diff --git a/packages/demo-wallet-client/src/ducks/sep6WithdrawAsset.ts b/packages/demo-wallet-client/src/ducks/sep6WithdrawAsset.ts index e1e52d18..77f1a223 100644 --- a/packages/demo-wallet-client/src/ducks/sep6WithdrawAsset.ts +++ b/packages/demo-wallet-client/src/ducks/sep6WithdrawAsset.ts @@ -135,29 +135,11 @@ export const initiateWithdrawAction = createAsyncThunk< signedChallengeTransaction, }); - // Get SEP-12 fields - log.instruction({ - title: "Making GET `/customer` request for user", - }); - - const sep12Fields = await collectSep12Fields({ - publicKey, - token, - kycServer: webAuthTomlResponse.KYC_SERVER, - }); - payload = { ...payload, kycServer: webAuthTomlResponse.KYC_SERVER, token, }; - - if (sep12Fields) { - payload = { - ...payload, - fields: { ...payload.fields, ...sep12Fields }, - }; - } } return payload; @@ -177,7 +159,11 @@ export const initiateWithdrawAction = createAsyncThunk< ); export const submitSep6WithdrawFields = createAsyncThunk< - { status: ActionStatus; withdrawResponse: Sep6WithdrawResponse }, + { + status: ActionStatus; + withdrawResponse: Sep6WithdrawResponse; + customerFields?: AnyObject; + }, { withdrawType: AnyObject; infoFields: AnyObject; @@ -217,6 +203,32 @@ export const submitSep6WithdrawFields = createAsyncThunk< claimableBalanceSupported, })) as Sep6WithdrawResponse; + if ( + withdrawResponse.type === + TransactionStatus.NON_INTERACTIVE_CUSTOMER_INFO_NEEDED + ) { + log.instruction({ + title: "Anchor requires additional customer information (KYC)", + }); + + // Get SEP-12 fields + log.instruction({ + title: "Making GET `/customer` request for user", + }); + + const customerFields = await collectSep12Fields({ + publicKey, + token, + kycServer, + }); + + return { + status: ActionStatus.NEEDS_KYC, + withdrawResponse, + customerFields, + }; + } + return { status: ActionStatus.CAN_PROCEED, withdrawResponse, @@ -241,6 +253,7 @@ export const sep6WithdrawAction = createAsyncThunk< transactionResponse: AnyObject; status: ActionStatus; requiredCustomerInfoUpdates: string[] | undefined; + customerFields?: { [key: string]: AnyObject }; }, string, { rejectValue: RejectMessage; state: RootState } @@ -248,7 +261,7 @@ export const sep6WithdrawAction = createAsyncThunk< "sep6WithdrawAsset/sep6WithdrawAction", async (amount, { rejectWithValue, getState }) => { try { - const { secretKey } = accountSelector(getState()); + const { secretKey, data } = accountSelector(getState()); const networkConfig = getNetworkConfig(); const { data: sep6Data } = sepWithdrawSelector(getState()); @@ -258,6 +271,7 @@ export const sep6WithdrawAction = createAsyncThunk< transferServerUrl, token, withdrawResponse, + kycServer, } = sep6Data; // Poll transaction until complete @@ -274,6 +288,22 @@ export const sep6WithdrawAction = createAsyncThunk< assetIssuer, }); + let customerFields; + + // Need to get KYC fields to get field info + if (currentStatus === TransactionStatus.PENDING_CUSTOMER_INFO_UPDATE) { + // Get SEP-12 fields + log.instruction({ + title: "Making GET `/customer` request for user", + }); + + customerFields = await collectSep12Fields({ + publicKey: data?.id!, + token, + kycServer, + }); + } + return { currentStatus, transactionResponse: transaction, @@ -282,6 +312,7 @@ export const sep6WithdrawAction = createAsyncThunk< ? ActionStatus.NEEDS_INPUT : ActionStatus.SUCCESS, requiredCustomerInfoUpdates, + customerFields, }; } catch (error) { const errorMessage = getErrorMessage(error); @@ -384,6 +415,10 @@ const sep6WithdrawAssetSlice = createSlice({ builder.addCase(submitSep6WithdrawFields.fulfilled, (state, action) => { state.status = action.payload.status; state.data.withdrawResponse = action.payload.withdrawResponse; + state.data.fields = { + ...state.data.fields, + ...action.payload.customerFields, + }; }); builder.addCase(submitSep6WithdrawFields.rejected, (state, action) => { state.errorString = action.payload?.errorString; @@ -416,11 +451,21 @@ const sep6WithdrawAssetSlice = createSlice({ state.status = action.payload.status; state.data.currentStatus = action.payload.currentStatus; state.data.transactionResponse = action.payload.transactionResponse; + + const customerFields = { + ...state.data.fields, + ...action.payload.customerFields, + }; + state.data.requiredCustomerInfoUpdates = action.payload.requiredCustomerInfoUpdates?.map((field) => ({ - ...state.data.fields[field], + ...customerFields[field], id: field, })); + + if (action.payload.customerFields) { + state.data.fields = customerFields; + } }); builder.addCase(sep6WithdrawAction.rejected, (state, action) => { state.errorString = action.payload?.errorString; diff --git a/packages/demo-wallet-client/src/types/types.ts b/packages/demo-wallet-client/src/types/types.ts index 1f49fd6a..caae31b2 100644 --- a/packages/demo-wallet-client/src/types/types.ts +++ b/packages/demo-wallet-client/src/types/types.ts @@ -245,6 +245,7 @@ export interface Sep6WithdrawResponse { fee_fixed?: number; fee_percent?: number; extra_info?: { message?: string }; + type?: string; /* eslint-enable camelcase */ } diff --git a/packages/demo-wallet-shared/methods/sep6/programmaticWithdrawFlow.ts b/packages/demo-wallet-shared/methods/sep6/programmaticWithdrawFlow.ts index 0b95d18f..595a4e38 100644 --- a/packages/demo-wallet-shared/methods/sep6/programmaticWithdrawFlow.ts +++ b/packages/demo-wallet-shared/methods/sep6/programmaticWithdrawFlow.ts @@ -1,6 +1,6 @@ import { each } from "lodash"; import { log } from "../../helpers/log"; -import { AnyObject } from "../../types/types"; +import { AnyObject, TransactionStatus } from "../../types/types"; type ProgrammaticWithdrawFlowProps = { assetCode: string; @@ -53,14 +53,19 @@ export const programmaticWithdrawFlow = async ({ const withdrawJson = await response.json(); - if (response.status !== 200) { - throw new Error(withdrawJson.error); - } + // "non_interactive_customer_info_needed" (403) case is handled later + if ( + withdrawJson.type === + TransactionStatus.NON_INTERACTIVE_CUSTOMER_INFO_NEEDED || + response.status === 200 + ) { + log.response({ + title: `${API_METHOD} \`${REQUEST_URL_STR}\``, + body: withdrawJson, + }); - log.response({ - title: `${API_METHOD} \`${REQUEST_URL_STR}\``, - body: withdrawJson, - }); + return withdrawJson; + } - return withdrawJson; + throw new Error(withdrawJson.error); };