-
Notifications
You must be signed in to change notification settings - Fork 3
✨ server: add card limit update handler #898
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
base: main
Are you sure you want to change the base?
Changes from all commits
4728f0a
86f062f
00d98c1
08ac3a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| ✨ add card limit case update |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| ✨ return processing for card-limit review states in kyc-api |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| ✨ add card limit support to persona hook |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| ✨ add card limit inquiry flow to kyc api |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,12 +18,16 @@ import database, { credentials } from "../database/index"; | |
| import auth from "../middleware/auth"; | ||
| import decodePublicKey from "../utils/decodePublicKey"; | ||
| import { | ||
| CARD_LIMIT_TEMPLATE, | ||
| createInquiry, | ||
| CRYPTOMATE_TEMPLATE, | ||
| getAccount, | ||
| getCardLimitStatus, | ||
| getInquiry, | ||
| getPendingInquiryTemplate, | ||
| getUnknownAccount, | ||
| PANDA_TEMPLATE, | ||
| parseAccount, | ||
| resumeInquiry, | ||
| scopeValidationErrors, | ||
| } from "../utils/persona"; | ||
|
|
@@ -41,7 +45,7 @@ export default new Hono() | |
| "query", | ||
| object({ | ||
| countryCode: optional(literal("true")), | ||
| scope: optional(picklist(["basic", "bridge", "manteca"])), | ||
| scope: optional(picklist(["basic", "bridge", "cardLimit", "manteca"])), | ||
| }), | ||
| validatorHook(), | ||
| ), | ||
|
|
@@ -59,6 +63,48 @@ export default new Hono() | |
| setUser({ id: account }); | ||
| setContext("exa", { credential }); | ||
|
|
||
| if (scope === "cardLimit") { | ||
| let unknownAccount: Awaited<ReturnType<typeof getUnknownAccount>> | undefined; | ||
| if (c.req.valid("query").countryCode) { | ||
| try { | ||
| unknownAccount = await getUnknownAccount(credentialId); | ||
| } catch (error: unknown) { | ||
| captureException(error, { level: "error", contexts: { details: { credentialId, scope: "cardLimit" } } }); | ||
| } | ||
| } | ||
| if (unknownAccount) { | ||
| const countryCode = parseAccount(unknownAccount, "basic")?.attributes["country-code"]; | ||
| countryCode && c.header("User-Country", countryCode); | ||
| } | ||
| const cardLimit = await getCardLimitStatus(credentialId, unknownAccount); | ||
|
aguxez marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The call to Suggested FixWrap the call to Prompt for AI Agent |
||
|
|
||
| switch (cardLimit.status) { | ||
| case "resolved": | ||
| return c.json({ code: "ok" }, 200); | ||
| case "approved": | ||
| captureException(new Error("inquiry approved but account not updated"), { | ||
| level: "error", | ||
| contexts: { inquiry: { templateId: CARD_LIMIT_TEMPLATE, referenceId: credentialId } }, | ||
| }); | ||
| return c.json({ code: "ok" }, 200); | ||
| case "noTemplate": | ||
| return c.json({ code: "no kyc" }, 400); | ||
| case "noInquiry": | ||
| case "created": | ||
| case "pending": | ||
| case "expired": | ||
| return c.json({ code: "not started" }, 400); | ||
| case "completed": | ||
| case "needs_review": | ||
| return c.json({ code: "processing" }, 400); | ||
| case "failed": | ||
| case "declined": | ||
| return c.json({ code: "bad kyc" }, 400); | ||
| default: | ||
| throw new Error("unknown inquiry status"); | ||
| } | ||
| } | ||
|
aguxez marked this conversation as resolved.
|
||
|
|
||
| if (scope === "basic" && credential.pandaId) { | ||
| if (c.req.valid("query").countryCode) { | ||
| const personaAccount = await getAccount(credentialId, scope).catch((error: unknown) => { | ||
|
|
@@ -108,12 +154,12 @@ export default new Hono() | |
| return c.json({ code: "not started", legacy: "kyc not started" }, 400); | ||
| case "completed": | ||
| case "needs_review": | ||
| return c.json({ code: "bad kyc", legacy: "kyc not approved" }, 400); // TODO send a different response for this transitory statuses | ||
| return c.json({ code: "processing", legacy: "kyc not approved" }, 400); | ||
|
aguxez marked this conversation as resolved.
Comment on lines
155
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚩 Behavioral change: completed/needs_review status now returns 'processing' instead of error codes The Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| case "failed": | ||
| case "declined": | ||
| return c.json({ code: "bad kyc", legacy: "kyc not approved" }, 400); | ||
| default: | ||
| throw new Error("Unknown inquiry status"); | ||
| throw new Error("unknown inquiry status"); | ||
| } | ||
| }, | ||
| ) | ||
|
|
@@ -124,7 +170,7 @@ export default new Hono() | |
| "json", | ||
| object({ | ||
| redirectURI: optional(string()), | ||
| scope: optional(picklist(["basic", "bridge", "manteca"])), | ||
| scope: optional(picklist(["basic", "bridge", "cardLimit", "manteca"])), | ||
| }), | ||
| validatorHook({ debug }), | ||
| ), | ||
|
|
@@ -141,6 +187,51 @@ export default new Hono() | |
| setUser({ id: parse(Address, credential.account) }); | ||
| setContext("exa", { credential }); | ||
|
|
||
| if (scope === "cardLimit") { | ||
| const cardLimit = await getCardLimitStatus(credentialId); | ||
| switch (cardLimit.status) { | ||
| case "resolved": | ||
| return c.json({ code: "already approved" }, 400); | ||
| case "approved": | ||
| captureException(new Error("inquiry approved but account not updated"), { | ||
| level: "error", | ||
| contexts: { inquiry: { templateId: CARD_LIMIT_TEMPLATE, referenceId: credentialId } }, | ||
| }); | ||
| return c.json({ code: "already approved" }, 400); | ||
| case "noTemplate": | ||
| return c.json({ code: "not started" }, 400); | ||
| case "noInquiry": { | ||
| const basicAccount = await getAccount(credentialId, "basic").catch((error: unknown) => { | ||
| captureException(error, { level: "error", contexts: { details: { credentialId, scope: "cardLimit" } } }); | ||
| }); | ||
| const { data } = await createInquiry( | ||
| credentialId, | ||
| CARD_LIMIT_TEMPLATE, | ||
| redirectURI, | ||
| basicAccount | ||
| ? { | ||
| "name-first": basicAccount.attributes["name-first"], | ||
| "name-last": basicAccount.attributes["name-last"], | ||
| } | ||
| : undefined, | ||
| ); | ||
| return c.json(await generateInquiryTokens(data.id), 200); | ||
| } | ||
| case "completed": | ||
| case "needs_review": | ||
| return c.json({ code: "processing" }, 400); | ||
| case "pending": | ||
| case "created": | ||
| case "expired": | ||
| return c.json(await generateInquiryTokens(cardLimit.id), 200); | ||
| case "failed": | ||
| case "declined": | ||
| return c.json({ code: "failed" }, 400); | ||
| default: | ||
| throw new Error("unknown inquiry status"); | ||
| } | ||
| } | ||
|
|
||
| let inquiryTemplateId: Awaited<ReturnType<typeof getPendingInquiryTemplate>>; | ||
| try { | ||
| inquiryTemplateId = await getPendingInquiryTemplate(credentialId, scope); | ||
|
|
@@ -157,8 +248,7 @@ export default new Hono() | |
| const inquiry = await getInquiry(credentialId, inquiryTemplateId); | ||
| if (!inquiry) { | ||
| const { data } = await createInquiry(credentialId, inquiryTemplateId, redirectURI); | ||
| const { inquiryId, sessionToken } = await generateInquiryTokens(data.id); | ||
| return c.json({ inquiryId, sessionToken }, 200); | ||
| return c.json(await generateInquiryTokens(data.id), 200); | ||
| } | ||
|
|
||
| switch (inquiry.attributes.status) { | ||
|
|
@@ -173,15 +263,13 @@ export default new Hono() | |
| return c.json({ code: "failed", legacy: "kyc failed" }, 400); | ||
| case "completed": | ||
| case "needs_review": | ||
| return c.json({ code: "failed", legacy: "kyc failed" }, 400); // TODO send a different response | ||
| return c.json({ code: "processing", legacy: "kyc failed" }, 400); | ||
|
aguxez marked this conversation as resolved.
|
||
| case "pending": | ||
| case "created": | ||
| case "expired": { | ||
| const { inquiryId, sessionToken } = await generateInquiryTokens(inquiry.id); | ||
| return c.json({ inquiryId, sessionToken }, 200); | ||
| } | ||
| case "expired": | ||
| return c.json(await generateInquiryTokens(inquiry.id), 200); | ||
| default: | ||
| throw new Error("Unknown inquiry status"); | ||
| throw new Error("unknown inquiry status"); | ||
| } | ||
| }, | ||
| ); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.