diff --git a/arazzo/one-time-payment-fixed-receive.arazzo.yaml b/arazzo/one-time-payment-fixed-receive.arazzo.yaml new file mode 100644 index 00000000..da594a50 --- /dev/null +++ b/arazzo/one-time-payment-fixed-receive.arazzo.yaml @@ -0,0 +1,280 @@ +arazzo: "1.0.1" +info: + title: "Open Payments — One-Time Payment (Fixed Receive)" + summary: > + End-to-end workflow for sending a one-time payment where the recipient + specifies the amount to receive. + description: | + This workflow documents the complete API call sequence for an Open Payments + client to send a one-time payment with a fixed receive amount — the most + common e-commerce checkout flow. + + **Actors:** + - **Client** — a web or mobile application acting on behalf of the sender. + - **Recipient's ASE** — the Account Servicing Entity that hosts the + recipient's wallet address, authorization server, and resource server. + - **Sender's ASE** — the Account Servicing Entity that hosts the sender's + wallet address, authorization server, and resource server. + + **Flow summary:** + 1. Resolve the recipient's wallet address to discover server URLs. + 2. Obtain a grant and create an incoming payment on the recipient's ASE. + 3. Obtain a grant and create a quote on the sender's ASE. + 4. Obtain an interactive grant (with user consent) for the outgoing payment. + 5. Continue the grant after user interaction to receive an access token. + 6. Create the outgoing payment on the sender's ASE to initiate the transfer. + version: "1.0.0" + +sourceDescriptions: + - name: walletAddressServer + url: ../open-payments-specifications/openapi/wallet-address-server.yaml + type: openapi + - name: resourceServer + url: ../open-payments-specifications/openapi/resource-server.yaml + type: openapi + - name: authServer + url: ../open-payments-specifications/openapi/auth-server.yaml + type: openapi + +workflows: + - workflowId: oneTimePaymentFixedReceive + summary: "One-time payment with a fixed receive amount" + description: | + Complete Open Payments flow for a one-time payment where the incoming + payment has an `incomingAmount` set. This is the standard e-commerce + checkout pattern where the merchant knows exactly how much should be + received. + inputs: + type: object + required: + - recipientWalletAddressUrl + - senderWalletAddressUrl + - amount + - assetCode + - assetScale + - clientWalletAddress + properties: + recipientWalletAddressUrl: + type: string + description: "The recipient's wallet address URL (e.g. https://wallet.example.com/alice)" + senderWalletAddressUrl: + type: string + description: "The sender's wallet address URL (e.g. https://wallet.example.com/bob)" + amount: + type: string + description: "Amount to receive in minor units (e.g. '2500' for $25.00 with scale 2)" + assetCode: + type: string + description: "Currency code (e.g. USD)" + assetScale: + type: integer + description: "Asset scale (e.g. 2 for USD cents)" + clientWalletAddress: + type: string + description: "The client's wallet address for GNAP identification" + finishUri: + type: string + description: "URI to redirect the user to after interaction (optional, defaults to client callback)" + finishNonce: + type: string + description: "Unique nonce for the interaction finish (optional)" + + steps: + # ─── Step 1: Resolve recipient's wallet address ─── + - stepId: getRecipientWalletAddress + description: > + Retrieve the recipient's wallet address details to discover the + authorization server URL and resource server URL. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + recipientAuthServer: $response.body.authServer + recipientResourceServer: $response.body.resourceServer + recipientAssetCode: $response.body.assetCode + recipientAssetScale: $response.body.assetScale + + # ─── Step 2: Request grant for incoming payment on recipient's ASE ─── + - stepId: requestIncomingPaymentGrant + description: > + Request a non-interactive grant from the recipient's authorization + server to create an incoming payment resource. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: incoming-payment + actions: + - create + - read + identifier: $inputs.recipientWalletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + incomingPaymentAccessToken: $response.body.access_token.value + incomingPaymentContinueUri: $response.body.continue.uri + + # ─── Step 3: Create incoming payment on recipient's resource server ─── + - stepId: createIncomingPayment + description: > + Create an incoming payment resource on the recipient's account with + the fixed receive amount. The response includes unique payment + details (e.g. ILP address) for addressing payments to the recipient. + operationId: resourceServer.create-incoming-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.recipientWalletAddressUrl + incomingAmount: + value: $inputs.amount + assetCode: $inputs.assetCode + assetScale: $inputs.assetScale + successCriteria: + - condition: $statusCode == 201 + outputs: + incomingPaymentUrl: $response.body.id + + # ─── Step 4: Resolve sender's wallet address ─── + - stepId: getSenderWalletAddress + description: > + Retrieve the sender's wallet address details to discover the + sender's authorization and resource server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + senderAuthServer: $response.body.authServer + senderResourceServer: $response.body.resourceServer + + # ─── Step 5: Request grant for quote on sender's ASE ─── + - stepId: requestQuoteGrant + description: > + Request a non-interactive grant from the sender's authorization + server to create a quote resource. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: quote + actions: + - create + - read + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + quoteAccessToken: $response.body.access_token.value + + # ─── Step 6: Create quote on sender's resource server ─── + - stepId: createQuote + description: > + Create a quote on the sender's account. The receiver is the URL of + the incoming payment. Since the incoming payment has an + `incomingAmount`, the quote automatically calculates the debit + amount including any fees. + operationId: resourceServer.create-quote + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + method: ilp + successCriteria: + - condition: $statusCode == 201 + outputs: + quoteId: $response.body.id + debitAmount: $response.body.debitAmount + receiveAmount: $response.body.receiveAmount + expiresAt: $response.body.expiresAt + + # ─── Step 7: Request interactive grant for outgoing payment ─── + - stepId: requestOutgoingPaymentGrant + description: > + Request an interactive grant from the sender's authorization server + for the outgoing payment. This returns a redirect URI where the + sender must provide explicit consent before the payment can proceed. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: outgoing-payment + actions: + - create + - read + identifier: $inputs.senderWalletAddressUrl + limits: + debitAmount: $steps.createQuote.outputs.debitAmount + receiveAmount: $steps.createQuote.outputs.receiveAmount + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + client: $inputs.clientWalletAddress + interact: + start: + - redirect + finish: + method: redirect + uri: $inputs.finishUri + nonce: $inputs.finishNonce + successCriteria: + - condition: $statusCode == 200 + outputs: + interactRedirectUri: $response.body.interact.redirect + interactFinishNonce: $response.body.interact.finish + continueAccessToken: $response.body.continue.access_token.value + continueUri: $response.body.continue.uri + continueWait: $response.body.continue.wait + + # ─── Step 8: Continue grant after user interaction ─── + - stepId: continueOutgoingPaymentGrant + description: > + After the sender has completed the interaction (consented to the + payment via the identity provider), the client continues the grant + request to obtain the access token for the outgoing payment. + The client MUST verify the interaction hash before making this call. + operationId: authServer.post-continue + parameters: + - name: id + in: path + value: $steps.requestOutgoingPaymentGrant.outputs.continueUri + requestBody: + contentType: application/json + payload: + interact_ref: "{interact_ref_from_redirect}" + successCriteria: + - condition: $statusCode == 200 + outputs: + outgoingPaymentAccessToken: $response.body.access_token.value + outgoingPaymentManageUrl: $response.body.access_token.manage + + # ─── Step 9: Create outgoing payment on sender's resource server ─── + - stepId: createOutgoingPayment + description: > + Create the outgoing payment resource on the sender's account using + the quote ID. This instructs the sender's ASE to execute the payment. + The payment setup is now complete — the actual money movement happens + on the underlying payment rails between the two ASEs. + operationId: resourceServer.create-outgoing-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + quoteId: $steps.createQuote.outputs.quoteId + successCriteria: + - condition: $statusCode == 201 + outputs: + outgoingPaymentId: $response.body.id + outgoingPaymentFailed: $response.body.failed + sentAmount: $response.body.sentAmount + + outputs: + incomingPaymentUrl: $steps.createIncomingPayment.outputs.incomingPaymentUrl + quoteId: $steps.createQuote.outputs.quoteId + outgoingPaymentId: $steps.createOutgoingPayment.outputs.outgoingPaymentId + debitAmount: $steps.createQuote.outputs.debitAmount + receiveAmount: $steps.createQuote.outputs.receiveAmount diff --git a/arazzo/one-time-payment-fixed-send.arazzo.yaml b/arazzo/one-time-payment-fixed-send.arazzo.yaml new file mode 100644 index 00000000..04cad4f4 --- /dev/null +++ b/arazzo/one-time-payment-fixed-send.arazzo.yaml @@ -0,0 +1,264 @@ +arazzo: "1.0.1" +info: + title: "Open Payments — One-Time Payment (Fixed Send)" + summary: > + End-to-end workflow for sending a one-time payment where the sender + specifies the exact debit amount (remittance pattern). + description: | + This workflow documents the complete API call sequence for an Open Payments + client to send a one-time payment with a fixed send amount — the standard + remittance pattern where the sender knows exactly how much they want to + debit from their account. + + The key difference from the fixed-receive workflow is: + - The incoming payment is created **without** an `incomingAmount`. + - The quote is created with a `debitAmount` to fix the debit side. + - The ASE calculates the receive amount based on the exchange rate. + + **Actors:** + - **Client** — application acting on behalf of the sender. + - **Recipient's ASE** — hosts the recipient's wallet and auth/resource servers. + - **Sender's ASE** — hosts the sender's wallet and auth/resource servers. + version: "1.0.0" + +sourceDescriptions: + - name: walletAddressServer + url: ../open-payments-specifications/openapi/wallet-address-server.yaml + type: openapi + - name: resourceServer + url: ../open-payments-specifications/openapi/resource-server.yaml + type: openapi + - name: authServer + url: ../open-payments-specifications/openapi/auth-server.yaml + type: openapi + +workflows: + - workflowId: oneTimePaymentFixedSend + summary: "One-time payment with a fixed send (debit) amount" + description: | + Complete Open Payments flow where the sender specifies how much to + debit from their account. The incoming payment has no `incomingAmount`, + and the quote uses `debitAmount` to fix the sender's side. + inputs: + type: object + required: + - recipientWalletAddressUrl + - senderWalletAddressUrl + - debitAmount + - debitAssetCode + - debitAssetScale + - clientWalletAddress + properties: + recipientWalletAddressUrl: + type: string + description: "The recipient's wallet address URL" + senderWalletAddressUrl: + type: string + description: "The sender's wallet address URL" + debitAmount: + type: string + description: "Amount to debit in minor units (e.g. '2500' for $25.00 with scale 2)" + debitAssetCode: + type: string + description: "Currency code of the sender's asset (e.g. USD)" + debitAssetScale: + type: integer + description: "Asset scale (e.g. 2 for USD cents)" + clientWalletAddress: + type: string + description: "The client's wallet address for GNAP identification" + finishUri: + type: string + description: "URI to redirect the user to after interaction" + finishNonce: + type: string + description: "Unique nonce for the interaction finish" + + steps: + # ─── Step 1: Resolve recipient's wallet address ─── + - stepId: getRecipientWalletAddress + description: > + Retrieve the recipient's wallet address details to discover server + URLs and supported asset information. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + recipientAuthServer: $response.body.authServer + recipientResourceServer: $response.body.resourceServer + + # ─── Step 2: Request grant for incoming payment ─── + - stepId: requestIncomingPaymentGrant + description: > + Request a non-interactive grant from the recipient's authorization + server to create and read incoming payments. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: incoming-payment + actions: + - create + - read + identifier: $inputs.recipientWalletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + incomingPaymentAccessToken: $response.body.access_token.value + + # ─── Step 3: Create incoming payment WITHOUT incomingAmount ─── + - stepId: createIncomingPayment + description: > + Create an incoming payment on the recipient's account WITHOUT + specifying an `incomingAmount`. This creates an open-ended + incoming payment where the received amount will be determined + by the quote and exchange rate. + operationId: resourceServer.create-incoming-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.recipientWalletAddressUrl + successCriteria: + - condition: $statusCode == 201 + outputs: + incomingPaymentUrl: $response.body.id + + # ─── Step 4: Resolve sender's wallet address ─── + - stepId: getSenderWalletAddress + description: > + Retrieve the sender's wallet address details to discover the + sender's authorization and resource server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + senderAuthServer: $response.body.authServer + senderResourceServer: $response.body.resourceServer + + # ─── Step 5: Request grant for quote ─── + - stepId: requestQuoteGrant + description: > + Request a non-interactive grant from the sender's authorization + server to create and read quotes. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: quote + actions: + - create + - read + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + quoteAccessToken: $response.body.access_token.value + + # ─── Step 6: Create quote with fixed debitAmount ─── + - stepId: createQuote + description: > + Create a quote on the sender's account with a fixed `debitAmount`. + The sender's ASE calculates the corresponding receive amount based + on the exchange rate, fees, and the quote method. + operationId: resourceServer.create-quote + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + method: ilp + debitAmount: + value: $inputs.debitAmount + assetCode: $inputs.debitAssetCode + assetScale: $inputs.debitAssetScale + successCriteria: + - condition: $statusCode == 201 + outputs: + quoteId: $response.body.id + debitAmount: $response.body.debitAmount + receiveAmount: $response.body.receiveAmount + + # ─── Step 7: Request interactive grant for outgoing payment ─── + - stepId: requestOutgoingPaymentGrant + description: > + Request an interactive grant from the sender's authorization server + for the outgoing payment. The user must explicitly consent to the + payment through the redirect flow. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: outgoing-payment + actions: + - create + - read + identifier: $inputs.senderWalletAddressUrl + limits: + debitAmount: $steps.createQuote.outputs.debitAmount + receiveAmount: $steps.createQuote.outputs.receiveAmount + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + client: $inputs.clientWalletAddress + interact: + start: + - redirect + finish: + method: redirect + uri: $inputs.finishUri + nonce: $inputs.finishNonce + successCriteria: + - condition: $statusCode == 200 + outputs: + interactRedirectUri: $response.body.interact.redirect + continueAccessToken: $response.body.continue.access_token.value + continueUri: $response.body.continue.uri + + # ─── Step 8: Continue grant after user interaction ─── + - stepId: continueOutgoingPaymentGrant + description: > + After the sender completes the interaction (consents to the payment), + the client continues the grant to obtain the access token. + operationId: authServer.post-continue + parameters: + - name: id + in: path + value: $steps.requestOutgoingPaymentGrant.outputs.continueUri + requestBody: + contentType: application/json + payload: + interact_ref: "{interact_ref_from_redirect}" + successCriteria: + - condition: $statusCode == 200 + outputs: + outgoingPaymentAccessToken: $response.body.access_token.value + + # ─── Step 9: Create outgoing payment ─── + - stepId: createOutgoingPayment + description: > + Create the outgoing payment using the quote. This triggers the + actual payment execution between the two ASEs. + operationId: resourceServer.create-outgoing-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + quoteId: $steps.createQuote.outputs.quoteId + successCriteria: + - condition: $statusCode == 201 + outputs: + outgoingPaymentId: $response.body.id + outgoingPaymentFailed: $response.body.failed + sentAmount: $response.body.sentAmount + + outputs: + incomingPaymentUrl: $steps.createIncomingPayment.outputs.incomingPaymentUrl + quoteId: $steps.createQuote.outputs.quoteId + outgoingPaymentId: $steps.createOutgoingPayment.outputs.outgoingPaymentId + debitAmount: $steps.createQuote.outputs.debitAmount + receiveAmount: $steps.createQuote.outputs.receiveAmount diff --git a/arazzo/recurring-payment.arazzo.yaml b/arazzo/recurring-payment.arazzo.yaml new file mode 100644 index 00000000..08cab0c7 --- /dev/null +++ b/arazzo/recurring-payment.arazzo.yaml @@ -0,0 +1,274 @@ +arazzo: "1.0.1" +info: + title: "Open Payments — Recurring Payment" + summary: > + Workflow for setting up a recurring payment with interval-based grant + authorization. + description: | + This workflow documents the API call sequence for setting up a recurring + payment using Open Payments. Recurring payments use the GNAP interactive + grant flow with `limits.interval` to authorize periodic debits. + + **Key differences from one-time payments:** + - The outgoing payment grant includes an `interval` in ISO 8601 repeating + interval format (e.g. `R12/2024-01-01T00:00:00Z/P1M` for 12 monthly + payments). + - After the initial grant, the client can create multiple outgoing payments + within the granted limits without requiring additional user interaction. + + **Actors:** + - **Client** — application managing the subscription/recurring billing. + - **Recipient's ASE** — hosts the recipient's wallet and servers. + - **Sender's ASE** — hosts the sender's wallet and servers. + version: "1.0.0" + +sourceDescriptions: + - name: walletAddressServer + url: ../open-payments-specifications/openapi/wallet-address-server.yaml + type: openapi + - name: resourceServer + url: ../open-payments-specifications/openapi/resource-server.yaml + type: openapi + - name: authServer + url: ../open-payments-specifications/openapi/auth-server.yaml + type: openapi + +workflows: + # ─── Workflow 1: Set up recurring payment grant ─── + - workflowId: setupRecurringPayment + summary: "Set up a recurring payment grant with interval limits" + description: | + Establishes a recurring payment authorization with the sender's ASE. + After this workflow completes, the client holds an access token that + permits creating outgoing payments within the specified interval and + amount limits without further user interaction. + inputs: + type: object + required: + - recipientWalletAddressUrl + - senderWalletAddressUrl + - debitAmountPerInterval + - debitAssetCode + - debitAssetScale + - interval + - clientWalletAddress + properties: + recipientWalletAddressUrl: + type: string + description: "Recipient's wallet address URL" + senderWalletAddressUrl: + type: string + description: "Sender's wallet address URL" + debitAmountPerInterval: + type: string + description: "Maximum debit amount per interval (minor units)" + debitAssetCode: + type: string + description: "Currency code (e.g. USD)" + debitAssetScale: + type: integer + description: "Asset scale (e.g. 2)" + interval: + type: string + description: > + ISO 8601 repeating interval (e.g. 'R12/2024-01-01T00:00:00Z/P1M' + for 12 monthly periods starting Jan 1 2024) + clientWalletAddress: + type: string + description: "Client's wallet address" + finishUri: + type: string + description: "Redirect URI after interaction" + finishNonce: + type: string + description: "Unique nonce for the finish step" + + steps: + # ─── Step 1: Resolve recipient's wallet address ─── + - stepId: getRecipientWalletAddress + description: > + Resolve the recipient's wallet address to discover auth and resource + server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + recipientAuthServer: $response.body.authServer + recipientResourceServer: $response.body.resourceServer + + # ─── Step 2: Request incoming payment grant ─── + - stepId: requestIncomingPaymentGrant + description: > + Request a non-interactive grant to create incoming payments on the + recipient's ASE. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: incoming-payment + actions: + - create + - read + identifier: $inputs.recipientWalletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + incomingPaymentAccessToken: $response.body.access_token.value + + # ─── Step 3: Create incoming payment ─── + - stepId: createIncomingPayment + description: > + Create an incoming payment without an `incomingAmount` since the + exact receive amount will depend on exchange rates at payment time. + operationId: resourceServer.create-incoming-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.recipientWalletAddressUrl + successCriteria: + - condition: $statusCode == 201 + outputs: + incomingPaymentUrl: $response.body.id + + # ─── Step 4: Resolve sender's wallet address ─── + - stepId: getSenderWalletAddress + description: > + Resolve the sender's wallet address for auth and resource server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + senderAuthServer: $response.body.authServer + senderResourceServer: $response.body.resourceServer + + # ─── Step 5: Request quote grant ─── + - stepId: requestQuoteGrant + description: > + Request a non-interactive grant to create quotes on the sender's ASE. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: quote + actions: + - create + - read + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + quoteAccessToken: $response.body.access_token.value + + # ─── Step 6: Create initial quote ─── + - stepId: createQuote + description: > + Create a quote for the first interval's payment. This establishes + the exchange rate and fees for the initial payment. + operationId: resourceServer.create-quote + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + method: ilp + debitAmount: + value: $inputs.debitAmountPerInterval + assetCode: $inputs.debitAssetCode + assetScale: $inputs.debitAssetScale + successCriteria: + - condition: $statusCode == 201 + outputs: + quoteId: $response.body.id + debitAmount: $response.body.debitAmount + receiveAmount: $response.body.receiveAmount + + # ─── Step 7: Request interactive recurring grant ─── + - stepId: requestRecurringGrant + description: > + Request an interactive grant for the outgoing payment with + `limits.interval` set. The interval defines how often the client + can create outgoing payments and the maximum amount per interval. + The user must consent to this recurring authorization. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: outgoing-payment + actions: + - create + - read + identifier: $inputs.senderWalletAddressUrl + limits: + receiver: $steps.createIncomingPayment.outputs.incomingPaymentUrl + interval: $inputs.interval + debitAmount: + value: $inputs.debitAmountPerInterval + assetCode: $inputs.debitAssetCode + assetScale: $inputs.debitAssetScale + client: $inputs.clientWalletAddress + interact: + start: + - redirect + finish: + method: redirect + uri: $inputs.finishUri + nonce: $inputs.finishNonce + successCriteria: + - condition: $statusCode == 200 + outputs: + interactRedirectUri: $response.body.interact.redirect + continueAccessToken: $response.body.continue.access_token.value + continueUri: $response.body.continue.uri + + # ─── Step 8: Continue grant after user consent ─── + - stepId: continueRecurringGrant + description: > + Continue the grant after the user has consented to the recurring + payment. The resulting access token permits creating outgoing + payments within the interval and amount limits. + operationId: authServer.post-continue + parameters: + - name: id + in: path + value: $steps.requestRecurringGrant.outputs.continueUri + requestBody: + contentType: application/json + payload: + interact_ref: "{interact_ref_from_redirect}" + successCriteria: + - condition: $statusCode == 200 + outputs: + outgoingPaymentAccessToken: $response.body.access_token.value + outgoingPaymentManageUrl: $response.body.access_token.manage + + # ─── Step 9: Create first outgoing payment ─── + - stepId: createOutgoingPayment + description: > + Create the first outgoing payment using the recurring grant. + Subsequent payments within the allowed interval can be created + by repeating the quote + outgoing payment steps without additional + user interaction. + operationId: resourceServer.create-outgoing-payment + requestBody: + contentType: application/json + payload: + walletAddress: $inputs.senderWalletAddressUrl + quoteId: $steps.createQuote.outputs.quoteId + successCriteria: + - condition: $statusCode == 201 + outputs: + outgoingPaymentId: $response.body.id + outgoingPaymentFailed: $response.body.failed + + outputs: + outgoingPaymentAccessToken: $steps.continueRecurringGrant.outputs.outgoingPaymentAccessToken + outgoingPaymentManageUrl: $steps.continueRecurringGrant.outputs.outgoingPaymentManageUrl + firstPaymentId: $steps.createOutgoingPayment.outputs.outgoingPaymentId + incomingPaymentUrl: $steps.createIncomingPayment.outputs.incomingPaymentUrl diff --git a/arazzo/token-management.arazzo.yaml b/arazzo/token-management.arazzo.yaml new file mode 100644 index 00000000..dd18bf96 --- /dev/null +++ b/arazzo/token-management.arazzo.yaml @@ -0,0 +1,137 @@ +arazzo: "1.0.1" +info: + title: "Open Payments — Token Management" + summary: > + Workflows for rotating and revoking GNAP access tokens and cancelling + active grants. + description: | + This document defines workflows for managing access tokens and grants + in the Open Payments ecosystem. GNAP (Grant Negotiation and Authorization + Protocol) access tokens can be rotated to extend their lifetime or + revoked when no longer needed. Active grants can also be cancelled. + + **Token rotation** replaces an existing access token with a new one + while maintaining the same permissions. This is particularly useful for + long-lived recurring payment grants where the access token may expire + before the grant interval ends. + + **Token revocation** permanently invalidates an access token. The client + should revoke tokens when they are no longer needed. + + **Grant cancellation** terminates an active grant and invalidates all + associated access tokens. + version: "1.0.0" + +sourceDescriptions: + - name: authServer + url: ../open-payments-specifications/openapi/auth-server.yaml + type: openapi + +workflows: + # ─── Workflow 1: Rotate Access Token ─── + - workflowId: rotateAccessToken + summary: "Rotate an existing GNAP access token" + description: | + Rotate an access token by calling its management URI. The AS responds + with a new access token that has the same access rights as the original. + The original token is invalidated upon successful rotation. + inputs: + type: object + required: + - tokenManageUrl + properties: + tokenManageUrl: + type: string + description: > + The `manage` URL of the access token to rotate. This was provided + in the original grant response as `access_token.manage`. + + steps: + - stepId: rotateToken + description: > + Call the token management endpoint to rotate the access token. + The original token's value is sent in the Authorization header + (handled by the HTTP signature layer). The AS returns a new + access token with fresh `value`, `manage` URL, and `expires_in`. + operationId: authServer.post-token + parameters: + - name: id + in: path + value: $inputs.tokenManageUrl + successCriteria: + - condition: $statusCode == 200 + outputs: + newAccessTokenValue: $response.body.access_token.value + newManageUrl: $response.body.access_token.manage + expiresIn: $response.body.access_token.expires_in + access: $response.body.access_token.access + + outputs: + accessToken: $steps.rotateToken.outputs.newAccessTokenValue + manageUrl: $steps.rotateToken.outputs.newManageUrl + expiresIn: $steps.rotateToken.outputs.expiresIn + + # ─── Workflow 2: Revoke Access Token ─── + - workflowId: revokeAccessToken + summary: "Revoke an active GNAP access token" + description: | + Permanently revoke an access token by sending a DELETE request to its + management URI. After revocation, the token cannot be used to access + any resources. + inputs: + type: object + required: + - tokenManageUrl + properties: + tokenManageUrl: + type: string + description: > + The `manage` URL of the access token to revoke. + + steps: + - stepId: revokeToken + description: > + Send a DELETE request to the token management endpoint to + permanently revoke the access token. + operationId: authServer.delete-token + parameters: + - name: id + in: path + value: $inputs.tokenManageUrl + successCriteria: + - condition: $statusCode == 204 + + outputs: {} + + # ─── Workflow 3: Cancel Grant ─── + - workflowId: cancelGrant + summary: "Cancel an active GNAP grant" + description: | + Cancel an active grant by sending a DELETE request to the continuation + URI. This revokes all access tokens associated with the grant and + prevents any further use of the grant's permissions. + inputs: + type: object + required: + - continueUri + properties: + continueUri: + type: string + description: > + The continuation URI from the original grant response + (`continue.uri`). + + steps: + - stepId: cancelGrant + description: > + Send a DELETE request to the grant's continuation URI. The AS + revokes all associated access tokens and cancels the grant. + operationId: authServer.delete-continue + parameters: + - name: id + in: path + value: $inputs.continueUri + successCriteria: + - condition: $statusCode == 204 + + outputs: {} diff --git a/arazzo/transaction-history.arazzo.yaml b/arazzo/transaction-history.arazzo.yaml new file mode 100644 index 00000000..0822f150 --- /dev/null +++ b/arazzo/transaction-history.arazzo.yaml @@ -0,0 +1,284 @@ +arazzo: "1.0.1" +info: + title: "Open Payments — Transaction History" + summary: > + Workflows for listing and retrieving incoming and outgoing payment + resources for account reconciliation. + description: | + This document defines workflows for retrieving transaction history + through the Open Payments API. These workflows cover listing and + retrieving both incoming and outgoing payments on a wallet address. + + These workflows are useful for: + - Account reconciliation + - Payment status checking + - Building transaction history UIs + - Monitoring payment completion + version: "1.0.0" + +sourceDescriptions: + - name: walletAddressServer + url: ../open-payments-specifications/openapi/wallet-address-server.yaml + type: openapi + - name: resourceServer + url: ../open-payments-specifications/openapi/resource-server.yaml + type: openapi + - name: authServer + url: ../open-payments-specifications/openapi/auth-server.yaml + type: openapi + +workflows: + # ─── Workflow 1: List incoming payments ─── + - workflowId: listIncomingPayments + summary: "List all incoming payments on a wallet address" + description: | + Obtain a grant and list incoming payments with pagination support. + inputs: + type: object + required: + - walletAddressUrl + - clientWalletAddress + properties: + walletAddressUrl: + type: string + description: "The wallet address to list incoming payments for" + clientWalletAddress: + type: string + description: "The client's wallet address for GNAP identification" + first: + type: integer + description: "Number of results to return (forward pagination)" + cursor: + type: string + description: "Pagination cursor from a previous response" + + steps: + - stepId: getWalletAddress + description: > + Resolve the wallet address to discover auth and resource server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + authServer: $response.body.authServer + resourceServer: $response.body.resourceServer + + - stepId: requestIncomingPaymentListGrant + description: > + Request a non-interactive grant with `list` permissions for + incoming payments. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: incoming-payment + actions: + - list + identifier: $inputs.walletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + accessToken: $response.body.access_token.value + + - stepId: listIncomingPayments + description: > + List incoming payments on the wallet address. Supports forward and + backward pagination via cursor parameters. + operationId: resourceServer.list-incoming-payments + parameters: + - name: wallet-address + in: query + value: $inputs.walletAddressUrl + - name: first + in: query + value: $inputs.first + - name: cursor + in: query + value: $inputs.cursor + successCriteria: + - condition: $statusCode == 200 + outputs: + incomingPayments: $response.body.result + paginationStartCursor: $response.body.pagination.startCursor + paginationEndCursor: $response.body.pagination.endCursor + hasNextPage: $response.body.pagination.hasNextPage + hasPreviousPage: $response.body.pagination.hasPreviousPage + + outputs: + incomingPayments: $steps.listIncomingPayments.outputs.incomingPayments + nextCursor: $steps.listIncomingPayments.outputs.paginationEndCursor + hasNextPage: $steps.listIncomingPayments.outputs.hasNextPage + + # ─── Workflow 2: List outgoing payments ─── + - workflowId: listOutgoingPayments + summary: "List all outgoing payments on a wallet address" + description: | + Obtain a grant and list outgoing payments with pagination support. + inputs: + type: object + required: + - walletAddressUrl + - clientWalletAddress + properties: + walletAddressUrl: + type: string + description: "The wallet address to list outgoing payments for" + clientWalletAddress: + type: string + description: "The client's wallet address for GNAP identification" + first: + type: integer + description: "Number of results to return" + cursor: + type: string + description: "Pagination cursor" + + steps: + - stepId: getWalletAddress + description: > + Resolve the wallet address to discover server URLs. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + authServer: $response.body.authServer + resourceServer: $response.body.resourceServer + + - stepId: requestOutgoingPaymentListGrant + description: > + Request a non-interactive grant with `list` permissions for + outgoing payments. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: outgoing-payment + actions: + - list + identifier: $inputs.walletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + accessToken: $response.body.access_token.value + + - stepId: listOutgoingPayments + description: > + List outgoing payments on the wallet address with pagination. + operationId: resourceServer.list-outgoing-payments + parameters: + - name: wallet-address + in: query + value: $inputs.walletAddressUrl + - name: first + in: query + value: $inputs.first + - name: cursor + in: query + value: $inputs.cursor + successCriteria: + - condition: $statusCode == 200 + outputs: + outgoingPayments: $response.body.result + paginationEndCursor: $response.body.pagination.endCursor + hasNextPage: $response.body.pagination.hasNextPage + + outputs: + outgoingPayments: $steps.listOutgoingPayments.outputs.outgoingPayments + nextCursor: $steps.listOutgoingPayments.outputs.paginationEndCursor + hasNextPage: $steps.listOutgoingPayments.outputs.hasNextPage + + # ─── Workflow 3: Get individual payment details ─── + - workflowId: getPaymentDetails + summary: "Get details of a specific incoming or outgoing payment" + description: | + Retrieve the current state of a specific payment resource by its ID. + inputs: + type: object + required: + - walletAddressUrl + - clientWalletAddress + properties: + walletAddressUrl: + type: string + description: "Wallet address URL" + clientWalletAddress: + type: string + description: "Client's wallet address" + incomingPaymentId: + type: string + description: "ID of the incoming payment to retrieve" + outgoingPaymentId: + type: string + description: "ID of the outgoing payment to retrieve" + + steps: + - stepId: getWalletAddress + description: > + Resolve the wallet address. + operationId: walletAddressServer.get-wallet-address + successCriteria: + - condition: $statusCode == 200 + outputs: + authServer: $response.body.authServer + + - stepId: requestReadGrant + description: > + Request a non-interactive grant with `read` permissions for both + incoming and outgoing payments. + operationId: authServer.post-request + requestBody: + contentType: application/json + payload: + access_token: + access: + - type: incoming-payment + actions: + - read + identifier: $inputs.walletAddressUrl + - type: outgoing-payment + actions: + - read + identifier: $inputs.walletAddressUrl + client: $inputs.clientWalletAddress + successCriteria: + - condition: $statusCode == 200 + outputs: + accessToken: $response.body.access_token.value + + - stepId: getIncomingPayment + description: > + Retrieve the details of a specific incoming payment. The response + includes received amounts, completion status, and payment methods. + operationId: resourceServer.get-incoming-payment + parameters: + - name: id + in: path + value: $inputs.incomingPaymentId + successCriteria: + - condition: $statusCode == 200 + outputs: + incomingPayment: $response.body + + - stepId: getOutgoingPayment + description: > + Retrieve the details of a specific outgoing payment. The response + includes sent amounts, failure status, and payment metadata. + operationId: resourceServer.get-outgoing-payment + parameters: + - name: id + in: path + value: $inputs.outgoingPaymentId + successCriteria: + - condition: $statusCode == 200 + outputs: + outgoingPayment: $response.body + + outputs: + incomingPayment: $steps.getIncomingPayment.outputs.incomingPayment + outgoingPayment: $steps.getOutgoingPayment.outputs.outgoingPayment