Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SEP-6: update KYC flow #331

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 46 additions & 31 deletions packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,52 @@ export const Sep6Deposit = () => {
return `Min: ${minAmount} | Max: ${maxAmount}`;
};

if (sep6DepositAsset.status === ActionStatus.NEEDS_KYC) {
return (
<Modal visible onClose={handleClose} parentId={CSS_MODAL_PARENT_ID}>
<Modal.Heading>SEP-6 Customer Info</Modal.Heading>
<Modal.Body>
{Object.keys(sep6DepositAsset.data.customerFields).length ? (
<Heading3>
<DetailsTooltip
details={
<>
These are the fields the receiving anchor requires. The
sending client obtains them from the /customer endpoint.{" "}
<TextLink href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#customer-get">
Learn more
</TextLink>
</>
}
isInline
tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM}
>
<>SEP-12 Required Info</>
</DetailsTooltip>
</Heading3>
) : null}
<div className="vertical-spacing">
{Object.entries(sep6DepositAsset.data.customerFields || {}).map(
([id, input]) => (
<KycFieldInput
id={id}
input={input as KycField}
onChange={handleCustomerFieldChange}
/>
),
)}
</div>
</Modal.Body>
<Modal.Footer>
<Button onClick={handleSubmit}>Submit</Button>
<Button onClick={handleClose} variant={Button.variant.secondary}>
Cancel
</Button>
</Modal.Footer>
</Modal>
);
}

if (sep6DepositAsset.status === ActionStatus.NEEDS_INPUT) {
if (
sep6DepositAsset.data.requiredCustomerInfoUpdates &&
Expand Down Expand Up @@ -279,37 +325,6 @@ export const Sep6Deposit = () => {
),
)}
</div>

{Object.keys(sep6DepositAsset.data.customerFields).length ? (
<Heading3>
<DetailsTooltip
details={
<>
These are the fields the receiving anchor requires. The
sending client obtains them from the /customer endpoint.{" "}
<TextLink href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#customer-get">
Learn more
</TextLink>
</>
}
isInline
tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM}
>
<>SEP-12 Required Info</>
</DetailsTooltip>
</Heading3>
) : null}
<div className="vertical-spacing">
{Object.entries(sep6DepositAsset.data.customerFields || {}).map(
([id, input]) => (
<KycFieldInput
id={id}
input={input as KycField}
onChange={handleCustomerFieldChange}
/>
),
)}
</div>
</Modal.Body>

<Modal.Footer>
Expand Down
79 changes: 47 additions & 32 deletions packages/demo-wallet-client/src/components/Sep6/Sep6Withdraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,53 @@ export const Sep6Withdraw = () => {
dispatch(submitSep6WithdrawCustomerInfoFields(formData.customerFields));
};

if (sep6WithdrawAsset.status === ActionStatus.NEEDS_KYC) {
return (
<Modal visible onClose={handleClose} parentId={CSS_MODAL_PARENT_ID}>
<Modal.Heading>SEP-6 Customer Info</Modal.Heading>
<Modal.Body>
{Object.keys(sep6WithdrawAsset.data.fields).length ? (
<Heading3>
<DetailsTooltip
details={
<>
These are the fields the receiving anchor requires. The
sending client obtains them from the /customer endpoint.{" "}
<TextLink href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#customer-get">
Learn more
</TextLink>
</>
}
isInline
tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM}
>
<>SEP-12 Required Info</>
</DetailsTooltip>
</Heading3>
) : null}

<div className="vertical-spacing">
{Object.entries(sep6WithdrawAsset.data.fields || {}).map(
([field, fieldInfo]) => (
<KycFieldInput
id={field}
input={fieldInfo as KycField}
onChange={handleCustomerFieldChange}
/>
),
)}
</div>
</Modal.Body>
<Modal.Footer>
<Button onClick={handleFieldsSubmit}>Submit</Button>
<Button onClick={handleClose} variant={Button.variant.secondary}>
Cancel
</Button>
</Modal.Footer>
</Modal>
);
}

if (sep6WithdrawAsset.status === ActionStatus.NEEDS_INPUT) {
if (
sep6WithdrawAsset.data.requiredCustomerInfoUpdates &&
Expand Down Expand Up @@ -243,38 +290,6 @@ export const Sep6Withdraw = () => {
/>
))}
</div>
{Object.keys(sep6WithdrawAsset.data.fields).length ? (
<Heading3>
<DetailsTooltip
details={
<>
These are the fields the receiving anchor requires. The
sending client obtains them from the /customer endpoint.{" "}
<TextLink href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#customer-get">
Learn more
</TextLink>
</>
}
isInline
tooltipPosition={DetailsTooltip.tooltipPosition.BOTTOM}
>
<>SEP-12 Required Info</>
</DetailsTooltip>
</Heading3>
) : null}

<div className="vertical-spacing">
{Object.entries(sep6WithdrawAsset.data.fields || {}).map(
([field, fieldInfo]) => (
<KycFieldInput
id={field}
input={fieldInfo as KycField}
onChange={handleCustomerFieldChange}
/>
),
)}
</div>

<ErrorMessage message={sep6WithdrawAsset.errorString} />
</Modal.Body>

Expand Down
89 changes: 67 additions & 22 deletions packages/demo-wallet-client/src/ducks/sep6DepositAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -184,7 +166,11 @@ export const initiateDepositAction = createAsyncThunk<
);

export const submitSep6DepositFields = createAsyncThunk<
{ status: ActionStatus; depositResponse: Sep6DepositResponse },
{
status: ActionStatus;
depositResponse: Sep6DepositResponse;
customerFields?: AnyObject;
},
{
amount?: string;
depositType: AnyObject;
Expand All @@ -200,8 +186,8 @@ export const submitSep6DepositFields = createAsyncThunk<
) => {
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());

Expand All @@ -227,6 +213,32 @@ 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)",
});

// 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,
};
}

return {
status: ActionStatus.CAN_PROCEED,
depositResponse,
Expand Down Expand Up @@ -290,14 +302,15 @@ export const sep6DepositAction = createAsyncThunk<
status: ActionStatus;
trustedAssetAdded: string;
requiredCustomerInfoUpdates: string[] | undefined;
customerFields?: { [key: string]: AnyObject };
},
undefined,
{ rejectValue: RejectMessage; state: RootState }
>(
"sep6DepositAsset/sep6DepositAction",
async (_, { rejectWithValue, getState, dispatch }) => {
try {
const { secretKey } = accountSelector(getState());
const { secretKey, data } = accountSelector(getState());
const networkConfig = getNetworkConfig();
const { data: sep6Data } = sep6DepositSelector(getState());

Expand All @@ -307,6 +320,7 @@ export const sep6DepositAction = createAsyncThunk<
depositResponse,
transferServerUrl,
token,
kycServer,
} = sep6Data;

const trustAssetCallback = async () => {
Expand Down Expand Up @@ -340,6 +354,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:
Expand All @@ -348,6 +378,7 @@ export const sep6DepositAction = createAsyncThunk<
: ActionStatus.SUCCESS,
trustedAssetAdded,
requiredCustomerInfoUpdates,
customerFields,
};
} catch (error) {
const errorMessage = getErrorMessage(error);
Expand Down Expand Up @@ -419,6 +450,10 @@ 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;
Expand All @@ -444,11 +479,21 @@ const sep6DepositAssetSlice = createSlice({
state.status = action.payload.status;
state.data.currentStatus = action.payload.currentStatus;
state.data.trustedAssetAdded = action.payload.trustedAssetAdded;

const customerFields = {
...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 = customerFields;
}
});
builder.addCase(sep6DepositAction.rejected, (state, action) => {
state.errorString = action.payload?.errorString;
Expand Down
Loading