From 3470ba499f25721a0b6068de23f1de466837237c Mon Sep 17 00:00:00 2001 From: oana-lolea Date: Mon, 14 Oct 2024 13:02:59 +0300 Subject: [PATCH 01/10] docs: added missing responses to id-provider requests (#3024) * docs: added missing responses to id-provider requests --- packages/auth/src/openapi/specs/id-provider.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/auth/src/openapi/specs/id-provider.yaml b/packages/auth/src/openapi/specs/id-provider.yaml index ad5e1afd7f..d8ab1937f7 100644 --- a/packages/auth/src/openapi/specs/id-provider.yaml +++ b/packages/auth/src/openapi/specs/id-provider.yaml @@ -89,6 +89,8 @@ paths: description: Client finish endpoint '401': description: Unauthorized + '404': + description: Not Found description: "To finish the user interaction for grant approval, this endpoint redirects the user to the client's finish url." parameters: - schema: From 76a5d00cc1f9a79529a34a86cf0dbcde193c8f19 Mon Sep 17 00:00:00 2001 From: Vaibhav Raj <68665948+dead8309@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:03:37 +0530 Subject: [PATCH 02/10] feat(backend): make payment retry attempts configurable via env (#3028) - Closes #3017 - rename config variable to a proper name - fix formatting --- packages/backend/src/config/app.ts | 6 +++++- packages/backend/src/index.ts | 1 + .../backend/src/open_payments/payment/outgoing/service.ts | 2 ++ .../backend/src/open_payments/payment/outgoing/worker.ts | 7 ++++--- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 29a3c7070a..46e2d02dff 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -187,7 +187,11 @@ export const Config = { 'INCOMING_PAYMENT_EXPIRY_MAX_MS', 2592000000 ), // 30 days - enableSpspPaymentPointers: envBool('ENABLE_SPSP_PAYMENT_POINTERS', true) + enableSpspPaymentPointers: envBool('ENABLE_SPSP_PAYMENT_POINTERS', true), + maxOutgoingPaymentRetryAttempts: envInt( + 'MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS', + 5 + ) } function parseRedisTlsConfig( diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index dfa64c0ef4..a178e8b345 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -464,6 +464,7 @@ export function initIocContainer( container.singleton('outgoingPaymentService', async (deps) => { return await createOutgoingPaymentService({ + config: await deps.use('config'), logger: await deps.use('logger'), knex: await deps.use('knex'), accountingService: await deps.use('accountingService'), diff --git a/packages/backend/src/open_payments/payment/outgoing/service.ts b/packages/backend/src/open_payments/payment/outgoing/service.ts index 336444e518..2c9ad6043f 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.ts @@ -42,6 +42,7 @@ import { QuoteService } from '../../quote/service' import { isQuoteError } from '../../quote/errors' import { Pagination, SortOrder } from '../../../shared/baseModel' import { FilterString } from '../../../shared/filters' +import { IAppConfig } from '../../../config/app' export interface OutgoingPaymentService extends WalletAddressSubresourceService { @@ -59,6 +60,7 @@ export interface OutgoingPaymentService } export interface ServiceDependencies extends BaseService { + config: IAppConfig knex: TransactionOrKnex accountingService: AccountingService receiverService: ReceiverService diff --git a/packages/backend/src/open_payments/payment/outgoing/worker.ts b/packages/backend/src/open_payments/payment/outgoing/worker.ts index 0df698f960..9ca47b8938 100644 --- a/packages/backend/src/open_payments/payment/outgoing/worker.ts +++ b/packages/backend/src/open_payments/payment/outgoing/worker.ts @@ -10,8 +10,6 @@ import { trace, Span } from '@opentelemetry/api' // First retry waits 10 seconds, second retry waits 20 (more) seconds, etc. export const RETRY_BACKOFF_SECONDS = 10 -const MAX_STATE_ATTEMPTS = 5 - // Returns the id of the processed payment (if any). export async function processPendingPayment( deps_: ServiceDependencies @@ -94,7 +92,10 @@ async function onLifecycleError( const error = typeof err === 'string' ? err : err.message const stateAttempts = payment.stateAttempts + 1 - if (stateAttempts < MAX_STATE_ATTEMPTS && isRetryableError(err)) { + if ( + stateAttempts < deps.config.maxOutgoingPaymentRetryAttempts && + isRetryableError(err) + ) { deps.logger.warn( { state: payment.state, error, stateAttempts }, 'payment lifecycle failed; retrying' From ba7045fd8658fe788f94146705c3b90d39b34600 Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:10:42 -0500 Subject: [PATCH 03/10] docs: update graphql descriptions auth (#3010) * Update descriptions for Auth Admin API - Added new descriptions for empty elements - Revised and refined existing descriptions * Update schema.graphql * Update schema.graphql Add description for the query id argument * Updated to match fields from backend API - Pagination - Filtering - Asset code * Revisions from Max's feedback Changed a few descriptions per Max's PR feedback * chore(auth): update generated types --------- Co-authored-by: Max Kurapov --- .../src/graphql/generated/graphql.schema.json | 120 +++++++++--------- packages/auth/src/graphql/schema.graphql | 109 ++++++++++------ 2 files changed, 128 insertions(+), 101 deletions(-) diff --git a/packages/auth/src/graphql/generated/graphql.schema.json b/packages/auth/src/graphql/generated/graphql.schema.json index 420f2498dc..974e62f145 100644 --- a/packages/auth/src/graphql/generated/graphql.schema.json +++ b/packages/auth/src/graphql/generated/graphql.schema.json @@ -15,7 +15,7 @@ "fields": [ { "name": "actions", - "description": "Access action (create, read, list or complete)", + "description": "Actions allowed with this access.", "args": [], "type": { "kind": "NON_NULL", @@ -35,7 +35,7 @@ }, { "name": "createdAt", - "description": "Date-time of creation", + "description": "The date and time when the access was created.", "args": [], "type": { "kind": "NON_NULL", @@ -51,7 +51,7 @@ }, { "name": "id", - "description": "Access id", + "description": "Unique identifier of the access object.", "args": [], "type": { "kind": "NON_NULL", @@ -67,7 +67,7 @@ }, { "name": "identifier", - "description": "Wallet address of a sub-resource (incoming payment, outgoing payment, or quote)", + "description": "Wallet address of the sub-resource (incoming payment, outgoing payment, or quote).", "args": [], "type": { "kind": "SCALAR", @@ -79,7 +79,7 @@ }, { "name": "limits", - "description": "Payment limits", + "description": "Limits for an outgoing payment associated with this access.", "args": [], "type": { "kind": "OBJECT", @@ -91,7 +91,7 @@ }, { "name": "type", - "description": "Access type (incoming payment, outgoing payment, or quote)", + "description": "Type of access (incoming payment, outgoing payment, or quote).", "args": [], "type": { "kind": "NON_NULL", @@ -135,7 +135,7 @@ "inputFields": [ { "name": "in", - "description": null, + "description": "List of finalization reasons to include in the filter.", "type": { "kind": "LIST", "name": null, @@ -155,7 +155,7 @@ }, { "name": "notIn", - "description": null, + "description": "List of finalization reasons to exclude in the filter.", "type": { "kind": "LIST", "name": null, @@ -186,7 +186,7 @@ "inputFields": [ { "name": "in", - "description": null, + "description": "List of states to include in the filter.", "type": { "kind": "LIST", "name": null, @@ -206,7 +206,7 @@ }, { "name": "notIn", - "description": null, + "description": "List of states to exclude in the filter.", "type": { "kind": "LIST", "name": null, @@ -237,7 +237,7 @@ "inputFields": [ { "name": "in", - "description": null, + "description": "Array of strings to filter by.", "type": { "kind": "LIST", "name": null, @@ -267,7 +267,7 @@ "fields": [ { "name": "access", - "description": "Access details", + "description": "Details of the access provided by the grant.", "args": [], "type": { "kind": "NON_NULL", @@ -291,7 +291,7 @@ }, { "name": "client", - "description": "Wallet address of the grantee's account", + "description": "Wallet address of the grantee's account.", "args": [], "type": { "kind": "NON_NULL", @@ -307,7 +307,7 @@ }, { "name": "createdAt", - "description": "Date-time of creation", + "description": "The date and time when the grant was created.", "args": [], "type": { "kind": "NON_NULL", @@ -323,7 +323,7 @@ }, { "name": "finalizationReason", - "description": "Reason a grant was finalized", + "description": "Specific outcome of a finalized grant, indicating whether the grant was issued, revoked, or rejected.", "args": [], "type": { "kind": "ENUM", @@ -335,7 +335,7 @@ }, { "name": "id", - "description": "Grant id", + "description": "Unique identifier of the grant.", "args": [], "type": { "kind": "NON_NULL", @@ -351,7 +351,7 @@ }, { "name": "state", - "description": "State of the grant", + "description": "Current state of the grant.", "args": [], "type": { "kind": "NON_NULL", @@ -384,7 +384,7 @@ "fields": [ { "name": "cursor", - "description": null, + "description": "A cursor for paginating through the grants.", "args": [], "type": { "kind": "NON_NULL", @@ -400,7 +400,7 @@ }, { "name": "node", - "description": null, + "description": "A grant node in the list.", "args": [], "type": { "kind": "NON_NULL", @@ -428,7 +428,7 @@ "inputFields": [ { "name": "finalizationReason", - "description": null, + "description": "Filter grants by their finalization reason.", "type": { "kind": "INPUT_OBJECT", "name": "FilterFinalizationReason", @@ -440,7 +440,7 @@ }, { "name": "identifier", - "description": null, + "description": "Filter grants by their unique identifier.", "type": { "kind": "INPUT_OBJECT", "name": "FilterString", @@ -452,7 +452,7 @@ }, { "name": "state", - "description": null, + "description": "Filter grants by their state.", "type": { "kind": "INPUT_OBJECT", "name": "FilterGrantState", @@ -477,19 +477,19 @@ "enumValues": [ { "name": "ISSUED", - "description": "grant was issued", + "description": "The grant was issued successfully.", "isDeprecated": false, "deprecationReason": null }, { "name": "REJECTED", - "description": "grant was rejected", + "description": "The grant request was rejected.", "isDeprecated": false, "deprecationReason": null }, { "name": "REVOKED", - "description": "grant was revoked", + "description": "The grant was revoked.", "isDeprecated": false, "deprecationReason": null } @@ -506,25 +506,25 @@ "enumValues": [ { "name": "APPROVED", - "description": "grant was approved", + "description": "The grant request has been approved.", "isDeprecated": false, "deprecationReason": null }, { "name": "FINALIZED", - "description": "grant was finalized and no more access tokens or interactions can be made on it", + "description": "The grant request has been finalized, and no more access tokens or interactions can be made.", "isDeprecated": false, "deprecationReason": null }, { "name": "PENDING", - "description": "grant request is awaiting interaction", + "description": "The grant request is awaiting interaction.", "isDeprecated": false, "deprecationReason": null }, { "name": "PROCESSING", - "description": "grant request is determining what state to enter next", + "description": "The grant request is processing.", "isDeprecated": false, "deprecationReason": null } @@ -538,7 +538,7 @@ "fields": [ { "name": "edges", - "description": null, + "description": "A list of edges representing grants and cursors for pagination.", "args": [], "type": { "kind": "NON_NULL", @@ -562,7 +562,7 @@ }, { "name": "pageInfo", - "description": null, + "description": "Information to aid in pagination.", "args": [], "type": { "kind": "NON_NULL", @@ -609,7 +609,7 @@ "fields": [ { "name": "debitAmount", - "description": "Amount to debit", + "description": "Amount to debit.", "args": [], "type": { "kind": "OBJECT", @@ -621,7 +621,7 @@ }, { "name": "interval", - "description": "Interval between payments", + "description": "Interval between payments.", "args": [], "type": { "kind": "SCALAR", @@ -633,7 +633,7 @@ }, { "name": "receiveAmount", - "description": "Amount to receive", + "description": "Amount to receive.", "args": [], "type": { "kind": "OBJECT", @@ -645,7 +645,7 @@ }, { "name": "receiver", - "description": "Wallet address URL of the receiver", + "description": "Wallet address URL of the receiver.", "args": [], "type": { "kind": "SCALAR", @@ -668,7 +668,7 @@ "fields": [ { "name": "createdAt", - "description": null, + "description": "The date and time when the model was created.", "args": [], "type": { "kind": "NON_NULL", @@ -684,7 +684,7 @@ }, { "name": "id", - "description": null, + "description": "Unique identifier for the model.", "args": [], "type": { "kind": "NON_NULL", @@ -722,7 +722,7 @@ "fields": [ { "name": "revokeGrant", - "description": "Revoke Grant", + "description": "Revoke an existing grant.", "args": [ { "name": "input", @@ -766,7 +766,7 @@ "fields": [ { "name": "endCursor", - "description": "Paginating forwards: the cursor to continue.", + "description": "The cursor used to fetch the next page when paginating forward.", "args": [], "type": { "kind": "SCALAR", @@ -778,7 +778,7 @@ }, { "name": "hasNextPage", - "description": "Paginating forwards: Are there more pages?", + "description": "Indicates if there are more pages when paginating forward.", "args": [], "type": { "kind": "NON_NULL", @@ -794,7 +794,7 @@ }, { "name": "hasPreviousPage", - "description": "Paginating backwards: Are there more pages?", + "description": "Indicates if there are more pages when paginating backward.", "args": [], "type": { "kind": "NON_NULL", @@ -810,7 +810,7 @@ }, { "name": "startCursor", - "description": "Paginating backwards: the cursor to continue.", + "description": "The cursor used to fetch the next page when paginating backward.", "args": [], "type": { "kind": "SCALAR", @@ -833,7 +833,7 @@ "fields": [ { "name": "assetCode", - "description": "[ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD`", + "description": "Should be an ISO 4217 currency code whenever possible, e.g. `USD`. For more information, refer to [assets](https://rafiki.dev/overview/concepts/accounting/#assets).", "args": [], "type": { "kind": "NON_NULL", @@ -849,7 +849,7 @@ }, { "name": "assetScale", - "description": "Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit", + "description": "Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit.", "args": [], "type": { "kind": "NON_NULL", @@ -865,7 +865,7 @@ }, { "name": "value", - "description": null, + "description": "The value of the payment amount.", "args": [], "type": { "kind": "NON_NULL", @@ -892,11 +892,11 @@ "fields": [ { "name": "grant", - "description": "Fetch a grant", + "description": "Fetch a specific grant by its ID.", "args": [ { "name": "id", - "description": null, + "description": "Unique identifier of the grant.", "type": { "kind": "NON_NULL", "name": null, @@ -925,11 +925,11 @@ }, { "name": "grants", - "description": "Fetch a page of grants.", + "description": "Fetch a paginated list of grants.", "args": [ { "name": "after", - "description": "Paginating forwards: the cursor before the the requested page.", + "description": "Forward pagination: Cursor (grant ID) to start retrieving grants after this point.", "type": { "kind": "SCALAR", "name": "String", @@ -941,7 +941,7 @@ }, { "name": "before", - "description": "Paginating backwards: the cursor after the the requested page.", + "description": "Backward pagination: Cursor (grant ID) to start retrieving grants before this point.", "type": { "kind": "SCALAR", "name": "String", @@ -953,7 +953,7 @@ }, { "name": "filter", - "description": "Filter grants based on specific criteria.", + "description": "Filter grants based on specified criteria such as ID, state, or finalization reason.", "type": { "kind": "INPUT_OBJECT", "name": "GrantFilter", @@ -965,7 +965,7 @@ }, { "name": "first", - "description": "Paginating forwards: The first **n** elements from the page.", + "description": "Forward pagination: Limit the result to the first **n** grants after the `after` cursor.", "type": { "kind": "SCALAR", "name": "Int", @@ -977,7 +977,7 @@ }, { "name": "last", - "description": "Paginating backwards: The last **n** elements from the page.", + "description": "Backward pagination: Limit the result to the last **n** grants before the `before` cursor.", "type": { "kind": "SCALAR", "name": "Int", @@ -989,7 +989,7 @@ }, { "name": "sortOrder", - "description": "Ascending or descending order of creation.", + "description": "Specify the sort order of grants based on their creation date, either ascending or descending.", "type": { "kind": "ENUM", "name": "SortOrder", @@ -1026,7 +1026,7 @@ "inputFields": [ { "name": "grantId", - "description": null, + "description": "Unique identifier of the grant to revoke.", "type": { "kind": "NON_NULL", "name": null, @@ -1052,7 +1052,7 @@ "fields": [ { "name": "id", - "description": null, + "description": "Unique identifier of the revoked grant.", "args": [], "type": { "kind": "NON_NULL", @@ -1082,13 +1082,13 @@ "enumValues": [ { "name": "ASC", - "description": "Choose ascending order for results.", + "description": "Sort the results in ascending order.", "isDeprecated": false, "deprecationReason": null }, { "name": "DESC", - "description": "Choose descending order for results.", + "description": "Sort the results in descending order.", "isDeprecated": false, "deprecationReason": null } @@ -1108,7 +1108,7 @@ { "kind": "SCALAR", "name": "UInt8", - "description": null, + "description": "The `UInt8` scalar type represents unsigned 8-bit whole numeric values, ranging from 0 to 255.", "fields": null, "inputFields": null, "interfaces": null, @@ -1118,7 +1118,7 @@ { "kind": "SCALAR", "name": "UInt64", - "description": null, + "description": "The `UInt64` scalar type represents unsigned 64-bit whole numeric values. It is capable of handling values that are larger than the JavaScript `Number` type limit (greater than 2^53).", "fields": null, "inputFields": null, "interfaces": null, diff --git a/packages/auth/src/graphql/schema.graphql b/packages/auth/src/graphql/schema.graphql index a80d651089..efe49d4f07 100644 --- a/packages/auth/src/graphql/schema.graphql +++ b/packages/auth/src/graphql/schema.graphql @@ -1,158 +1,185 @@ type Query { - "Fetch a page of grants." + "Fetch a paginated list of grants." grants( - "Paginating forwards: the cursor before the the requested page." + "Forward pagination: Cursor (grant ID) to start retrieving grants after this point." after: String - "Paginating backwards: the cursor after the the requested page." + "Backward pagination: Cursor (grant ID) to start retrieving grants before this point." before: String - "Paginating forwards: The first **n** elements from the page." + "Forward pagination: Limit the result to the first **n** grants after the `after` cursor." first: Int - "Paginating backwards: The last **n** elements from the page." + "Backward pagination: Limit the result to the last **n** grants before the `before` cursor." last: Int - "Filter grants based on specific criteria." + "Filter grants based on specified criteria such as ID, state, or finalization reason." filter: GrantFilter - "Ascending or descending order of creation." + "Specify the sort order of grants based on their creation date, either ascending or descending." sortOrder: SortOrder ): GrantsConnection! - "Fetch a grant" - grant(id: ID!): Grant! + "Fetch a specific grant by its ID." + grant( + "Unique identifier of the grant." + id: ID! + ): Grant! } type Mutation { - "Revoke Grant" + "Revoke an existing grant." revokeGrant(input: RevokeGrantInput!): RevokeGrantMutationResponse! } type PageInfo { - "Paginating forwards: the cursor to continue." + "The cursor used to fetch the next page when paginating forward." endCursor: String - "Paginating forwards: Are there more pages?" + "Indicates if there are more pages when paginating forward." hasNextPage: Boolean! - "Paginating backwards: Are there more pages?" + "Indicates if there are more pages when paginating backward." hasPreviousPage: Boolean! - "Paginating backwards: the cursor to continue." + "The cursor used to fetch the next page when paginating backward." startCursor: String } type GrantsConnection { + "Information to aid in pagination." pageInfo: PageInfo! + "A list of edges representing grants and cursors for pagination." edges: [GrantEdge!]! } type GrantEdge { + "A grant node in the list." node: Grant! + "A cursor for paginating through the grants." cursor: String! } input GrantFilter { + "Filter grants by their unique identifier." identifier: FilterString + "Filter grants by their state." state: FilterGrantState + "Filter grants by their finalization reason." finalizationReason: FilterFinalizationReason } input FilterString { + "Array of strings to filter by." in: [String!] } input FilterGrantState { + "List of states to include in the filter." in: [GrantState!] + "List of states to exclude in the filter." notIn: [GrantState!] } input FilterFinalizationReason { + "List of finalization reasons to include in the filter." in: [GrantFinalization!] + "List of finalization reasons to exclude in the filter." notIn: [GrantFinalization!] } input RevokeGrantInput { + "Unique identifier of the grant to revoke." grantId: String! } interface Model { + "Unique identifier for the model." id: ID! + "The date and time when the model was created." createdAt: String! } type Grant implements Model { - "Grant id" + "Unique identifier of the grant." id: ID! - "Wallet address of the grantee's account" + "Wallet address of the grantee's account." client: String! - "Access details" + "Details of the access provided by the grant." access: [Access!]! - "State of the grant" + "Current state of the grant." state: GrantState! - "Reason a grant was finalized" + "Specific outcome of a finalized grant, indicating whether the grant was issued, revoked, or rejected." finalizationReason: GrantFinalization - "Date-time of creation" + "The date and time when the grant was created." createdAt: String! } type Access implements Model { - "Access id" + "Unique identifier of the access object." id: ID! - "Wallet address of a sub-resource (incoming payment, outgoing payment, or quote)" + "Wallet address of the sub-resource (incoming payment, outgoing payment, or quote)." identifier: String - "Access type (incoming payment, outgoing payment, or quote)" + "Type of access (incoming payment, outgoing payment, or quote)." type: String! - "Access action (create, read, list or complete)" + "Actions allowed with this access." actions: [String]! - "Payment limits" + "Limits for an outgoing payment associated with this access." limits: LimitData - "Date-time of creation" + "The date and time when the access was created." createdAt: String! } type LimitData { - "Wallet address URL of the receiver" + "Wallet address URL of the receiver." receiver: String - "Amount to debit" + "Amount to debit." debitAmount: PaymentAmount - "Amount to receive" + "Amount to receive." receiveAmount: PaymentAmount - "Interval between payments" + "Interval between payments." interval: String } type PaymentAmount { + "The value of the payment amount." value: UInt64! - "[ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD`" + "Should be an ISO 4217 currency code whenever possible, e.g. `USD`. For more information, refer to [assets](https://rafiki.dev/overview/concepts/accounting/#assets)." assetCode: String! - "Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit" + "Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit." assetScale: UInt8! } type RevokeGrantMutationResponse { + "Unique identifier of the revoked grant." id: ID! } enum GrantState { - "grant request is determining what state to enter next" + "The grant request is processing." PROCESSING - "grant request is awaiting interaction" + "The grant request is awaiting interaction." PENDING - "grant was approved" + "The grant request has been approved." APPROVED - "grant was finalized and no more access tokens or interactions can be made on it" + "The grant request has been finalized, and no more access tokens or interactions can be made." FINALIZED } enum GrantFinalization { - "grant was issued" + "The grant was issued successfully." ISSUED - "grant was revoked" + "The grant was revoked." REVOKED - "grant was rejected" + "The grant request was rejected." REJECTED } enum SortOrder { - "Choose ascending order for results." + "Sort the results in ascending order." ASC - "Choose descending order for results." + "Sort the results in descending order." DESC } +""" +The `UInt8` scalar type represents unsigned 8-bit whole numeric values, ranging from 0 to 255. +""" scalar UInt8 + +""" +The `UInt64` scalar type represents unsigned 64-bit whole numeric values. It is capable of handling values that are larger than the JavaScript `Number` type limit (greater than 2^53). +""" scalar UInt64 From db67759e24b344ea6c721bda8dd7f62c150b346a Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:00:13 -0500 Subject: [PATCH 04/10] updating table styles (#3038) - Added custom table wrapper to larger tables and tables with code-formatted text - Typo fix on docker-compose page - Diagram was too large for local playground page, added LargeImg component --- .../content/docs/admin/manage-liquidity.mdx | 36 +++++++++++++++++++ .../src/content/docs/admin/manage-peering.mdx | 4 +++ .../docs/integration/playground/overview.mdx | 9 +++-- .../docs/integration/prod/docker-compose.mdx | 10 ++++-- .../requirements/exchange-rates.mdx | 8 +++++ .../docs/integration/requirements/idp.mdx | 4 +++ .../integration/requirements/sending-fees.mdx | 4 +++ .../requirements/wallet-addresses.mdx | 8 +++++ .../requirements/webhook-events.mdx | 36 +++++++++++++++++++ .../docs/overview/concepts/accounting.mdx | 4 +++ .../docs/overview/concepts/telemetry.mdx | 2 +- .../docs/resources/webhook-event-types.mdx | 4 +++ 12 files changed, 123 insertions(+), 6 deletions(-) diff --git a/packages/documentation/src/content/docs/admin/manage-liquidity.mdx b/packages/documentation/src/content/docs/admin/manage-liquidity.mdx index 906ff80d44..a5963dc761 100644 --- a/packages/documentation/src/content/docs/admin/manage-liquidity.mdx +++ b/packages/documentation/src/content/docs/admin/manage-liquidity.mdx @@ -52,6 +52,8 @@ mutation DepositAssetLiquidity($input: DepositAssetLiquidityInput!) { +
+ | Variable | Description | | ---------------- | -------------------------------------------------------------------------- | | `assetID` | The id of the asset to deposit liquidity into | @@ -59,6 +61,8 @@ mutation DepositAssetLiquidity($input: DepositAssetLiquidityInput!) { | `id` | The id of the transfer (deposit) | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | +
+ If the asset liquidity deposit was successful, `DepositAssetLiquidity` returns `true`. ### Withdraw asset liquidity using the `CreateAssetLiquidityWithdrawal` mutation @@ -98,6 +102,8 @@ mutation CreateAssetLiquidityWithdrawal( +
+ | Variable | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | The id of the transfer (withdrawal) | @@ -106,6 +112,8 @@ mutation CreateAssetLiquidityWithdrawal( | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | | `timeoutSeconds` | Interval, in seconds, after a pending transfer is initiated at which it can be posted or voided (zero denotes a no timeout, single-phase posted transfer) | +
+ If the asset liquidity withdrawal was successful, `CreateAssetLiquidityWithdrawal` returns `true`. ### Deposit and withdraw asset liquidity using Rafiki Admin @@ -154,6 +162,8 @@ mutation DepositPeerLiquidity($input: DepositPeerLiquidityInput!) { +
+ | Variable | Description | | ---------------- | -------------------------------------------------------------------------- | | `id` | The id of the transfer (deposit) | @@ -161,6 +171,8 @@ mutation DepositPeerLiquidity($input: DepositPeerLiquidityInput!) { | `amount` | Amount of liquidity to deposit | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | +
+ If the peer liquidity deposit was successful, `DepositPeerLiquidity` returns `true`. ### Withdraw peer liquidity using the `CreatePeerLiquidityWithdrawal` mutation @@ -200,6 +212,8 @@ mutation CreatePeerLiquidityWithdrawal( +
+ | Variable | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | The id of the transfer (withdrawal) | @@ -208,6 +222,8 @@ mutation CreatePeerLiquidityWithdrawal( | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | | `timeoutSeconds` | Interval, in seconds, after a pending transfer is initiated at which it can be posted or voided (zero denotes a no timeout, single-phase posted transfer) | +
+ If the peer liquidity withdrawal was successful, `CreatePeerLiquidityWithdrawal` returns `true`. ### Deposit and withdraw peer liquidity using Rafiki Admin @@ -253,12 +269,16 @@ mutation CreateIncomingPaymentWithdrawal( +
+ | Variable | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `incomingPaymentId` | The id of the incoming payment to withdraw from | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | | `timeoutSeconds` | Interval, in seconds, after a pending transfer is initiated at which it can be posted or voided (zero denotes a no timeout, single-phase posted transfer) | +
+ If the incoming payment liquidity withdrawal was successful, `CreateIncomingPaymentWithdrawal` returns `true`. ### Deposit outgoing payment liquidity using the `DepositOutgoingPaymentLiquidity` mutation @@ -295,11 +315,15 @@ mutation DepositOutgoingPaymentLiquidity( +
+ | Variable | Description | | ------------------- | -------------------------------------------------------------------------- | | `outgoingPaymentId` | The id of the outgoing payment to deposit into | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | +
+ If the outgoing payment liquidity deposit was successful, `DepositOutgoingPaymentLiquidity` returns `true`. ### Withdraw outgoing payment liquidity using the `CreateOutgoingPaymentWithdrawal` mutation @@ -337,12 +361,16 @@ mutation CreateOutgoingPaymentWithdrawal( +
+ | Variable | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `outgoingPaymentId` | The id of the outgoing payment to withdraw from | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | | `timeoutSeconds` | Interval, in seconds, after a pending transfer is initiated at which it can be posted or voided (zero denotes a no timeout, single-phase posted transfer) | +
+ If the outgoing payment liquidity withdrawal was successful, `CreateOutgoingPaymentWithdrawal` returns `true`. ## Two-phase withdrawals @@ -393,11 +421,15 @@ mutation PostLiquidityWithdrawal($input: PostLiquidityWithdrawalInput!) { +
+ | Variable | Description | | ---------------- | -------------------------------------------------------------------------- | | `withdrawalId` | The id of the liquidity withdrawal to post | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | +
+ If the liquidity withdrawal was successfully posted, `PostLiquidityWithdrawal` returns `true`. ### Void and rollback an unsuccessful transfer using the `VoidLiquidityWithdrawal` mutation @@ -432,9 +464,13 @@ mutation VoidLiquidityWithdrawal($input: VoidLiquidityWithdrawalInput!) { +
+ | Variable | Description | | ---------------- | -------------------------------------------------------------------------- | | `withdrawalId` | The id of the liquidity withdrawal to void | | `idempotencyKey` | Unique key to ensure duplicate or retried requests are processed only once | +
+ If the liquidity withdrawal was successfully voided and rolled back, `VoidLiquidityWithdrawal` returns `true`. diff --git a/packages/documentation/src/content/docs/admin/manage-peering.mdx b/packages/documentation/src/content/docs/admin/manage-peering.mdx index 6fab621d7d..c01db8b25f 100644 --- a/packages/documentation/src/content/docs/admin/manage-peering.mdx +++ b/packages/documentation/src/content/docs/admin/manage-peering.mdx @@ -66,6 +66,8 @@ mutation CreatePeer($input: CreatePeerInput!) { +
+ | Variable | Description | Required | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------- | | `assetID` | The ID of the asset that you and your peer will use to ultimately settle your net obligations outside of Interledger. | Y | @@ -76,6 +78,8 @@ mutation CreatePeer($input: CreatePeerInput!) { | `http.outgoing.authtoken` | The token that you will use to present to your peer and connect to and send packets to their Rafiki instance. | Y | | `initialLiquidity` | Initial amount of liquidity to deposit for peer. Liquidity can also be deposited using the `DepositPeerLiquidity` mutation. | N | +
+ ```json diff --git a/packages/documentation/src/content/docs/integration/playground/overview.mdx b/packages/documentation/src/content/docs/integration/playground/overview.mdx index cbc4caedf6..0a66edb1b2 100644 --- a/packages/documentation/src/content/docs/integration/playground/overview.mdx +++ b/packages/documentation/src/content/docs/integration/playground/overview.mdx @@ -2,6 +2,8 @@ title: Overview --- +import { LargeImg } from '@interledger/docs-design-system' + import { LinkOut } from '@interledger/docs-design-system' import { CodeBlock } from '@interledger/docs-design-system' @@ -10,6 +12,8 @@ The Local Playground provides a suite of packages that, together, mock an accoun This suite of packages includes: +
+ | Package name | Services | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | [`backend`](/integration/services/backend-service) |
  • SPSP
  • Open Payments APIs
  • GraphQL Admin APIs
  • STREAM endpoint
| @@ -17,6 +21,8 @@ This suite of packages includes: | `mock-account-servicing-entity` | mocks an account servicing entity | | [`frontend`](/integration/services/frontend-service) | Remix app to expose a UI for Rafiki admin management via interaction with the Backend Admin APIs | +
+ These packages depend on the following databases: Overview of components #### Mock account servicing entity 1 - Cloud Nine Wallet diff --git a/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx b/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx index 4defcbd457..c0c3fd7970 100644 --- a/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx +++ b/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx @@ -6,7 +6,7 @@ This is an example deployment of Rafiki on a Linux virtual machine using nginx a ## Virtual Machine preparation -Deploy the virutal machine: +Deploy the virtual machine: ```sh sudo apt update && sudo apt install nginx certbot python3-certbot-nginx @@ -14,7 +14,7 @@ sudo apt update && sudo apt install nginx certbot python3-certbot-nginx ## Domain preparation -Generate the Let’s encrypt certificates: +Generate the Let's Encrypt certificates: ```sh certbot certonly --manual --preferred-challenges=dns --email EMAIL --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d DOMAIN @@ -24,7 +24,7 @@ certbot certonly --manual --preferred-challenges=dns --email EMAIL --server http Domain can be in wildcard format. You will also need to update the TXT record in this step. ::: -As Let's encrypt certificates are valid for 90 days, you must set up a cron process to renew the certificate on a regular schedule: +As Let's Encrypt certificates are valid for 90 days, you must set up a cron process to renew the certificate on a regular schedule: ```sh crontab -e @@ -36,6 +36,8 @@ crontab -e Next update the DNS records to point to the static external IP address of the volumes: +
+ | service | URL | example | | --------- | ---------------- | ---------------------- | | admin | admin.DOMAIN | admin.myrafiki.com | @@ -43,6 +45,8 @@ Next update the DNS records to point to the static external IP address of the vo | connector | connector.DOMAIN | connector.myrafiki.com | | ilp | ilp.DOMAIN | ilp.myrafiki.com | +
+ ## Server preparation Create nginx configuration files for every exposed domain: diff --git a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx index 5f5e06f150..fa36104107 100644 --- a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx @@ -48,12 +48,16 @@ GET https://cloud-nine-wallet/rates ### Response objects +
+ | Variable | Type | Description | Required | | -------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------- | | `base` | String | The asset code represented as an ISO 4217 currency code, e.g. USD | Y | | `rates` | Object | Object containing `` pairs, e.g. `{EUR: 0.8930}` | Y | | `rates.` | Number | The exchange rate given `base` and `` | Y | +
+ ## Specify rate caching duration (optional) Specify how long your Rafiki instance will cache exchange rates via the `backend` service's `EXCHANGE_RATES_LIFETIME` variable or use the default setting of `15_000` ms (15 seconds). @@ -98,8 +102,12 @@ export function loader({ request }: LoaderFunctionArgs) { ## Environment variables +
+ | Variable | Type | Description | Required | | ------------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- | | `EXCHANGE_RATES_URL` | `backend` | Your exchange rates endpoint | Y | | `EXCHANGE_RATES_LIFETIME` | `backend` | The amount of time Rafiki caches exchange rates, in ms | Y | | `SLIPPAGE` | `backend` | The variance allowed between a quote and the actual amount required when a payment is initiated | Y | + +
\ No newline at end of file diff --git a/packages/documentation/src/content/docs/integration/requirements/idp.mdx b/packages/documentation/src/content/docs/integration/requirements/idp.mdx index f9c6676e67..7d8a30a691 100644 --- a/packages/documentation/src/content/docs/integration/requirements/idp.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/idp.mdx @@ -67,6 +67,8 @@ https://idp.wallet.example.com/interact/{id}/{nonce}` The endpoints are called in the sequence listed in the table below. +
+ | Method | Endpoint | Purpose | | ---------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- | | | `/interact/{id}/{nonce}` | [Start user interaction session](#start-user-interaction-session) | @@ -75,6 +77,8 @@ The endpoints are called in the sequence listed in the table below. | | `/interact/{id}/{nonce}/finish` | [Finish user interaction](#finish-interaction)
| | | `/interact/{id}/{nonce}` | [Continue grant](#continue-grant) | +
+ We also provide an OpenAPI specification that describes the endpoints. #### Start user interaction session diff --git a/packages/documentation/src/content/docs/integration/requirements/sending-fees.mdx b/packages/documentation/src/content/docs/integration/requirements/sending-fees.mdx index a37bf8548e..ba6975ba6a 100644 --- a/packages/documentation/src/content/docs/integration/requirements/sending-fees.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/sending-fees.mdx @@ -52,6 +52,8 @@ Let's assume your asset scale is 2. You'll charge a fixed fee of 100 (\$1.00) an
+
+ | Variable | Description | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `assetId` | The asset's unique ID assigned by Rafiki when the asset was created. | @@ -59,6 +61,8 @@ Let's assume your asset scale is 2. You'll charge a fixed fee of 100 (\$1.00) an | `fixed` | The amount of the flat, fixed fee to charge. Assuming USD with an asset scale of 2 in the example above, the value of `100` equals \$1.00. | | `basisPoints` | The amount of the variable fee to charge based on the total amount. One basis point is equal to 0.01% of the total amount. `100` basis points equals 1%, and `10000` basis points equals 100%. In the example above, the fee is 1%. | +
+ ```json diff --git a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx index 045e5efac1..d62b53e7ea 100644 --- a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx @@ -81,6 +81,8 @@ We strongly recommend you store at least the `walletAddress.id` in your internal +
+ | Variable | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `assetId` | The unique ID of the asset, assigned by Rafiki when the asset was created, that the wallet address's underlying payment account is denominated in | @@ -88,6 +90,8 @@ We strongly recommend you store at least the `walletAddress.id` in your internal | `url` | The wallet address's case-insensitive URL | | `additionalProperties` | Optional [additional properties](/apis/graphql/backend/inputobjects/#additionalpropertyinput) associated with the wallet address | +
+ ```json @@ -187,12 +191,16 @@ The request is a standard request to create a JSON Web Key (JWK), which is a JSO Open Payments requires the following values. +
+ | Parameter | Required value | Description | | --------- | -------------- | ----------------------------------------------------------------------------- | | `alg` | `EdDSA` | The algorithm used to generate the key pair | | `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key | | `crv` | `Ed25519` | The cryptographic curve used with the key | +
+ Additionally, the request must contain the `walletAddressId` of the wallet address that the key pair will be associated with. diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index e2125e6a86..6140fc887e 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -26,20 +26,28 @@ For Rafiki to notify you about webhook events, you must expose a webhook endpoin When an event occurs, the [`backend`](/integration/services/backend-service) service makes a request to your configured webhook endpoint. The `backend` service expects a `200` status in return. +
+ | Variable | Type | Description | | ------------- | --------- | ------------------------------------------------------------------- | | `WEBHOOK_URL` | `backend` | The endpoint to where requests are made when a webhook event occurs | +
+ ## Webhook event request body Each webhook event is sent as a JSON payload with the following structure in the request body. The parameters within the `data` object will vary depending on the event. +
+ | Attribute | Type | Description | Required | | --------- | ------ | --------------------------------------------------- | -------- | | `id` | String | UUID for the event | Y | | `type` | Enum | The `EventType` | Y | | `data` | Object | Additional data that coincides with the `EventType` | Y | +
+ :::tip[Duplicate events] The `id` in the webhook event payload is unique. Your system can use the ID to determine whether the event has been received previously, preventing duplicate event processing. ::: @@ -143,21 +151,29 @@ If a non-200 status is returned, indicating an error, or the request times out, The first retry is after 10 seconds. Additional retries occur after 20 more seconds, then after 30 more seconds, and so on. +
+ | Variable | Type | Description | | ------------------- | --------- | --------------------------------------------------------------------------------------------------------------- | | `WEBHOOK_TIMEOUT` | `backend` | The amount of time, in milliseconds, after which a webhook request will time out | | `WEBHOOK_MAX_RETRY` | `backend` | The maximum number of retries for a webhook event when a non-200 status is returned or if the request timed out | +
+ ## Webhook events ### Incoming payments +
+ | Event type | Description | | ----------------------------------------------------------- | --------------------------------------------------------------------------------- | | [`incoming_payment.created`](#incoming-payment-created) | An incoming payment has been created | | [`incoming_payment.completed`](#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds | | [`incoming_payment.expired`](#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds | +
+ #### Incoming payment created @@ -251,12 +267,16 @@ In some scenarios, a sender may not have specified an `incomingAmount` when the ### Outgoing payments +
+ | Event type | Description | | ----------------------------------------------------------- | -------------------------------------------------- | | [`outgoing_payment.created`](#outgoing-payment-created) | An outgoing payment has been created | | [`outgoing_payment.completed`](#outgoing-payment-completed) | An outgoing payment has completed | | [`outgoing_payment.failed`](#outgoing-payment-failed) | An outgoing payment partially or completely failed | +
+ #### Outgoing payment created @@ -353,11 +373,15 @@ The `outgoing_payment.failed` event indicates that an outgoing payment has eithe ### Wallet addresses +
+ | Event type | Description | | --------------------------------------------------------------------- | ------------------------------------------------------------------ | | [`wallet_address.not_found`](#wallet-address-not-found) | The requested wallet address was not found on this Rafiki instance | | [`wallet_address.web_monetization`](#wallet-address-web-monetization) | Web Monetization payments have been received via STREAM | +
+ #### Wallet address not found @@ -381,10 +405,14 @@ The `wallet_address.not_found` event indicates that a wallet address was request When you receive this event, look up the associated account in your system and create a wallet address for the account. The initial wallet address request will succeed if you create it within your configured `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` time frame. +
+ | Environment variable | Type | Description | | ---------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- | | `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | `backend` | The time in milliseconds that you have to create a missing wallet address before the initial request times out | +
+ #### Wallet address Web Monetization @@ -409,10 +437,14 @@ The `wallet_address.web_monetization` event indicates that a wallet address rece ### Low asset liquidity +
+ | Event type | Description | | --------------------------------------------- | ------------------------------------------------------------- | | [`asset.liquidity_low`](#asset-liquidity-low) | Your asset liquidity has dropped below your defined threshold | +
+ #### Asset liquidity low @@ -436,10 +468,14 @@ The `asset.liquidity_low` event indicates that an asset's liquidity has dropped ### Low peer liquidity +
+ | Event type | Description | | ------------------------------------------- | ------------------------------------------------------------ | | [`peer.liquidity_low`](#peer-liquidity-low) | Your peer liquidity has dropped below your defined threshold | +
+ #### Peer liquidity low diff --git a/packages/documentation/src/content/docs/overview/concepts/accounting.mdx b/packages/documentation/src/content/docs/overview/concepts/accounting.mdx index 3c74bed597..c7430a16a7 100644 --- a/packages/documentation/src/content/docs/overview/concepts/accounting.mdx +++ b/packages/documentation/src/content/docs/overview/concepts/accounting.mdx @@ -38,12 +38,16 @@ An asset represents an item of value that can be transferred via the Interledger In Rafiki, the `asset` type is comprised of the following properties. +
+ | Property | Type | Description | Example | | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------- | ------- | | `value ` | BigInt | A numerical amount | `10000` | | `assetCode` | String | Should be an ISO 4217 currency code whenever possible | `"USD"` | | `assetScale` | Integer | Difference in order of magnitude between the standard unit and a fractional unit | `2` | +
+ To convert an asset amount into a currency amount that's easier to read, apply the following formula: $currencyAmount = \frac{value}{10^{assetScale}}$ diff --git a/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx b/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx index d9e7da79b8..1386ce6af7 100644 --- a/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx +++ b/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx @@ -161,7 +161,7 @@ You must deploy your own OTEL Collector that acts as a sidecar container to Rafi ### Telemetry environment variables -
+
| Variable name | Type | Description | Required | | -------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | diff --git a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx index 4291ef7fa9..20d28aaf72 100644 --- a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx +++ b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx @@ -6,6 +6,8 @@ Webhooks notify you of specific events that occur within your Rafiki instance al The following is an enumeration of all of Rafiki's [webhook event](/integration/requirements/webhook-events) types along with their descriptions, which you must listen to and handle. +
+ | Value | Description | | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [`incoming_payment.created`](/integration/requirements/webhook-events/#incoming-payment-created) | An incoming payment has been created. | @@ -18,3 +20,5 @@ The following is an enumeration of all of Rafiki's [webhook event](/integration/ | [`wallet_address.web_monetization`](/integration/requirements/webhook-events/#wallet-address-web-monetization) | Web Monetization payments received via STREAM. | | [`asset.liquidity_low`](/integration/requirements/webhook-events/#asset-liquidity-low) | Asset liquidity has dropped below defined threshold. | | [`peer.liquidity_low`](/integration/requirements/webhook-events/#peer-liquidity-low) | Peer liquidity has dropped below defined threshold. | + +
\ No newline at end of file From 66741c2240a2195fa22a3b314c6cc183ab281529 Mon Sep 17 00:00:00 2001 From: Chen Hui Jing <1461498+huijing@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:20:55 +0800 Subject: [PATCH 05/10] fix: add border to overflow tables (#3040) --- packages/documentation/src/styles/rafiki.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/documentation/src/styles/rafiki.css b/packages/documentation/src/styles/rafiki.css index 10cff423be..d648e2bd40 100644 --- a/packages/documentation/src/styles/rafiki.css +++ b/packages/documentation/src/styles/rafiki.css @@ -98,6 +98,8 @@ 0 0, 100%; background-attachment: local, local, scroll, scroll; + box-shadow: var(--sl-shadow-sm); + border-radius: var(--border-radius); } .overflow-table thead th { From 6b971bf4636bbc658530e8447c52866054db2aec Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:13:35 -0500 Subject: [PATCH 06/10] docs: added new backend environment variable (#3039) * Added new backend environment variable Added MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS, default value, and description to the docs * prettier ran prettier --- .../src/content/docs/admin/admin-user-guide.mdx | 3 ++- .../src/content/docs/integration/playground/overview.mdx | 5 +---- .../content/docs/integration/requirements/exchange-rates.mdx | 2 +- .../docs/integration/requirements/wallet-addresses.mdx | 3 ++- .../src/content/docs/resources/webhook-event-types.mdx | 2 +- packages/documentation/src/partials/backend-variables.mdx | 1 + 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx index 49dda0827d..cc9fe587ce 100644 --- a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx +++ b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx @@ -249,9 +249,10 @@ Fill out the following fields to create a new wallet address: | | Asset | Select an asset to associate with this wallet. | :::note[Wallet address requirements] + - At least one asset has to be created prior to creating a new wallet address. Refer to [Create asset](#create-asset) for more information. - Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical. -::: + ::: After completing this section, select **Create** to add the new wallet address. diff --git a/packages/documentation/src/content/docs/integration/playground/overview.mdx b/packages/documentation/src/content/docs/integration/playground/overview.mdx index 0a66edb1b2..59c7d41c25 100644 --- a/packages/documentation/src/content/docs/integration/playground/overview.mdx +++ b/packages/documentation/src/content/docs/integration/playground/overview.mdx @@ -83,10 +83,7 @@ The secondary Happy Life Bank docker-compose file (`./happy-life-bank/docker-com The following components are made available via the Local Playground: - + #### Mock account servicing entity 1 - Cloud Nine Wallet diff --git a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx index fa36104107..702448c80c 100644 --- a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx @@ -110,4 +110,4 @@ export function loader({ request }: LoaderFunctionArgs) { | `EXCHANGE_RATES_LIFETIME` | `backend` | The amount of time Rafiki caches exchange rates, in ms | Y | | `SLIPPAGE` | `backend` | The variance allowed between a quote and the actual amount required when a payment is initiated | Y | -
\ No newline at end of file +
diff --git a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx index d62b53e7ea..47cd4b7cac 100644 --- a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx @@ -8,9 +8,10 @@ import { CodeBlock } from '@interledger/docs-design-system' Each payment account belonging to your users (e.g., customers) must have at least one associated wallet address for the account to be able to send and receive payments over Interledger and Open Payments. A wallet address serves as a publicly shareable standardized ID for a payment account. :::note[Wallet address requirements] + - Your Rafiki instance must be set up for at least one asset before wallet addresses can be created as each wallet address must have an asset assigned to it. - Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical. -::: + ::: ## Create wallet addresses diff --git a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx index 20d28aaf72..0bad323af4 100644 --- a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx +++ b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx @@ -21,4 +21,4 @@ The following is an enumeration of all of Rafiki's [webhook event](/integration/ | [`asset.liquidity_low`](/integration/requirements/webhook-events/#asset-liquidity-low) | Asset liquidity has dropped below defined threshold. | | [`peer.liquidity_low`](/integration/requirements/webhook-events/#peer-liquidity-low) | Peer liquidity has dropped below defined threshold. | - \ No newline at end of file + diff --git a/packages/documentation/src/partials/backend-variables.mdx b/packages/documentation/src/partials/backend-variables.mdx index 5f4d4d41ae..08ae12bf44 100644 --- a/packages/documentation/src/partials/backend-variables.mdx +++ b/packages/documentation/src/partials/backend-variables.mdx @@ -24,6 +24,7 @@ import { LinkOut } from '@interledger/docs-design-system' | `INSTANCE_NAME` | | `undefined` | this Rafiki instance's name used to communicate for [auto-peering](/integration/playground/autopeering/) | | `KEY_ID` | backend.key.id | `undefined` | this Rafiki instance's client key id | | `LOG_LEVEL` | backend.logLevel | `info` | [Pino Log Level](https://getpino.io/#/docs/api?id=levels) | +| `MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS` | | `5` | Maximum number of retry attempts for an outgoing payment before it is considered failed | | `NODE_ENV` | backend.nodeEnv | `development` | node environment, `development`, `test`, or `production` | | `OPEN_PAYMENTS_PORT` | backend.port.openPayments | `3003` | port of the Open Payments resource server port | | `OPEN_PAYMENTS_URL` | backend.serviceUrls.OPEN_PAYMENTS_URL | `undefined` | public endpoint of this Open Payments Resource Server | From e48c2798e4b1c70e9d78904e65d3bb2c954b47f4 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 24 Oct 2024 18:54:31 +0200 Subject: [PATCH 07/10] chore(auth): fix generated types (#3045) --- .../auth/src/graphql/generated/graphql.ts | 87 +++++++++++-------- packages/auth/src/graphql/schema.graphql | 5 +- 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/packages/auth/src/graphql/generated/graphql.ts b/packages/auth/src/graphql/generated/graphql.ts index dfc10637f5..3926b739d1 100644 --- a/packages/auth/src/graphql/generated/graphql.ts +++ b/packages/auth/src/graphql/generated/graphql.ts @@ -14,114 +14,130 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + /** The `UInt8` scalar type represents unsigned 8-bit whole numeric values, ranging from 0 to 255. */ UInt8: { input: any; output: any; } + /** The `UInt64` scalar type represents unsigned 64-bit whole numeric values. It is capable of handling values that are larger than the JavaScript `Number` type limit (greater than 2^53). */ UInt64: { input: any; output: any; } }; export type Access = Model & { __typename?: 'Access'; - /** Access action (create, read, list or complete) */ + /** Actions allowed with this access. */ actions: Array>; - /** Date-time of creation */ + /** The date and time when the access was created. */ createdAt: Scalars['String']['output']; - /** Access id */ + /** Unique identifier of the access object. */ id: Scalars['ID']['output']; - /** Wallet address of a sub-resource (incoming payment, outgoing payment, or quote) */ + /** Wallet address of the sub-resource (incoming payment, outgoing payment, or quote). */ identifier?: Maybe; - /** Payment limits */ + /** Limits for an outgoing payment associated with this access. */ limits?: Maybe; - /** Access type (incoming payment, outgoing payment, or quote) */ + /** Type of access (incoming payment, outgoing payment, or quote). */ type: Scalars['String']['output']; }; export type FilterFinalizationReason = { + /** List of finalization reasons to include in the filter. */ in?: InputMaybe>; + /** List of finalization reasons to exclude in the filter. */ notIn?: InputMaybe>; }; export type FilterGrantState = { + /** List of states to include in the filter. */ in?: InputMaybe>; + /** List of states to exclude in the filter. */ notIn?: InputMaybe>; }; export type FilterString = { + /** Array of strings to filter by. */ in?: InputMaybe>; }; export type Grant = Model & { __typename?: 'Grant'; - /** Access details */ + /** Details of the access provided by the grant. */ access: Array; - /** Wallet address of the grantee's account */ + /** Wallet address of the grantee's account. */ client: Scalars['String']['output']; - /** Date-time of creation */ + /** The date and time when the grant was created. */ createdAt: Scalars['String']['output']; - /** Reason a grant was finalized */ + /** Specific outcome of a finalized grant, indicating whether the grant was issued, revoked, or rejected. */ finalizationReason?: Maybe; - /** Grant id */ + /** Unique identifier of the grant. */ id: Scalars['ID']['output']; - /** State of the grant */ + /** Current state of the grant. */ state: GrantState; }; export type GrantEdge = { __typename?: 'GrantEdge'; + /** A cursor for paginating through the grants. */ cursor: Scalars['String']['output']; + /** A grant node in the list. */ node: Grant; }; export type GrantFilter = { + /** Filter grants by their finalization reason. */ finalizationReason?: InputMaybe; + /** Filter grants by their unique identifier. */ identifier?: InputMaybe; + /** Filter grants by their state. */ state?: InputMaybe; }; export enum GrantFinalization { - /** grant was issued */ + /** The grant was issued successfully. */ Issued = 'ISSUED', - /** grant was rejected */ + /** The grant request was rejected. */ Rejected = 'REJECTED', - /** grant was revoked */ + /** The grant was revoked. */ Revoked = 'REVOKED' } export enum GrantState { - /** grant was approved */ + /** The grant request has been approved. */ Approved = 'APPROVED', - /** grant was finalized and no more access tokens or interactions can be made on it */ + /** The grant request has been finalized, and no more access tokens or interactions can be made. */ Finalized = 'FINALIZED', - /** grant request is awaiting interaction */ + /** The grant request is awaiting interaction. */ Pending = 'PENDING', - /** grant request is determining what state to enter next */ + /** The grant request is processing. */ Processing = 'PROCESSING' } export type GrantsConnection = { __typename?: 'GrantsConnection'; + /** A list of edges representing grants and cursors for pagination. */ edges: Array; + /** Information to aid in pagination. */ pageInfo: PageInfo; }; export type LimitData = { __typename?: 'LimitData'; - /** Amount to debit */ + /** Amount to debit. */ debitAmount?: Maybe; - /** Interval between payments */ + /** Interval between payments. */ interval?: Maybe; - /** Amount to receive */ + /** Amount to receive. */ receiveAmount?: Maybe; - /** Wallet address URL of the receiver */ + /** Wallet address URL of the receiver. */ receiver?: Maybe; }; export type Model = { + /** The date and time when the model was created. */ createdAt: Scalars['String']['output']; + /** Unique identifier for the model. */ id: Scalars['ID']['output']; }; export type Mutation = { __typename?: 'Mutation'; - /** Revoke Grant */ + /** Revoke an existing grant. */ revokeGrant: RevokeGrantMutationResponse; }; @@ -132,30 +148,31 @@ export type MutationRevokeGrantArgs = { export type PageInfo = { __typename?: 'PageInfo'; - /** Paginating forwards: the cursor to continue. */ + /** The cursor used to fetch the next page when paginating forward. */ endCursor?: Maybe; - /** Paginating forwards: Are there more pages? */ + /** Indicates if there are more pages when paginating forward. */ hasNextPage: Scalars['Boolean']['output']; - /** Paginating backwards: Are there more pages? */ + /** Indicates if there are more pages when paginating backward. */ hasPreviousPage: Scalars['Boolean']['output']; - /** Paginating backwards: the cursor to continue. */ + /** The cursor used to fetch the next page when paginating backward. */ startCursor?: Maybe; }; export type PaymentAmount = { __typename?: 'PaymentAmount'; - /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ + /** Should be an ISO 4217 currency code whenever possible, e.g. `USD`. For more information, refer to [assets](https://rafiki.dev/overview/concepts/accounting/#assets). */ assetCode: Scalars['String']['output']; - /** Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit */ + /** Difference in orders of magnitude between the standard unit of an asset and a corresponding fractional unit. */ assetScale: Scalars['UInt8']['output']; + /** The value of the payment amount. */ value: Scalars['UInt64']['output']; }; export type Query = { __typename?: 'Query'; - /** Fetch a grant */ + /** Fetch a specific grant by its ID. */ grant: Grant; - /** Fetch a page of grants. */ + /** Fetch a paginated list of grants. */ grants: GrantsConnection; }; @@ -175,18 +192,20 @@ export type QueryGrantsArgs = { }; export type RevokeGrantInput = { + /** Unique identifier of the grant to revoke. */ grantId: Scalars['String']['input']; }; export type RevokeGrantMutationResponse = { __typename?: 'RevokeGrantMutationResponse'; + /** Unique identifier of the revoked grant. */ id: Scalars['ID']['output']; }; export enum SortOrder { - /** Choose ascending order for results. */ + /** Sort the results in ascending order. */ Asc = 'ASC', - /** Choose descending order for results. */ + /** Sort the results in descending order. */ Desc = 'DESC' } diff --git a/packages/auth/src/graphql/schema.graphql b/packages/auth/src/graphql/schema.graphql index efe49d4f07..0511ff0add 100644 --- a/packages/auth/src/graphql/schema.graphql +++ b/packages/auth/src/graphql/schema.graphql @@ -16,10 +16,7 @@ type Query { ): GrantsConnection! "Fetch a specific grant by its ID." - grant( - "Unique identifier of the grant." - id: ID! - ): Grant! + grant("Unique identifier of the grant." id: ID!): Grant! } type Mutation { From 75518e84db606e8df46b07b139981ef525dbad14 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 24 Oct 2024 22:14:19 +0200 Subject: [PATCH 08/10] chore(backend): dont wait 30s when completing/expiring incoming payment (#3043) * chore(backend): dont wait 30s when completing/expiring incoming payment * chore(backend): clean up busy logs * chore(auth): correct formatting * test(backend): updating tests --- .../open_payments/payment/incoming/model.ts | 3 +-- .../payment/incoming/service.test.ts | 26 ++++++++++--------- .../open_payments/payment/incoming/service.ts | 5 ++-- .../ilp/connector/core/middleware/account.ts | 12 --------- 4 files changed, 17 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index b22f086af7..f68a254357 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -155,8 +155,7 @@ export class IncomingPayment incomingPayment = await IncomingPayment.query() .patchAndFetchById(this.id, { state: IncomingPaymentState.Completed, - // Add 30 seconds to allow a prepared (but not yet fulfilled/rejected) packet to finish before sending webhook event. - processAt: new Date(Date.now() + 30_000) + processAt: new Date() }) .whereNotIn('state', [ IncomingPaymentState.Expired, diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index d26e5c4ac4..90465420ba 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -587,8 +587,7 @@ describe('Incoming Payment Service', (): void => { test('Sets state of fully paid incoming payment to "completed"', async (): Promise => { const now = new Date() - jest.useFakeTimers() - jest.setSystemTime(now) + jest.useFakeTimers({ now }) await expect( incomingPayment.onCredit({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -597,7 +596,7 @@ describe('Incoming Payment Service', (): void => { ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000) + processAt: now }) await expect( incomingPaymentService.get({ @@ -605,7 +604,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000) + processAt: now }) }) }) @@ -662,9 +661,8 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toBeUndefined() - const now = incomingPayment.expiresAt jest.useFakeTimers() - jest.setSystemTime(now) + jest.setSystemTime(incomingPayment.expiresAt) await expect(incomingPaymentService.processNext()).resolves.toBe( incomingPayment.id ) @@ -674,7 +672,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Expired, - processAt: new Date(now.getTime() + 30_000) + processAt: new Date() }) }) @@ -820,13 +818,14 @@ describe('Incoming Payment Service', (): void => { }) test('updates state of pending incoming payment to complete', async (): Promise => { const now = new Date() - jest.spyOn(global.Date, 'now').mockImplementation(() => now.valueOf()) + jest.useFakeTimers({ now }) + await expect( incomingPaymentService.complete(incomingPayment.id) ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000) + processAt: now }) await expect( incomingPaymentService.get({ @@ -834,7 +833,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000) + processAt: now }) }) @@ -845,6 +844,9 @@ describe('Incoming Payment Service', (): void => { }) test('updates state of processing incoming payment to complete', async (): Promise => { + const now = new Date() + jest.useFakeTimers({ now }) + await incomingPayment.onCredit({ totalReceived: BigInt(100) }) @@ -860,7 +862,7 @@ describe('Incoming Payment Service', (): void => { ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(incomingPayment.expiresAt.getTime()) + processAt: now }) await expect( incomingPaymentService.get({ @@ -868,7 +870,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(incomingPayment.expiresAt.getTime()) + processAt: now }) }) diff --git a/packages/backend/src/open_payments/payment/incoming/service.ts b/packages/backend/src/open_payments/payment/incoming/service.ts index 62a8a99547..2fac831885 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.ts @@ -268,8 +268,7 @@ async function handleExpired( ) await incomingPayment.$query(deps.knex).patch({ state: IncomingPaymentState.Expired, - // Add 30 seconds to allow a prepared (but not yet fulfilled/rejected) packet to finish before sending webhook event. - processAt: new Date(Date.now() + 30_000) + processAt: new Date() }) } else { deps.logger.debug({ amountReceived }, 'deleting expired incoming payment') @@ -437,7 +436,7 @@ async function completeIncomingPayment( } await payment.$query(trx).patch({ state: IncomingPaymentState.Completed, - processAt: new Date(Date.now() + 30_000) + processAt: new Date() }) return await addReceivedAmount(deps, payment) }) diff --git a/packages/backend/src/payment-method/ilp/connector/core/middleware/account.ts b/packages/backend/src/payment-method/ilp/connector/core/middleware/account.ts index 12f74deb2e..e918aec4ba 100644 --- a/packages/backend/src/payment-method/ilp/connector/core/middleware/account.ts +++ b/packages/backend/src/payment-method/ilp/connector/core/middleware/account.ts @@ -87,10 +87,6 @@ export function createAccountMiddleware(serverAddress: string): ILPMiddleware { LiquidityAccountType.INCOMING ) } - ctx.services.logger.debug( - { incomingPaymentId: incomingPayment.id }, - 'destination account is incoming payment' - ) return incomingPayment } // Open Payments SPSP fallback account @@ -104,20 +100,12 @@ export function createAccountMiddleware(serverAddress: string): ILPMiddleware { LiquidityAccountType.WEB_MONETIZATION ) } - ctx.services.logger.debug( - { walletAddressId: walletAddress.id }, - 'destination account is wallet address' - ) return walletAddress } } const address = ctx.request.prepare.destination const peer = await peers.getByDestinationAddress(address) if (peer) { - ctx.services.logger.debug( - { peerId: peer.id }, - 'destination account is peer' - ) return peer } if ( From cb3e774f2809a4b6bbdefd584c0fd543e397d3c3 Mon Sep 17 00:00:00 2001 From: Melissa Henderson <57110301+melissahenderson@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:39:56 -0400 Subject: [PATCH 09/10] docs: fixes #2965 (#3048) --- .../docs/integration/requirements/idp.mdx | 96 +++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/packages/documentation/src/content/docs/integration/requirements/idp.mdx b/packages/documentation/src/content/docs/integration/requirements/idp.mdx index 7d8a30a691..f74729a290 100644 --- a/packages/documentation/src/content/docs/integration/requirements/idp.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/idp.mdx @@ -2,12 +2,16 @@ title: Identity provider (IdP) --- -import { Badge } from '@astrojs/starlight/components' -import { Mermaid, CodeBlock, LinkOut } from '@interledger/docs-design-system' +import { Badge, Steps } from '@astrojs/starlight/components' +import { + Mermaid, + MermaidWrapper, + LinkOut +} from '@interledger/docs-design-system' An identity provider (IdP) is a system or service that stores and manages user identity information, authentication, and consent. Examples of IdPs include OpenID Connect and Okta. -Integration with an IdP is required if you plan to support Open Payments outgoing payments for your users. Open Payments requires that outgoing payment grant requests, which precede outgoing payment requests, be interactive. In an interactive grant request, explicit interaction by an individual (e.g., an account holder) is required to approve the grant. An example of an interaction is an end-user tapping an Approve button in an app to authorize a payment. +Integration with an IdP is required if you plan to support outgoing payments via Open Payments. Open Payments requires outgoing payment _grant_ requests, which precede outgoing payment requests, be interactive. In an interactive grant request, explicit interaction by an individual (e.g., an account holder) is required to approve the grant. An example of an interaction is an end-user tapping _Approve_ in an app to authorize a payment. Your IdP will: @@ -15,23 +19,25 @@ Your IdP will: - Facilitate interactions with the client's end-user to gather consent :::note -We provide Ory Kratos, a cloud-based user management system, for the identity and user management of your Rafiki Admin users. Kratos is for internal use only and **cannot** be used here as your IdP. +We provide Ory Kratos, a cloud-based user management system, for the identity and user management of your [Rafiki Admin](/admin/admin-user-guide) users. Kratos is for internal use only and **cannot** be used as your client-facing IdP. ::: ### Interactions and consent -Before an Open Payments outgoing payment is created, an outgoing payment grant must be issued. +Before an outgoing payment is created via Open Payments, an outgoing payment grant must be issued. Outgoing payment grant requests must be interactive. This means the request requires explicit interaction, often from the account holder, to gather consent (permission) to create the outgoing payment. The interaction is facilitated by your IdP. Your IdP: -1. Provides an interface to gather consent, for example, a consent screen -2. Sends the interaction choice to your authorization server -3. Sends a request to your authorization server to finish the interaction -4. Redirects the user after the interaction is complete + + 1. Provides an interface to gather consent (for example, a consent screen) 2. + Sends the interaction choice (accept/deny) to your authorization server 3. + Sends a request to your authorization server to finish the interaction 4. + Redirects the user after the interaction is complete + ### Authorization server -The purpose of an Open Payments authorization server is to grant permission to clients to access the Open Payments APIs for creating incoming payments, quotes, and outgoing payments against an account holder's account. +The purpose of an Open Payments authorization server is to grant permission to clients to access the Open Payments APIs. These APIs are used to create incoming payments, quotes, and outgoing payments against an account holder's account. Rafiki's [auth service](/integration/services/auth-service) provides you with a reference implementation of an Open Payments authorization server. The server extends an [API](#interaction-endpoints) for your IdP to use to begin and finish an interaction, collect authorization, get information about a particular grant, and communicate that a user has authorized a grant. You can use the service as an alternative to developing your own in-house service. @@ -53,19 +59,17 @@ The following `backend` variables must be configured on your authorization serve -## Interaction endpoints +## Manage grants Your Open Payments authorization server extends an API for your IdP server to use after a pending grant request is created. Each interaction with an endpoint is identified by an `id` and a `nonce`. Both are provided as query parameters when your authorization server redirects to your IdP server. -The endpoints are tied to the `IDENTITY_SERVER_URL` you defined when configuring your environment variables. For example, if your identity server URL is `https://idp.wallet.example.com`, then to start a user interaction session, the `/interact/{id}/{nonce}` endpoint would be called: +The endpoints are appended to the `IDENTITY_SERVER_URL` you defined when configuring your [environment variables](#environment-variables). For example: `https://idp.wallet.example.com/interact/{id}/{nonce}` -``` -https://idp.wallet.example.com/interact/{id}/{nonce}` -``` +### Interaction endpoints -The endpoints are called in the sequence listed in the table below. +The endpoints are called in the sequence listed below.
@@ -83,25 +87,29 @@ We also provide an grant initialization request. +Called by your IdP server to end the interaction and redirect the end-user's browser session to the URI of the grant initialization request. -The `result` query parameter will indicate the success or failure of the grant authorization. In case of success, the SHA-256 hash of the interaction is sent in the response along with an `interact_ref` that identifies the interaction on the authorization server and the URI of the grant initialization request. +The `result` query parameter will indicate the success or failure of the grant authorization. When successful, the SHA-256 hash of the interaction is sent in the response along with an `interact_ref` that identifies the interaction on your authorization server and the URI of the grant initialization request. -The following table lists examples of the possible response types on this endpoint. +The following are examples of the possible response types. -
+
| Response | Description | Example | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | @@ -113,16 +121,50 @@ The following table lists examples of the possible response types on this endpoi #### Continue grant -The client requests a grant from your authorization server for an accepted interaction. Your authorization server responds with an access token. +Called by the client to request a grant from your authorization server if the interaction was successful (accepted). Your authorization server responds with an access token. -## x-idp-secret header +### x-idp-secret header -`x-idp-secret` is the name of a header that is used for requests to the following endpoints: +The purpose of the `x-idp-secret` header is to secure communications between your IdP and authorization servers. + +The header is used for requests to the following endpoints and its value should be a shared secret known to both entities. - `GET /grant/:id/:nonce` - `POST /grant/:id/:nonce/accept` - `POST /grant/:id/:nonce/reject` -The header's purpose is to secure communications between your IdP and authorization servers and its value should be a shared secret known to both entities. When your IdP server sends requests to your authorization server, your IdP must provide the secret via this header. +When your IdP server sends requests to your authorization server, your IdP must provide the secret via this header. To set up the header, set the [`IDENTITY_SERVER_SECRET`](#environment-variables) on your authorization server environment to a value that is also used to configure your IdP server's requests to your authorization server. + +### Sequence diagram + +The following diagram provides a high-level view of the flow from when a pending grant request is created through to the authorization server returning an access token to continue a successful (accepted) interaction. + +The diagram is for illustrative purposes and is not an exact representation of the flow. Additional information can be found in the Open Payments documentation. + + + +{/* prettier-ignore */} +>Authorization Server: Sends interactive outgoing payment grant request + Authorization Server-->>Client: HTTP 200 request successful + Client->>Authorization Server: Starts user interaction session + Authorization Server->>Authorization Server: Sets session + Authorization Server-->>Client: HTTP 302 instructs client to redirect to Identity Provider + Client->>Identity Provider: Redirects end-user's browser to the Identity Provider's consent screen + Identity Provider->>Identity Provider: End-user accepts interaction + Identity Provider->>Authorization Server: Provides end-user's interaction choice + Authorization Server-->>Identity Provider: 202 HTTP choice accepted + Identity Provider->>Authorization Server: Instructs server to finish interaction + Authorization Server->>Authorization Server: Ends session + Authorization Server-->>Identity Provider: 302 HTTP instructs Identity Provider to redirect to client + Identity Provider->>Client: Redirects to Client + Client->>Client: Verifies hash + Client->>Authorization Server: Requests continuation of grant + Authorization Server->>Client: 200 HTTP OK, returns access token +`} +/> + + From 59d6e48fc532cb8da47115b2b4175bee13ab30ee Mon Sep 17 00:00:00 2001 From: Melissa Henderson <57110301+melissahenderson@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:06:17 -0400 Subject: [PATCH 10/10] docs: idp, keys, test wallet (#3053) --- .../content/docs/admin/admin-user-guide.mdx | 10 ++++- .../integration/playground/autopeering.mdx | 4 +- .../docs/integration/playground/testnet.mdx | 8 +++- .../docs/integration/requirements/idp.mdx | 44 ++++++++++++++----- .../integration/services/auth-service.mdx | 8 ++-- .../docs/overview/concepts/open-payments.mdx | 2 +- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx index cc9fe587ce..30e93626ae 100644 --- a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx +++ b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx @@ -47,7 +47,15 @@ After running the `invite-user` script, the script generates a recovery link tha /> :::note -The invitation link is single-use for security purposes. Once accessed, it becomes invalid. If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. Example: \``http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm`\`. +The invitation link is single-use for security purposes. Once accessed, it becomes invalid. + +If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. For example: + +{/* prettier-ignore */} +```js wrap +`http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm` +``` + ::: #### Generate a recovery link diff --git a/packages/documentation/src/content/docs/integration/playground/autopeering.mdx b/packages/documentation/src/content/docs/integration/playground/autopeering.mdx index c2ceaa4fc6..8de006c3c5 100644 --- a/packages/documentation/src/content/docs/integration/playground/autopeering.mdx +++ b/packages/documentation/src/content/docs/integration/playground/autopeering.mdx @@ -4,7 +4,7 @@ title: Auto-Peering with the Test Network import { LinkOut } from '@interledger/docs-design-system' -You can start one local instance of Rafiki and peer it automatically with the remote Test Network by running the following commands: +You can start one local instance of Rafiki and peer it automatically with the remote Test Network by running the following commands: ```sh ## using Tigerbeetle DB @@ -13,7 +13,7 @@ pnpm localenv:compose:autopeer pnpm localenv:compose:psql:autopeer ``` -The mock account servicing entity, Cloud Nine Wallet, in your local Rafiki instance will automatically peer with the remote Test Network instance. The required services will be exposed externally using the localtunnel package. +The mock account servicing entity, Cloud Nine Wallet, in your local Rafiki instance will automatically peer with the remote Test Network instance. The required services will be exposed externally using the localtunnel package. The exposed ports are: diff --git a/packages/documentation/src/content/docs/integration/playground/testnet.mdx b/packages/documentation/src/content/docs/integration/playground/testnet.mdx index 3c81e2025a..bdf07901eb 100644 --- a/packages/documentation/src/content/docs/integration/playground/testnet.mdx +++ b/packages/documentation/src/content/docs/integration/playground/testnet.mdx @@ -12,8 +12,12 @@ The [Local Playground](/integration/playground/overview/) is not the only way to The current applications include: -- An Interledger test wallet -- An e-commerce application +- + An Interledger test wallet + +- + An e-commerce application + ## Peering with the Test Network diff --git a/packages/documentation/src/content/docs/integration/requirements/idp.mdx b/packages/documentation/src/content/docs/integration/requirements/idp.mdx index f74729a290..4c839264d7 100644 --- a/packages/documentation/src/content/docs/integration/requirements/idp.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/idp.mdx @@ -6,12 +6,13 @@ import { Badge, Steps } from '@astrojs/starlight/components' import { Mermaid, MermaidWrapper, - LinkOut + LinkOut, + CodeBlock } from '@interledger/docs-design-system' An identity provider (IdP) is a system or service that stores and manages user identity information, authentication, and consent. Examples of IdPs include OpenID Connect and Okta. -Integration with an IdP is required if you plan to support outgoing payments via Open Payments. Open Payments requires outgoing payment _grant_ requests, which precede outgoing payment requests, be interactive. In an interactive grant request, explicit interaction by an individual (e.g., an account holder) is required to approve the grant. An example of an interaction is an end-user tapping _Approve_ in an app to authorize a payment. +Integration with an IdP is required if you plan to support outgoing payments via Open Payments. The Open Payments standard requires interactive outgoing payment _grant_ requests, which precede outgoing payment requests. In an interactive grant request, explicit interaction by an individual (e.g., the client's end-user) is required to approve the grant. An example of an interaction is an end-user tapping _Approve_ in an app to authorize a payment. Your IdP will: @@ -24,22 +25,28 @@ We provide Ory Kratos, a cloud-based user management system, for the identity an ### Interactions and consent -Before an outgoing payment is created via Open Payments, an outgoing payment grant must be issued. +Before an outgoing payment is created via Open Payments, an outgoing payment _grant_ must be issued. -Outgoing payment grant requests must be interactive. This means the request requires explicit interaction, often from the account holder, to gather consent (permission) to create the outgoing payment. The interaction is facilitated by your IdP. Your IdP: +Outgoing payment grant requests must be interactive. This means the request requires explicit interaction, often from the a client's end-user, to gather consent (permission) before creating the outgoing payment. The interaction is facilitated by your IdP. + +Your IdP: - 1. Provides an interface to gather consent (for example, a consent screen) 2. - Sends the interaction choice (accept/deny) to your authorization server 3. - Sends a request to your authorization server to finish the interaction 4. - Redirects the user after the interaction is complete + +1. Provides an interface to gather consent (for example, a consent screen) +2. Sends the interaction choice (accept/deny) to your authorization server +3. Sends a request to your authorization server to finish the interaction +4. Redirects the user after the interaction is complete + ### Authorization server The purpose of an Open Payments authorization server is to grant permission to clients to access the Open Payments APIs. These APIs are used to create incoming payments, quotes, and outgoing payments against an account holder's account. -Rafiki's [auth service](/integration/services/auth-service) provides you with a reference implementation of an Open Payments authorization server. The server extends an [API](#interaction-endpoints) for your IdP to use to begin and finish an interaction, collect authorization, get information about a particular grant, and communicate that a user has authorized a grant. You can use the service as an alternative to developing your own in-house service. +Rafiki's [auth service](/integration/services/auth-service) provides you with a reference implementation of an Open Payments authorization server. You can use the service as an alternative to developing your own in-house service. + +The authorization server extends an HTTP API for your IdP to use to start and finish interactions, collect authorization, get information about a grant, and communicate whether an end-user has authorized a grant. The API's [endpoints](#interaction-endpoints) are described below. ## Environment variables @@ -61,11 +68,24 @@ The following `backend` variables must be configured on your authorization serve ## Manage grants -Your Open Payments authorization server extends an API for your IdP server to use after a pending grant request is created. +After a pending grant request is created, your IdP server can use the interaction endpoints listed below to: + +- Start and finish interactions +- Collect authorization +- Get information about a grant +- Communicate whether an end-user has authorized a grant + +Each interaction is identified by an `id` and a `nonce`. Both are provided as query parameters when your authorization server redirects to your IdP server. + +The endpoints are appended to the `IDENTITY_SERVER_URL` you defined when configuring your [environment variables](#environment-variables). + + -Each interaction with an endpoint is identified by an `id` and a `nonce`. Both are provided as query parameters when your authorization server redirects to your IdP server. +```http +https://idp.wallet.example.com/interact/{id}/{nonce} +``` -The endpoints are appended to the `IDENTITY_SERVER_URL` you defined when configuring your [environment variables](#environment-variables). For example: `https://idp.wallet.example.com/interact/{id}/{nonce}` + ### Interaction endpoints diff --git a/packages/documentation/src/content/docs/integration/services/auth-service.mdx b/packages/documentation/src/content/docs/integration/services/auth-service.mdx index dad5fcc686..a262c4cd66 100644 --- a/packages/documentation/src/content/docs/integration/services/auth-service.mdx +++ b/packages/documentation/src/content/docs/integration/services/auth-service.mdx @@ -32,17 +32,19 @@ When a request comes from a client with an account known to your local instance When a request comes from a client registered with another instance of Rafiki, the `auth` service resolves the client's key endpoint (e.g., `https://wallet.example.com/alice/jwks.json`) to retrieve the client's public keys, then filters out the correct key using the key id (`kid`) in the client's signature. +Review the Open Payments documentation for more information about client keys. + ## Identity provider (IdP) An identity provider (IdP) is a system or service that manages user authentication, identity information, and consent. When you use your Google account credentials to “Sign in with Google” on an app or website, for example, Google is acting as your identity provider. -Integration with an [IdP](/integration/requirements/idp) is required when using Rafiki’s `auth` service because Open Payments requires interactive outgoing payment grant requests. This means there must be explicit interaction by an individual (typically a client’s end user/your customer) to approve or deny an outgoing payment before a grant is issued. +Integration with an [IdP](/integration/requirements/idp) is required when using Rafiki’s `auth` service because the Open Payments standard requires interactive outgoing payment _grant_ requests. In an interactive request, there must be explicit interaction by an individual (e.g., a client's end-user) to approve or deny the grant. In this case, the grant must be explicitly approved before an outgoing payment is created. :::note -Rafiki’s [`frontend`](/integration/services/frontend-service) service requires an IdP for authentication and user management of your administrators. Out of the box, Rafiki uses Ory Kratos to enable your admins to access the Admin API. Kratos is for internal use and cannot be used for your customer-facing Open Payments authorization server. +Rafiki’s [`frontend`](/integration/services/frontend-service) service requires an IdP for authentication and user management of your [Rafiki Admin](/admin/admin-user-guide) users. Out of the box, Rafiki uses Ory Kratos, a cloud-based user management system. Kratos is for internal use only and **cannot** be used as your customer-facing Open Payments authorization server. ::: -For more information about interactive grants and how they work with identity providers, review the Grant negotiation and authorization page in the Open Payments docs. +For more information about interactive grants and how they work with identity providers, review our [Identity Provider](/integration/requirements/idp) page and the Grant negotiation and authorization page in the Open Payments docs. ## GraphQL Auth Admin API diff --git a/packages/documentation/src/content/docs/overview/concepts/open-payments.mdx b/packages/documentation/src/content/docs/overview/concepts/open-payments.mdx index b6f53ecce9..ca7c364e7c 100644 --- a/packages/documentation/src/content/docs/overview/concepts/open-payments.mdx +++ b/packages/documentation/src/content/docs/overview/concepts/open-payments.mdx @@ -35,4 +35,4 @@ Rafiki’s [`backend`](/integration/services/backend-service) service is the mai ## Rafiki's auth service -Rafiki’s [`auth`](/integration/services/auth-service) service is a reference implementation of an opinionated Open Payments authorization server. The authorization server is responsible for delegating authorization (via grants) to clients to use the Open Payments APIs, resolving clients’ public keys to authenticate and authorize incoming requests, and creating payments and quotes on the backend. Open Payments leverages the Grant Negotiation and Authorization Protocol (GNAP) for delegating authorization. You can learn more about the protocol by reviewing its specification. +Rafiki’s [`auth`](/integration/services/auth-service) service is a reference implementation of an opinionated Open Payments authorization server. The authorization server is responsible for delegating authorization (via grants) to clients to use the Open Payments APIs, resolving clients’ public keys to authenticate and authorize incoming requests, and creating payments and quotes on the backend. Open Payments leverages the Grant Negotiation and Authorization Protocol (GNAP) for delegating authorization. You can learn more about the protocol by reviewing its specification.