commit d671ef0f4cfa7d17b43b265501ae595882549f17 parent 726df07be957dbc053ff633242ca3c4bd752e8fd Author: Sebastian <sebasjm@gmail.com> Date: Fri, 6 Sep 2024 13:08:26 -0300 update to exchange v21 and merchant v17 Diffstat:
14 files changed, 290 insertions(+), 82 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -355,7 +355,7 @@ export function CaseDetails({ account }: { account: string }) { justification: "", keep_investigating: false, properties: {}, - new_measure: "m2", + new_measures: "m2", new_rules: { custom_measures: {}, expiration_time: AbsoluteTime.toProtocolTimestamp( diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx @@ -174,7 +174,7 @@ export function CaseUpdate({ successor_measure: undefined, }, properties: {}, - new_measure: undefined, + new_measures: undefined, }; return api.makeAmlDesicion(officer.account, decision); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.stories.tsx @@ -37,18 +37,18 @@ export const Example = tests.createExample(TestedComponent, { timeout_kycs: [], pending_kycs: [ { - aml_status: 0, exchange_url: "http://exchange.taler", payto_uri: "payto://iban/de123123123" as PaytoString, kyc_url: "http://exchange.taler/kyc", + exchange_http_status: 0, }, { - aml_status: 1, + exchange_http_status: 1, exchange_url: "http://exchange.taler", payto_uri: "payto://iban/de123123123" as PaytoString, }, { - aml_status: 2, + exchange_http_status: 2, exchange_url: "http://exchange.taler", payto_uri: "payto://iban/de123123123" as PaytoString, }, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx @@ -119,18 +119,11 @@ function PendingTable({ entries }: PendingTableProps): VNode { <td>{e.exchange_url}</td> <td>{e.payto_uri}</td> <td> - {e.aml_status === 1 ? ( - <i18n.Translate> - There is an anti-money laundering process pending to - complete. - </i18n.Translate> - ) : ( - <i18n.Translate> - The account is frozen due to the anti-money laundering - rules. Contact the exchange service provider for further - instructions. - </i18n.Translate> - )} + <i18n.Translate> + There is an anti-money laundering process pending to + complete. + </i18n.Translate> + </td> </tr> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx @@ -103,14 +103,37 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode { if (resp.type === "ok") { return onConfirm(resp.body.order_id); } else { - setNotif({ - message: i18n.str`Could not create order`, - type: "ERROR", - description: - resp.case === HttpStatusCode.Gone - ? i18n.str`No more stock for product with id "${resp.body.product_id}".` - : resp.detail?.hint, - }); + switch (resp.case) { + case HttpStatusCode.UnavailableForLegalReasons: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: i18n.str`No exchange would accept a payment because of KYC requirements.` + }); + return; + } + case HttpStatusCode.Unauthorized: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: resp.detail?.hint, + }); + return; + } + case HttpStatusCode.Gone: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: i18n.str`No more stock for product with id "${resp.body.product_id}".` + }); + return; + } + default: { + assertUnreachable(resp) + } + } } }) .catch((error) => { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx @@ -89,11 +89,31 @@ export default function Update({ oid, onBack }: Props): VNode { type: "SUCCESS", }); } else { - setNotif({ - message: i18n.str`Could not create the refund`, - type: "ERROR", - description: resp.detail?.hint, - }); + switch (resp.case) { + case HttpStatusCode.UnavailableForLegalReasons: { + setNotif({ + message: i18n.str`Could not create the refund`, + type: "ERROR", + description: i18n.str`There are pending KYC requirements.` + }); + return; + } + case HttpStatusCode.Unauthorized: + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + case HttpStatusCode.Gone: { + setNotif({ + message: i18n.str`Could not create the refund`, + type: "ERROR", + description: resp.detail?.hint, + }); + return; + } + default: { + assertUnreachable(resp) + } + } } }) .catch((error) => diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx @@ -164,11 +164,33 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { type: "SUCCESS", }); } else { - setNotif({ - message: i18n.str`Could not create the refund`, - type: "ERROR", - description: resp.detail?.hint, - }); + switch (resp.case) { + case HttpStatusCode.UnavailableForLegalReasons: { + setNotif({ + message: i18n.str`Could not create the refund`, + type: "ERROR", + description: i18n.str`There are pending KYC requirements.` + }); + return; + } + case HttpStatusCode.Unauthorized: + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + case HttpStatusCode.Gone: { + setNotif({ + message: i18n.str`Could not create the refund`, + type: "ERROR", + description: resp.detail?.hint, + }); + return; + } + default: { + assertUnreachable(resp) + } + + } + } }) .catch((error) => diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx @@ -87,14 +87,37 @@ export default function TemplateUsePage({ if (resp.type === "ok") { onOrderCreated(resp.body.order_id) } else { - setNotif({ - message: i18n.str`Could not create order from template`, - type: "ERROR", - description: - resp.case === HttpStatusCode.Gone - ? i18n.str`No more stock for product with id "${resp.body.product_id}".` - : resp.detail?.hint, - }); + switch (resp.case) { + case HttpStatusCode.UnavailableForLegalReasons: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: i18n.str`No exchange would accept a payment because of KYC requirements.` + }); + return; + } + case HttpStatusCode.Unauthorized: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: resp.detail?.hint, + }); + return; + } + case HttpStatusCode.Gone: { + setNotif({ + message: i18n.str`Could not create order`, + type: "ERROR", + description: i18n.str`No more stock for product with id "${resp.body.product_id}".` + }); + return; + } + default: { + assertUnreachable(resp) + } + } } }) .catch((error) => { diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts @@ -1063,7 +1063,7 @@ export async function postAmlDecision( justification: "Bla", keep_investigating: false, new_rules: req.newRules, - new_measure: req.newMeasure, + new_measures: req.newMeasure, properties: { foo: "42", }, diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -87,7 +87,7 @@ export type ReservePub = string & { [__pubId]: true }; */ export class TalerExchangeHttpClient { httpLib: HttpRequestLibrary; - public readonly PROTOCOL_VERSION = "20:0:0"; + public readonly PROTOCOL_VERSION = "21:0:0"; cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; constructor( @@ -631,21 +631,25 @@ export class TalerExchangeHttpClient { } /** - * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet + * https://docs.taler.net/core/api-exchange.html#get--kyc-check-$H_PAYTO * */ async checkKycStatus( account: ReserveAccount, - requirementId: number, + paytoHash: string, params: { timeout?: number; + awaitAuth?: boolean; } = {}, ) { - const url = new URL(`kyc-check/${String(requirementId)}`, this.baseUrl); + const url = new URL(`kyc-check/${paytoHash}`, this.baseUrl); if (params.timeout !== undefined) { url.searchParams.set("timeout_ms", String(params.timeout)); } + if (params.awaitAuth !== undefined) { + url.searchParams.set("await_auth", params.awaitAuth ? "YES" : "NO"); + } const resp = await this.httpLib.fetch(url.href, { method: "GET", @@ -1052,8 +1056,8 @@ function buildAMLDecisionSignature( .put(hash(stringToBytes(canonicalJson(decision.properties) + "\0"))) .put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0"))) .put( - decision.new_measure != null - ? hash(stringToBytes(decision.new_measure)) + decision.new_measures != null + ? hash(stringToBytes(decision.new_measures)) : zero, ) .put(bufferForUint64(decision.keep_investigating ? 1 : 0)) diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -45,6 +45,7 @@ import { codecForOtpDeviceSummaryResponse, codecForOutOfStockResponse, codecForPaidRefundStatusResponse, + codecForPaymentDeniedLegallyResponse, codecForPaymentResponse, codecForPostOrderResponse, codecForProductDetail, @@ -137,7 +138,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public readonly PROTOCOL_VERSION = "16:0:0"; + public readonly PROTOCOL_VERSION = "17:0:0"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -276,6 +277,8 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.GatewayTimeout: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure(resp, resp.status, codecForPaymentDeniedLegallyResponse()) default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -431,6 +434,8 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure(resp, resp.status, codecForPaymentDeniedLegallyResponse()) default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -1234,6 +1239,8 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure(resp, resp.status, codecForPaymentDeniedLegallyResponse()) case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Gone: @@ -1471,6 +1478,8 @@ export class TalerMerchantInstanceHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure(resp, resp.status, codecForPaymentDeniedLegallyResponse()) default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } diff --git a/packages/taler-util/src/taler-signatures.ts b/packages/taler-util/src/taler-signatures.ts @@ -50,8 +50,8 @@ export function signAmlDecision( builder.put(hash(stringToBytes(decision.justification))); builder.put(hash(stringToBytes(canonicalJson(decision.properties) + "\0"))); builder.put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0"))); - if (decision.new_measure != null) { - builder.put(hash(stringToBytes(decision.new_measure))); + if (decision.new_measures != null) { + builder.put(hash(stringToBytes(decision.new_measures))); } else { builder.put(new Uint8Array(64)); } diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts @@ -1412,6 +1412,25 @@ export interface MeasureInformation { // Context for the check. Optional. context?: Object; + + // Operation that this measure relates to. + // NULL if unknown. Useful as a hint to the + // user if there are many (voluntary) measures + // and some related to unlocking certain operations. + // (and due to zero-amount thresholds, no measure + // was actually specifically triggered). + // + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + // New in protocol **v21**. + operation_type?: string; + + // Can this measure be undertaken voluntarily? + // Optional, default is false. + // Since protocol **vATTEST**. + voluntary?: boolean; } export interface AmlProgramRequirement { @@ -1597,6 +1616,8 @@ export interface LegitimizationNeededResponse { // Hash of the payto:// account URI for which KYC // is required. + // The account holder can uses the /kyc-check/$H_PAYTO + // endpoint to check the KYC status or initiate the KYC process. h_payto: PaytoHash; // Public key associated with the account. The client must sign @@ -1611,12 +1632,12 @@ export interface LegitimizationNeededResponse { account_pub?: EddsaPublicKeyString; // Identifies a set of measures that were triggered and that are - // now preventing this operation from proceeding. Gives the - // account holder a starting point for understanding why the - // transaction was blocked and how to lift it. The account holder - // should use the number to check for the account's AML/KYC status - // using the /kyc-check/$REQUIREMENT_ROW endpoint. - requirement_row: Integer | undefined; + // now preventing this operation from proceeding. Gives developers + // a starting point for understanding why the transaction was + // blocked and how to lift it. + // Can be zero (which means there is no requirement row), + // especially if bad_kyc_auth is set. + requirement_row: Integer; // True if the operation was denied because the // KYC auth key does not match the merchant public @@ -1686,7 +1707,8 @@ export interface AccountLimit { // Clients that are aware of hard limits *should* // inform users about the hard limit and prevent flows // in the UI that would cause violations of hard limits. - soft_limit: boolean; + // Made optional in **v21** with a default of 'false' if missing. + soft_limit?: boolean; } export interface KycProcessClientInformation { @@ -1854,6 +1876,12 @@ export interface AmlDecisionRequest { // Identifies a GNU Taler wallet or an affected bank account. h_payto: PaytoHash; + // Payto address of the account the decision is about. + // Optional. Must be given if the account is not yet + // known to the exchange. If given, must match h_payto. + // New since protocol **v21**. + payto_uri?: string; + // What are the new rules? // New since protocol **v20**. new_rules: LegitimizationRuleSet; @@ -1862,14 +1890,14 @@ export interface AmlDecisionRequest { // New since protocol **v20**. properties: AccountProperties; - // New measure to apply immediately to the account. + // Space-separated list of measures to trigger + // immediately on the account. + // Prefixed with a "+" to indicate that the + // measures should be ANDed. // Should typically be used to give the user some // information or request additional information. - // Use "verboten" to communicate to the customer - // that there is no KYC check that could be passed - // to modify the new_rules. - // New since protocol **v20**. - new_measure?: string; + // New since protocol **v21**. + new_measures?: string; // True if the account should remain under investigation by AML staff. // New since protocol **v20**. @@ -1969,10 +1997,6 @@ export interface ExchangeKeysResponse { // The exchange's currency or asset unit. currency: string; - /** - * FIXME: PARTIALLY IMPLEMENTED!! - */ - // How wallets should render this currency. currency_specification: CurrencySpecification; @@ -2009,7 +2033,7 @@ export interface ExchangeKeysResponse { // List of exchanges that this exchange is partnering // with to enable wallet-to-wallet transfers. - wads: ExchangePartner[]; + wads: ExchangePartnerListEntry[]; // EdDSA master public key of the exchange, used to sign entries // in denoms and signkeys. @@ -2020,11 +2044,26 @@ export interface ExchangeKeysResponse { reserve_closing_delay: RelativeTime; // Threshold amounts beyond which wallet should - // trigger the KYC process of the issuing - // exchange. Optional option, if not given there is no limit. + // trigger the KYC process of the issuing exchange. + // Optional option, if not given there is no limit. // Currency must match currency. wallet_balance_limit_without_kyc?: AmountString[]; + // Array of limits that apply to all accounts. + // All of the given limits will be hard limits. + // Wallets and merchants are expected to obey them + // and not even allow the user to cross them. + // Since protocol **v21**. + hard_limits: AccountLimit[]; + + // Array of limits with a soft threshold of zero + // that apply to all accounts without KYC. + // Wallets and merchants are expected to trigger + // a KYC process before attempting any zero-limited + // operations. + // Since protocol **v21**. + zero_limits: ZeroLimitedOperation[]; + // Denominations offered by this exchange denominations: DenomGroup[]; @@ -2065,6 +2104,18 @@ export interface ExchangeKeysResponse { extensions_sig?: EddsaSignature; } +interface ZeroLimitedOperation { + + // Operation that is limited to an amount of + // zero until the client has passed some KYC check. + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + operation_type: string; + +} + interface ExtensionManifest { // The criticality of the extension MUST be provided. It has the same // semantics as "critical" has for extensions in X.509: @@ -2198,7 +2249,7 @@ export interface AggregateTransferFee { sig: EddsaSignatureString; } -interface ExchangePartner { +interface ExchangePartnerListEntry { // Base URL of the partner exchange. partner_base_url: string; @@ -2268,6 +2319,8 @@ export const codecForExchangeConfig = (): Codec<ExchangeVersionResponse> => .property("currency_specification", codecForCurrencySpecificiation()) .property("supported_kyc_requirements", codecForList(codecForString())) .build("TalerExchangeApi.ExchangeVersionResponse"); + +// FIXME: complete the codec to check for valid exchange response export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> => buildCodecForObject<ExchangeKeysResponse>() .property("version", codecForString()) @@ -2277,6 +2330,8 @@ export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> => .property("asset_type", codecForAny()) .property("auditors", codecForAny()) .property("currency_specification", codecForAny()) + .property("zero_limits", codecForAny()) + .property("hard_limits", codecForAny()) .property("denominations", codecForAny()) .property("exchange_pub", codecForAny()) .property("exchange_sig", codecForAny()) @@ -2294,7 +2349,6 @@ export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> => .property("wads", codecForAny()) .property("wallet_balance_limit_without_kyc", codecForAny()) .property("wire_fees", codecForAny()) - .build("TalerExchangeApi.ExchangeKeysResponse"); export const codecForEventCounter = (): Codec<EventCounter> => @@ -2336,6 +2390,8 @@ export const codecForMeasureInformation = (): Codec<MeasureInformation> => .property("prog_name", codecForString()) .property("check_name", codecForString()) .property("context", codecForAny()) + .property("operation_type", codecOptional(codecForString())) + .property("voluntary", codecOptional(codecForBoolean())) .build("TalerExchangeApi.MeasureInformation"); // export const codecForAmlDecisionDetails = (): Codec<AmlDecisionDetails> => @@ -2430,7 +2486,7 @@ export const codecForLegitimizationNeededResponse = .property("hint", codecOptional(codecForString())) .property("h_payto", codecForString()) .property("account_pub", codecOptional(codecForString())) - .property("requirement_row", codecOptional(codecForNumber())) + .property("requirement_row", (codecForNumber())) .property("bad_kyc_auth", codecOptional(codecForBoolean())) .build("TalerExchangeApi.LegitimizationNeededResponse"); @@ -2454,7 +2510,7 @@ export const codecForAccountLimit = (): Codec<AccountLimit> => ) .property("timeframe", codecForDuration) .property("threshold", codecForAmountString()) - .property("soft_limit", codecForBoolean()) + .property("soft_limit", codecOptional(codecForBoolean())) .build("TalerExchangeApi.AccountLimit"); export const codecForKycCheckPublicInformation = diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -29,10 +29,12 @@ import { } from "./codec.js"; import { AccessToken, + AccountLimit, CoinEnvelope, ExchangeWireAccount, PaytoString, buildCodecForUnion, + codecForAccountLimit, codecForConstNumber, codecForConstString, codecForEither, @@ -812,6 +814,16 @@ export interface PaymentResponse { // payment. pos_confirmation?: string; } +export interface PaymentDeniedLegallyResponse { + + // Base URL of the exchanges that denied the payment. + // The wallet should refresh the coins from these + // exchanges, but may try to pay with coins from + // other exchanges. + exchange_base_urls: string[]; + +} + export interface PaymentStatusRequestParams { // Hash of the order’s contract terms (this is used to @@ -1335,17 +1347,55 @@ export interface MerchantAccountKycRedirect { // proceed with the KYC process (as returned // by the exchange's /kyc-check/ endpoint). // Optional, missing if the account is blocked - // due to AML and not due to KYC. + // due to the need for a KYC auth transfer. + // (See payto_kycauth in that case.) kyc_url?: string; - // AML status of the account. - aml_status: Integer; + // Array with limitations that currently apply to this + // account and that may be increased or lifted if the + // KYC check is passed. + // Note that additional limits *may* exist and not be + // communicated to the client. If such limits are + // reached, this *may* be indicated by the account + // going into aml_review state. However, it is + // also possible that the exchange may legally have + // to deny operations without being allowed to provide + // any justification. + // The limits should be used by the client to + // possibly structure their operations (e.g. withdraw + // what is possible below the limit, ask the user to + // pass KYC checks or withdraw the rest after the time + // limit is passed, warn the user to not withdraw too + // much or even prevent the user from generating a + // request that would cause it to exceed hard limits). + limits?: AccountLimit[]; // Base URL of the exchange this is about. exchange_url: string; + // Numeric error code indicating errors the exchange + // returned, or TALER_EC_INVALID for none. + // Optional (as there may not always have + // been an error code). Since protocol **v17**. + exchange_code?: number; + + // HTTP status code returned by the exchange when we asked for + // information about the KYC status. + // Since protocol **v17**. + exchange_http_status: number; + // Our bank wire account this is about. payto_uri: PaytoString; + + // Array of wire transfer instructions (including + // optional amount and subject) for a KYC auth wire + // transfer. Set only if this is required + // to get the given exchange working. + // Array because the exchange may have multiple + // bank accounts, in which case any of these + // accounts will do. + // Optional. Since protocol **v17**. + payto_kycauths?: string[]; } export interface ExchangeKycTimeout { @@ -2823,6 +2873,11 @@ export const codecForPaymentResponse = (): Codec<PaymentResponse> => .property("sig", codecForString()) .build("TalerMerchantApi.PaymentResponse"); +export const codecForPaymentDeniedLegallyResponse = (): Codec<PaymentDeniedLegallyResponse> => + buildCodecForObject<PaymentDeniedLegallyResponse>() + .property("exchange_base_urls", codecForList(codecForString())) + .build("TalerMerchantApi.PaymentDeniedLegallyResponse"); + export const codecForStatusPaid = (): Codec<StatusPaid> => buildCodecForObject<StatusPaid>() .property("refund_amount", codecForAmountString()) @@ -2968,10 +3023,13 @@ export const codecForAccountKycRedirects = (): Codec<AccountKycRedirects> => export const codecForMerchantAccountKycRedirect = (): Codec<MerchantAccountKycRedirect> => buildCodecForObject<MerchantAccountKycRedirect>() - .property("kyc_url", codecForURLString()) - .property("aml_status", codecForNumber()) + .property("kyc_url", codecOptional(codecForURLString())) + .property("limits", codecOptional(codecForList(codecForAccountLimit()))) .property("exchange_url", codecForURLString()) + .property("exchange_code", codecForNumber()) + .property("exchange_http_status", codecForNumber()) .property("payto_uri", codecForPaytoString()) + .property("payto_kycauths", codecOptional(codecForList(codecForString()))) .build("TalerMerchantApi.MerchantAccountKycRedirect"); export const codecForExchangeKycTimeout = (): Codec<ExchangeKycTimeout> =>