diff --git a/.github/scripts/docs/fix_asset_paths.py b/.github/scripts/docs/fix_asset_paths.py new file mode 100644 index 0000000..9a1ed76 --- /dev/null +++ b/.github/scripts/docs/fix_asset_paths.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import io +import os +import sys + +def rewrite_paths(path: str) -> bool: + if not os.path.isfile(path): + return False + with io.open(path, 'r', encoding='utf-8', errors='ignore') as f: + s = f.read() + + # Markdown: ](/docs/... -> ](./docs/... + s = s.replace('](/docs/', '](./docs/') + # Markdown escaped (embedded JSON): ](\/docs\/ -> ](./docs/ + s = s.replace('](\\/docs\\/', '](./docs/') + + # HTML attributes + s = s.replace('src="/docs/', 'src="./docs/') + s = s.replace("src='/docs/", "src='./docs/") + s = s.replace('href="/docs/', 'href="./docs/') + s = s.replace("href='/docs/", "href='./docs/") + + # Generic occurrences in embedded JSON: \/docs\/ -> \.\/docs\/ + s = s.replace('\\/docs\\/', '\\./docs\\/') + + with io.open(path, 'w', encoding='utf-8') as f: + f.write(s) + print(f"Rewrote /docs -> ./docs in {path}") + return True + + +def main(argv): + if len(argv) < 2: + print("Usage: fix_asset_paths.py ", file=sys.stderr) + return 2 + path = argv[1] + ok = rewrite_paths(path) + return 0 if ok else 0 + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv)) + diff --git a/.github/scripts/docs/inject_mermaid.sh b/.github/scripts/docs/inject_mermaid.sh new file mode 100644 index 0000000..e7df7f6 --- /dev/null +++ b/.github/scripts/docs/inject_mermaid.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: +# bash scripts/docs/inject_mermaid.sh [MERMAID_VERSION] +# or with env vars INDEX and MERMAID_VERSION + +INDEX="${1:-${INDEX:-}}" +MERMAID_VERSION="${2:-${MERMAID_VERSION:-10}}" + +if [[ -z "${INDEX}" ]]; then + echo "INDEX path not provided" >&2 + exit 1 +fi + +if [[ ! -f "${INDEX}" ]]; then + echo "Index not found: ${INDEX}" + exit 0 +fi + +export INDEX MERMAID_VERSION +python3 - << 'PY' +import os, io + +p = os.environ['INDEX'] +version = os.environ.get('MERMAID_VERSION', '10') + +with io.open(p, 'r', encoding='utf-8', errors='ignore') as f: + s = f.read() + +if '' in s: + print('Mermaid already injected; skipping.') +else: + inject = '''\ + + + + +''' + inject = inject.replace('__VER__', version) + + if '' in s: + s = s.replace('', inject + '\n', 1) + else: + s = s + inject + + with io.open(p, 'w', encoding='utf-8') as f: + f.write(s) + print('Injected Mermaid support into', p) +PY + diff --git a/.github/workflows/backend-docs.yml b/.github/workflows/backend-docs.yml new file mode 100644 index 0000000..2e2d100 --- /dev/null +++ b/.github/workflows/backend-docs.yml @@ -0,0 +1,207 @@ +name: backend-docs + +on: + workflow_call: + inputs: + docs_dir: + description: Directory contenente description.md e openapi + required: false + default: "docs" + type: string + openapi_path: + description: Path a openapi (se non usare autodetect) + required: false + default: "" + type: string + mermaid_version: + description: Versione di Mermaid JS da iniettare (es. 10 o 10.9.1) + required: false + default: "10" + type: string + description_md: + description: Path a description.md (se diverso da default) + required: false + default: "" + type: string + output_dir: + description: Directory di output static site (per Pages) + required: false + default: "site" + type: string + node_version: + description: Versione Node per npx (@redocly/cli/marked) + required: false + default: "20" + type: string + deploy_pages: + description: Esegui deploy su GitHub Pages + required: false + default: "true" + type: string + checkout_submodules: + description: Checkout Git submodules as part of docs (true/false) + required: false + default: "false" + type: string + publish: + description: When 'true' uploads artifact and deploys Pages + required: false + default: "true" + type: string + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build-and-publish-docs: + name: Build Redoc HTML and Publish + runs-on: ubuntu-22.04 + timeout-minutes: 20 + defaults: + run: + working-directory: ${{ github.workspace }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: ${{ inputs.checkout_submodules == 'true' }} + # If you use submodules to pull docs from private repos, set submodules: true in the caller + # with: + # submodules: true + + - name: Check gh-pages branch availability + if: ${{ inputs.publish == 'true' && (inputs.deploy_pages || 'true') == 'true' }} + id: ghpages + shell: bash + run: | + set -euo pipefail + if git ls-remote --exit-code --heads "https://github.com/${GITHUB_REPOSITORY}.git" gh-pages >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "::notice title=gh-pages branch::No gh-pages branch found. Building from empty site state." + fi + + - name: Checkout existing Pages (gh-pages) + if: ${{ inputs.publish == 'true' && (inputs.deploy_pages || 'true') == 'true' && steps.ghpages.outputs.exists == 'true' }} + uses: actions/checkout@v4 + with: + ref: gh-pages + path: .gh-pages-prev + + - name: Setup Node.js ${{ inputs.node_version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version || '20' }} + + - name: Resolve BRANCH_DIR (head_ref or ref_name) + id: branchdir + shell: bash + run: | + set -euo pipefail + if [[ -n "${GITHUB_HEAD_REF:-}" ]]; then + RAW="$GITHUB_HEAD_REF" + else + RAW="$GITHUB_REF_NAME" + fi + SANITIZED="${RAW//\//-}" + echo "Resolved BRANCH values: raw='$RAW' sanitized='$SANITIZED'" + echo "::notice title=BRANCH_DIR::Using '$SANITIZED' (raw: '$RAW')" + echo "BRANCH_RAW=$RAW" >> "$GITHUB_ENV" + echo "BRANCH_DIR=$SANITIZED" >> "$GITHUB_ENV" + echo "dir=$SANITIZED" >> "$GITHUB_OUTPUT" + echo "raw=$RAW" >> "$GITHUB_OUTPUT" + + - name: Build docs with @redocly/cli (bundle per branch) + shell: bash + run: | + set -euo pipefail + DOCS_DIR="${{ inputs.docs_dir || 'docs' }}" + OPENAPI="${{ inputs.openapi_path || '' }}" + OUT_DIR="${{ inputs.output_dir || 'site' }}" + BRANCH_DIR="${BRANCH_DIR}" + mkdir -p "$OUT_DIR" + if [[ -d .gh-pages-prev ]]; then + rsync -a --exclude '.git' .gh-pages-prev/ "$OUT_DIR/" || true + fi + if [[ -z "$OPENAPI" ]]; then + for f in "$DOCS_DIR"/openapi.yaml "$DOCS_DIR"/openapi.yml "$DOCS_DIR"/openapi.json; do + if [[ -f "$f" ]]; then OPENAPI="$f"; break; fi + done + fi + if [[ -z "$OPENAPI" || ! -f "$OPENAPI" ]]; then + echo "File OpenAPI non trovato (cerca in ${DOCS_DIR}/openapi.yaml|yml|json o specifica input openapi_path)" >&2 + exit 2 + fi + mkdir -p "$OUT_DIR/$BRANCH_DIR" + + npx -y @redocly/cli build-docs "$OPENAPI" -o "$OUT_DIR/$BRANCH_DIR/index.html" + rsync -a --include='*/' --include='*.png' --exclude='*' "$DOCS_DIR"/ "$OUT_DIR/$BRANCH_DIR/docs/" || true + + + - name: Fix asset paths in generated HTML (HTML + embedded JSON) + shell: bash + continue-on-error: true + run: | + set -euo pipefail + OUT_DIR="${{ inputs.output_dir || 'site' }}" + BRANCH_DIR="${BRANCH_DIR}" + INDEX="$OUT_DIR/$BRANCH_DIR/index.html" + + if [[ ! -f "$INDEX" ]]; then + echo "Index not found: $INDEX" + exit 0 + fi + python3 ".github/scripts/docs/fix_asset_paths.py" "$INDEX" + + - name: Enable Mermaid diagrams (inject script) + shell: bash + continue-on-error: true + run: | + set -euo pipefail + OUT_DIR="${{ inputs.output_dir || 'site' }}" + BRANCH_DIR="${BRANCH_DIR}" + INDEX="$OUT_DIR/$BRANCH_DIR/index.html" + MERMAID_VERSION="${{ inputs.mermaid_version || '10' }}" + bash ".github/scripts/docs/inject_mermaid.sh" "$INDEX" "$MERMAID_VERSION" + # --- publish gating (artifact + deploy) --- + + - name: Upload Pages artifact + if: ${{ inputs.publish == 'true' && (inputs.deploy_pages || 'true') == 'true' }} + uses: actions/upload-pages-artifact@v3 + with: + name: github-pages-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ inputs.output_dir || 'site' }} + + - name: Deploy to GitHub Pages + if: ${{ inputs.publish == 'true' && (inputs.deploy_pages || 'true') == 'true' }} + id: deployment + uses: actions/deploy-pages@v4 + with: + artifact_name: github-pages-${{ github.run_id }}-${{ github.run_attempt }} + + - name: Report GitHub Pages URLs + if: ${{ inputs.publish == 'true' && (inputs.deploy_pages || 'true') == 'true' && steps.deployment.outputs.page_url != '' }} + shell: bash + run: | + set -euo pipefail + BASE_URL="${{ steps.deployment.outputs.page_url }}" + BRANCH_URL="${BASE_URL%/}/${BRANCH_DIR}/" + echo "::notice title=Pages base URL::${BASE_URL}" + echo "::notice title=Pages branch URL::${BRANCH_URL}" + { + echo "### GitHub Pages" + echo "- Base URL: ${BASE_URL}" + echo "- Branch URL (${BRANCH_RAW}): ${BRANCH_URL}" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Skipping publish (publish='${{ inputs.publish }}', deploy_pages='${{ inputs.deploy_pages }}') + if: ${{ inputs.publish != 'true' || (inputs.deploy_pages || 'true') != 'true' }} + run: echo "Skipping artifact upload and deploy (publish=${{ inputs.publish }}, deploy_pages=${{ inputs.deploy_pages }})" diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 0000000..8221ddc --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,21 @@ +name: Docs Build + +on: + push: + workflow_dispatch: + pull_request: + paths: + - "docs/**" + - "openapi.json" + - ".github/workflows/docs-build.yml" + - ".github/workflows/backend-docs.yml" + - ".github/scripts/docs/**" + +jobs: + build: + name: Reusable Docs Build + uses: ./.github/workflows/backend-docs.yml + with: + docs_dir: docs + openapi_path: openapi.json + publish: ${{ github.event_name == 'push' && 'true' || 'false' }} diff --git a/.gitignore b/.gitignore index 1096971..9b5881f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .vscode/* .DS_Store openapi.html +postman_collection.json +Simplified Flow.pdf +old-openapi.json +.tmp/ diff --git a/docs/bulk.md b/docs/bulk.md new file mode 100644 index 0000000..817e7b4 --- /dev/null +++ b/docs/bulk.md @@ -0,0 +1,48 @@ +You can allow users to pay multiple payees in a single operation using the additionalPayees parameter when creating a payment request. + +[![](https://mermaid.ink/img/pako:eNqFk81um0AQx18FTQ-5kGgxsGAOkUzanqoqcqxWqris2cFZFXbdZYnsWL70efpUeZIuH66hiRVOszO_-c9_PzhArjhCArVhBj8KttGsyqRjPy405kYo6XxZ9pkUJRYiF0zvF8719a2z4Fy0BCvv2R6xThyPkJfff17h6QU8fJO-u0DPwrfoS1aiM_1_setYru77qg26xGrRjjg7WvXK3-052HG26I0kp8V0spVp7W4wDi5stOCQGN2gCxXqirVLOLRdGZhHrDCDxIZrVtvIHeW_MS3YusS6BQ79mAwKJc1nVoly3_ddLdVaGXXlOk-oOZPMddq-ctA6tTyI52GQR7e7UXGrRdWevyqV7oEPnKNf5K-ZVGmOekwG1M-LYkQy-3qeWHvu6c_NmCxmRTzRHJHvyw4GVrgzY84jPglwxNX4q0GZ49emWk8lT3tqyWMmj_Zmtkz-UKo6XY5WzeYRkoKVtV01W37-Of5lNcrOaiMNJPNo1olAcoAdJF4Y3ZCQ0HlMI0r9yPNd2EMyC2KbpkEc0LmtUhIeXXju5pKbICC2jYRhHAbRPPKpC6wx6mEv85Ot3sgn-5iVHnwc_wL-ZjBd?type=png)](https://mermaid.live/edit#pako:eNqFk81um0AQx18FTQ-5kGgxsGAOkUzanqoqcqxWqris2cFZFXbdZYnsWL70efpUeZIuH66hiRVOszO_-c9_PzhArjhCArVhBj8KttGsyqRjPy405kYo6XxZ9pkUJRYiF0zvF8719a2z4Fy0BCvv2R6xThyPkJfff17h6QU8fJO-u0DPwrfoS1aiM_1_setYru77qg26xGrRjjg7WvXK3-052HG26I0kp8V0spVp7W4wDi5stOCQGN2gCxXqirVLOLRdGZhHrDCDxIZrVtvIHeW_MS3YusS6BQ79mAwKJc1nVoly3_ddLdVaGXXlOk-oOZPMddq-ctA6tTyI52GQR7e7UXGrRdWevyqV7oEPnKNf5K-ZVGmOekwG1M-LYkQy-3qeWHvu6c_NmCxmRTzRHJHvyw4GVrgzY84jPglwxNX4q0GZ49emWk8lT3tqyWMmj_Zmtkz-UKo6XY5WzeYRkoKVtV01W37-Of5lNcrOaiMNJPNo1olAcoAdJF4Y3ZCQ0HlMI0r9yPNd2EMyC2KbpkEc0LmtUhIeXXju5pKbICC2jYRhHAbRPPKpC6wx6mEv85Ot3sgn-5iVHnwc_wL-ZjBd) + +The above diagram shows how bulk works. +We assume the case in which a user wants to pay Beneficiary A for 100€, Beneficiary B for 50€ and Beneficiary C for 25€ and again Beneficiary A for 75€. +The additionalPayees parameter will be populated like this (you can also specify a per-payee `remittanceInformation`; if omitted, the main request `remittanceInformation` is used): + +```json +{ + "additionalPayees": [ + { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A", + "amount": 100, + "remittanceInformation": "Order A-100" + }, + { + "iban": "IT79Q0300203280941591243327", + "name": "Beneficiary B", + "amount": 50 + }, + { + "iban": "IT79Q0300203280941591243328", + "name": "Beneficiary C", + "amount": 25, + "remittanceInformation": "Service fee C" + }, + { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A", + "amount": 75 + } + ] +} +``` + +The payment request created will have an amount of 250€; the payer can now pay it with a single payment. +The wire transfer allowed with the PIS on a bulk payment is addressed to the FlowPay technical account (TA). +When the TA receives the payment, it splits the amount among the beneficiaries, groups them by their IBANs and names, and sends the payments to them with wire transfers. + +In case of a bulk payment to the same beneficiary, the payment initiation effective beneficiary is the beneficiary itself, so the payer can easily recognize the transaction. Otherwise, the payment initiation effective beneficiary is FlowPay. +Wire transfers to beneficiaries are sent with the same original payer, so beneficiaries can easily identify the payer. + +
+
In case of PagoPA payments
+ The bulk service is not natively supported with PagoPA payments; please contact us for more information and workarounds. +
diff --git a/docs/bulk_lifecycle.md b/docs/bulk_lifecycle.md deleted file mode 100644 index 1ad2bcb..0000000 --- a/docs/bulk_lifecycle.md +++ /dev/null @@ -1,18 +0,0 @@ -This endpoint enables bulk payments on documents that could be of different types.
Note: API of this endpoint does not require specific scopes, the authorization token used must have access to documents it wants to bulk pay - -

- -

- -The above diagram shows how bulk works. -We assume the case in which a user has two invoices for beneficiary 1 (Ben1), one invoice for beneficiary 2 (Ben2), and one PagoPA payment -The user wants to pay all of them in a single operation. - -The user's client, which has access to all the documents, calls the bulk endpoint with the list of documents to pay, the endpoint returns a new document (Bulk) that is linked to all the documents to pay. -The client gives the bulk document to the user, who can now pay it with a single Payment Initiation (PIS) operation. The wire transfer allowed with the PIS on the bulk document is addressed to the FlowPay technical account (TA). -When the TA receives the payment, it splits the amount among the beneficiaries and sends the payments to them with wire transfers. - -In case of a bulk payment to the same beneficiary, the payment initiation effective beneficiary is the beneficiary itself, so the payer can easily recognize the transaction. Otherwise, the payment initiation effective beneficiary is FlowPay. -Wire transfers to beneficiaries are sent with the same original payer, so beneficiaries can easily identify the payer. - -Note: pagoPA payments recipient is FlowPay itself, so related payments are not counted in the split operations. diff --git a/docs/chain_lifecycle.md b/docs/chain_lifecycle.md deleted file mode 100644 index f871f5f..0000000 --- a/docs/chain_lifecycle.md +++ /dev/null @@ -1,17 +0,0 @@ -This endpoint enables the chaining of payments across various documents by automating the payment of one invoice using the amount received from another active invoice.
Note: The API for this endpoint does not require specific scopes; the authorization token used must have access to the documents it wants to pay. - -The payment chain process works by linking a "ring" document that connects a trigger document to a target document. Once the ring document is created, it establishes a connection between the trigger document, which initiates the payment chain, and the target document, which will receive the payment once the trigger document has been settled through FlowPay. - -For example, let's assume there are two documents. The first document involves a payment that Business A must make to Business B, and the second document involves a payment that Business B must make to Business C. To automate these payments, Business B creates a "ring" using its authorization token. This ring links the first document as the "trigger" (activation document) and the second document as the "target" (destination document). - -Once the ring is created, checkout sessions can be generated for each document. The checkout for the trigger document proceeds as a standard payment process, while the checkout for the target document is created with a chain type. This chain-type checkout for the target document does not require an immediate payment but awaits the payment of the trigger document. - -When Business B logs in and completes the checkout for the target document, no payment is made at that point. However, when the trigger document is paid, it automatically triggers the payment of the target document. If there is any residual amount after the payment of the target document, Business B will receive it. - -In this way, the payment automation ensures that all documents involved are settled sequentially, using the funds received from one active document to pay another pending document, minimizing manual intervention and simplifying the cash flow management between the involved businesses. - -### Using Payment Chain for Split Payment - -In a split payment scenario, the payment chain can be utilized to automatically distribute funds between multiple parties, including retaining a portion of the payment for the partner before the remaining amount is forwarded to the creditor. For example, suppose Business A needs to pay Business B, but a portion of the payment is retained by Business C (acting as a partner or intermediary). Business A would create a trigger document representing the payment it owes to Business B. A ring document is then created, linking this trigger document to two target documents: one for Business B and another for Business C. - -When Business A makes the payment, the payment chain ensures that the funds are first routed through Business C. The portion that Business C is entitled to (as agreed upon) is retained, and the remainder of the payment is automatically forwarded to Business B. This setup ensures that the split payment is handled seamlessly, with minimal manual intervention, and all parties receive their due amounts according to the predefined rules of the payment chain. diff --git a/docs/checkout.md b/docs/checkout.md new file mode 100644 index 0000000..6f76118 --- /dev/null +++ b/docs/checkout.md @@ -0,0 +1,35 @@ +# Hosted Checkout + +The hosted checkout page handles the user experience to select a payment method, perform Strong Customer Authentication (SCA) with the bank (for PIS), and complete the payment. + +- Link: use the `link` returned by `POST /payment-requests`. +- Redirect: set `redirectUrl` at creation time to receive the user back with `?status=success|error|cancel`. +- Callback: set `callbackUrl` to receive server-to-server notifications of status changes. + +## Redirect parameters + +When the checkout completes or the user cancels, the user is redirected to `redirectUrl` with a `status` query parameter: + +- `success`: the payment has been authorised. +- `error`: the payment failed or an unrecoverable error occurred. +- `cancel`: the user exited the flow. + +Optionally, `requestId` and `sessionId` can be included for convenience. Always use the API to retrieve the authoritative status. + +## Callback notifications + +If `callbackUrl` is provided, FlowPay sends server-to-server notifications when the payment status changes via a single event type `payment.status_change`. Inspect the `status` field in the payload (enum `PaymentRequestStatus`: `created`, `inProgress`, `authorized`, `rejected`, `onHold`, `locked`, `forwarded`, `refunded`, `deleted`). + +- Method: POST +- Headers: `Content-Type: application/json` +- Payload: includes `requestId`, optional `sessionId`, `status`, and timestamps. + +Note: ensure idempotency on your endpoint; the same event may be retried. + +## Theming and branding + +Branding (logo, primary color, legal info) is configured per partner in the FlowPay backoffice. Contact support to enable custom themes or environment-specific branding. + +## Mobile and PWA behavior + +The checkout behaves as a mobile-friendly web app and supports handoff between desktop and mobile via QR code in the PDF returned by `GET /payment-requests/{requestId}` with `Accept: application/pdf`. diff --git a/docs/fee_description.md b/docs/fee_description.md index 2796c07..eb5e0f3 100644 --- a/docs/fee_description.md +++ b/docs/fee_description.md @@ -4,14 +4,13 @@ FlowPay provides a fully automated fee management system that simplifies the pro Fees calculation rules are determined during the initial system setup, tailored to the specific contractual requirements of each partner. These fees can be charged either to the creditor or the debtor, depending on the case. When the creditor is responsible for the fee, a split payment is automatically generated. Conversely, if the debtor bears the cost, a bulk payment is triggered. -Each rule is defined for a specific payment method (pis, sdd, card) and type of document. Additionally, minimum amounts can be established, below which fees are not applied, this allows the creation of exceptions to the general rule or progressive fee structures. +The fee calculation rules are flexible and can be customized to suit various payment scenarios. Rules can be defined based on the type of payment (e.g., bulk, split, locked, PagoPA) and the payment method used (e.g., PIS, SDD, card). Also, minimum amounts can be set, below which fees are not applied, allowing for exceptions to the general rule or progressive fee structures. Rules also determine which party will be responsible for the fees, ensuring a clear and transparent process. -In cases where documents involve multiple due dates subject to fees, the system does not allow partial payments tied to individual deadlines. Instead, the user is required to make a single full payment, in line with the overall terms of the document. +Every fee rule is composed of a fixed component and a variable one based on the amount of the transaction. +For bulk and split payments, the fee includes an additional fixed component for each beneficiary; this covers the costs of payments from the technical account to the beneficiaries. -It is also possible to specify whether the fee is calculated as a percentage of the total amount of the document to be paid or based on a fixed amount per unit. +In case of partial payments, the system automatically calculates the fee based on the amount of the single payment, ensuring that the fee is applied consistently across all payments. If the fee rule changes, the system automatically applies the new rules to all future payments, ensuring that the most up-to-date fee structure is always in effect. # Lifecycle -When the user selects the payment method to be used, the fee calculation is done automatically by the payment gateway retrieving the most up-to-date rule applicable to that specific payment. Once the calculation is performed, the gateway automatically inserts the fee document into an aggregated document (Bulk). - -In accordance with the documentation related to bulk payments, if a user accesses a checkout linked to a document that has subsequently been included in a Bulk document, the payment gateway will automatically retrieve the final Bulk document. This ensures that the payment process is always aligned with the most recent information, simplifying the management of fees and their application to multiple payments. +When the payer selects the payment method to be used, the fee calculation is done automatically by the payment gateway retrieving the most up-to-date rule applicable to that specific payment. Once the calculation is performed, the gateway automatically inserts the fee amount into the payment request. diff --git a/docs/general.md b/docs/general.md index 7f614cb..f9e13b1 100644 --- a/docs/general.md +++ b/docs/general.md @@ -1,65 +1,69 @@ -[![Run in Postman](https://run.pstmn.io/button.svg)](/postman_collection) -


-If you can't find what you're looking for, it doesn't mean we can't do it. Write to us and tell us about your idea. + + + + # API Support If you have any questions or need help with the APIs, you can open a ticket on our support portal. Click on the button below to open a ticket. - + +
- # FlowPay's commitment to the payments ecosystem -FlowPay is both a payment institution and a start-up. - -As a Payment Service Provider (PSP), we are regulated by the Bank of Italy and we are authorised to provide payment services to our customers. As a start-up, we are always looking for new ideas and new ways to improve our services and help you grow your business. +As a Payment Service Provider (PSP), FlowPay is regulated by the Bank of Italy and we are authorised to provide payment services to our customers. We believe that the best API a payment institution can provide is one that is tailored to the payment use case, **allowing customers to focus on their business and not the payment process**. On the end user side, FlowPay ensures that **users own their data, can access it at any time and fully manage it**. They can choose which data to share with third parties in the most transparent way possible. -# Introduction +## Contributions + +FlowPay welcomes contributions from partners. The OpenAPI file is publicly available on GitHub: [FlowPay/client-openapi](https://github.com/FlowPay/client-openapi). -FlowPay's services compose a multi-tenant platform, where each tenant is a user of FlowPay and can be a legal entity or an individual. -Users can delegate third-party applications, called _clients_, to act on their behalf using the OAuth2 protocol. +Partners can propose changes by forking the repository and submitting a pull request. + +# Introduction -The APIs provided are REST and accessible via HTTPS, some endpoints are protected by the OAuth2, so you need to register your application and obtain a valid access token to use them. +The APIs are REST over HTTPS. Some endpoints are restricted; partners must register their application and obtain an API key from the developer portal to access them. API keys can be rotated, scoped, and revoked at any time from the backoffice. ## Account Information Service (AIS) -AIS (Account Information Service) is a financial service that allows third parties to access a user's account information from different banks or financial institutions. AIS works by using APIs provided to securely connect to the user's bank account and retrieve the necessary information. +FlowPay is authorised to provide AIS. In the Simplified Flow model, consents are collected directly by FlowPay via a hosted flow to minimise partner burden. -As a payment institution authorised by the Bank of Italy, FlowPay can offer AIS to its customers, allowing them to **access account information, balances and transaction history of the tenants' bank accounts** for which they are authorised. -These services enable many use cases such as account aggregation, personal financial management, credit scoring and many others. +Endpoints: -In line with its principles, **FlowPay provides a seamless and compliant way to access tenants' bank details**, taking on the burden of negotiating PSD2 consent with the user and **providing a single API to access all banks**. All PSD2 consents are collected directly by FlowPay, so **there is no need for the client to implement a consent acquisition or renewal process**. -If desired, a client can initiate a consent acquisition process themselves and manage the user experience. +- `POST /ais/consents`: create a consent session; returns a `link` to complete SCA with the bank. +- `GET /ais/consents/{consentId}`: retrieve consent status (`pending`, `active`, `expired`, ...). +- `GET /ais/accounts`: list available bank accounts under active consents. +- `GET /ais/accounts/{accountId}/balances`: current and available balances. +- `GET /ais/accounts/{accountId}/transactions`: list transactions, filterable by date. -## Payment Initiation Service (PIS) +If no active consent exists, AIS endpoints return `403` and partners can create a new consent using the consent endpoint. -PIS (Payment Initiation Service) is a financial service that allows third-party providers to initiate a payment transaction from a user's bank account. PIS works by using APIs provided to securely connect to the user's bank account and initiate the payment. +## Payment Initiation Service (PIS) -FlowPay is an authorised PIS Provider (PISP), which means that it can mediate between the user and the bank to authorise the payment. +FlowPay is an authorised PIS Provider (PISP). In the Simplified Flow, partners create Request To Pay objects and direct users to a hosted checkout to perform Strong Customer Authentication with their bank and authorise the payment. -APIs allow users to initiate any traditional payment type: +APIs allow users to initiate traditional payment types: - Simple account-to-account payment: user can initiate a SEPA Credit Transfer (SCT) payment from one of its bank accounts. - Future date payment: the payer can schedule a payment for a future date. -- Recurring payment: the payer can schedule a recurring payment with a fixed frequency. In addition, FlowPay extends traditional payment methods by providing value-added services such as @@ -67,111 +71,144 @@ In addition, FlowPay extends traditional payment methods by providing value-adde - **Payment chain**: user can authorise a payment to be executed when a previous payment has been successfully received. - **Locked payment**: the user can authorise a payment to be executed if a previous payment has been successfully received. The check is performed by the client application that initiated the payment request. -Each of these services uses the FlowPay technical account, but the payment retains the original payer and payee information. - -# Onboarding - -Tenants can share their FlowPay resources with third-party applications by simply granting permission based on the OAuth2 protocol. +Each of these services may route funds via a FlowPay technical account when required by business rules, while preserving original payer/payee information in remittance data. -A partner who intends to develop an integration to access tenants' data must first register its application and obtain the `client_id` and `client_secret` pair. +## Hosted Checkout -The developer portal can be reached at https://developer.flowpay.it, to access it's necessary to have a company account registered with FlowPay services. +See checkout behavior, redirects, callbacks, and branding in `docs/checkout.md`. -## Become part of the FlowPay ecosystem +# Onboarding -To use FlowPay APIs you need to register your tenant, create your first application and obtain a valid access token. If you don't have an account, you need to register. +Partners register their application in the developer portal () and obtain one or more API keys with configurable scopes. Keys can be rotated or revoked at any time. Access to the portal requires a company account enabled for FlowPay services. -The onboarding procedure takes a few minutes, to start you need to click the _Register_ button at the bottom of the developer portal. +# Sandbox environment -The first step of registration is to verify a bank account, this step is also one of the two requirements for verifying the company's identity, therefore the linked account must belong to the company you intend to register. +FlowPay provides a sandbox environment to allow partners to test the APIs before going into production. The sandbox is a safe space where you can experiment with the APIs without affecting real accounts or transactions. -Once the bank account has been verified, the system will automatically retrieve the company's details. Finally, you need to provide your personal information as a contact person for the company. If you want, you can also proceed with your identity verification and obtain your personal FlowPay account, this adds a level of security and helps to track tokens granted for your company and could be mandatory to grant a token for some advanced services. + -If you need to test the full functionality of the APIs, you can request a dedicated sandbox environment, and test all the features of the APIs, including the onboarding flow. +# Pagination -# Oauth2 authentication flows +FlowPay APIs implement offset-based pagination on all list endpoints, following a standard response structure. -FlowPay uses the OAuth2 protocol to authenticate third-party applications and users. -Oauth2 is the industry standard for authentication and authorization, and is used by most of the major companies in the world. +## Query parameters -The scopes +When requesting a paginated resource, the following query parameters are supported: -FlowPay supports the following authentication flows: +- `limit` (integer): the maximum number of items to return. Default is 50, maximum is 100. +- `offset` (integer): the number of items to skip before starting to return results. -## Authorization code flow +## Response structure -## Client credentials flow +Each paginated response follows the `PaginatedResult` format: -The Client Credentials grant type is used by clients to obtain an access token outside of the context of a user. +- `total`: total number of available items. +- `limit`: maximum number of items returned in this page (as requested). +- `offset`: number of items skipped from the beginning of the collection. +- `count`: number of items actually returned in this response. +- `items`: array of objects representing the current page results. -This is typically used by clients to access resources about themselves rather than to access a user's resources. +## Example -The client makes a request to the token endpoint by sending the following parameters using the "application/x-www-form-urlencoded" format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body: +### Request -- grant_type - - REQUIRED. Value MUST be set to "client_credentials". -- scope - - OPTIONAL. The scope of the access request as described by Section 3.3. -- client_id - - REQUIRED. The client identifier as described by Section 2.2. -- client_secret - - REQUIRED. The client secret as described by Section 2.3.1. +```markdown +### Request +GET /payment-requests?limit=20&offset=0 +X-API-Key: {api_key} +``` -## How token works and tenant filtering +### Response -(negli header ) +```json +{ + "total": 147, + "limit": 20, + "offset": 0, + "count": 20, + "items": [ + { + "id": "req_12345", + "amount": 1000, + "currency": "EUR", + "created_at": "2024-06-01T10:00:00Z", + "status": "created" + } + ] +} +``` -# Pagination +This structure allows clients to calculate pagination UI and control navigation across multiple pages using `offset` and `limit`. # Rate limits -Requests are limited to 100 requests per minute per source IP, if you exceed this limit you will receive a 429 error. - -There is also a burst limit of 10 requests per second. - -

Tenant

- -

A tenant can be a company or a single person, is identified by a unique identifier (tenant ID ) and owns its data, - for transparency and security reasons each tenant can manage its data via the dedicated Account portal -

+Requests are limited to 100 requests per minute per source IP; exceeding this limit results in 429 responses. A burst limit of 10 requests per second also applies. Production limits can be customised per partner on request. diff --git a/docs/invoice_lifecycle.md b/docs/invoice_lifecycle.md deleted file mode 100644 index c8f3f85..0000000 --- a/docs/invoice_lifecycle.md +++ /dev/null @@ -1,25 +0,0 @@ -# Invoice lifecycle - -This endpoint allows you to create, update, and retrieve invoices. It also allows you to manage each step of the invoice lifecycle, from draft to get paid. - -## Invoice - -An invoice is a commercial document that itemizes a transaction between a buyer and a seller. It serves as a request for payment from the seller to the buyer, indicating the products or services provided, their quantities, prices, and any applicable taxes or discounts. Invoices typically include payment terms, such as due dates and accepted payment methods. -Only legal entities can issue invoices. - -In order to work seamlessly with real customers' invoice management, we provide also API to manage proforma and credit notes. - -## Proforma invoice - -A proforma invoice is a preliminary invoice that is issued before the completion of a transaction. It provides an estimate of the costs and terms of a potential sale, allowing the buyer to review and approve them before proceeding. Proforma invoices are often used in international trade to facilitate customs procedures, as they provide a detailed breakdown of the expected costs, including shipping charges, taxes, and duties. They are also used to provide a quote for potential buyers or to seek approval for a budgeted purchase. -In common practice, payments are often collected against proforma invoices and then the final invoice is issued, FlowPay supports this scenario. -Creating a checkout for a proforma invoice is allowed, but it's necessary to provide the final invoice details when it's issued and link it to the original proforma invoice. -Linking a proforma invoice to a paid invoice is also supported, this can be useful to keep track of the entire invoicing process. - -When linked, the invoice replaces previously created proforma checkout and the payment status of each document is updated accordingly. - -## Credit note - -A credit note, also known as a credit memo or credit memo invoice, is a document issued by a seller to a buyer to indicate a reduction in the amount owed. It is essentially a negative invoice that reflects a credit or refund due to various reasons, such as product returns, billing errors, or pricing adjustments. Credit notes help maintain accurate accounting records and allow for the reconciliation of accounts between buyers and sellers. -You can also create a checkout for a credit note to refund the customer, but it's necessary to provide the original invoice and link it to the original invoice. -Linking a credit note to an invoice will automatically update the invoice status and amount due. diff --git a/docs/kyc_description.md b/docs/kyc_description.md deleted file mode 100644 index f7a27ef..0000000 --- a/docs/kyc_description.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/pagopa_lifecycle.md b/docs/pagopa_lifecycle.md index 877dd2d..5387fac 100644 --- a/docs/pagopa_lifecycle.md +++ b/docs/pagopa_lifecycle.md @@ -4,7 +4,7 @@ As a direct PSP participant in the PagoPA network, FlowPay is fully integrated w FlowPay allows users to pay PagoPA notices by providing the taxpayer identification number (codice fiscale) of the creditor entity and the notice number. Given the dynamic nature of the amounts due, which may change over time, FlowPay automatically updates the payable amount daily and each time a payment session is initiated. -When a payment session for a PagoPA document is started, FlowPay communicates the activation of the notice to the PagoPA system. This notice becomes payable exclusively through the specific session until it is unlocked by FlowPay, which can last up to 30 minutes. Upon successful authorization of the payment, FlowPay releases the payer from the debt and issues a receipt. If an email address is provided via the document upload API, FlowPay sends the receipt by email; otherwise, the partner can retrieve it from a specific endpoint. +When a payment session for a PagoPA document is started, FlowPay communicates the activation of the notice to the PagoPA system. This notice becomes payable exclusively through the specific session until it is unlocked by FlowPay, which can last up to 30 minutes. Upon successful authorization of the payment, FlowPay releases the payer from the debt and issues a receipt. If an email address is provided via the document upload API, FlowPay sends the receipt by email; otherwise, the partner can retrieve it from the API using `GET /payment-requests/{requestId}` with header `Accept: application/pdf`. Additionally, if the partner prefers, they can generate their own receipt to send to the payer by utilizing the data retrievable from FlowPay's API. This allows the partner to have full control over the customer experience and the format of the receipt. diff --git a/docs/pagopa_status.md b/docs/pagopa_status.md index 17ee40a..c035a27 100644 --- a/docs/pagopa_status.md +++ b/docs/pagopa_status.md @@ -1,7 +1,7 @@ -Status of the payment notice on pagopa' systems +Status of the payment notice on PagoPA systems - `ready`: Notice ready to be paid -- `activated`: user has started the payment process, the payment notice has been activated on pagopa' systems. An activated payment notice cannot be paid by another payment provider until it is paid or unactivated. +- `activated`: user has started the payment process, the payment notice has been activated on PagoPA systems. An activated payment notice cannot be paid by another payment provider until it is paid or deactivated. - `locked`: The notice is locked due to an activation from another PSP. -- `paid`: paid with FlowPay +- `paid`: Paid with FlowPay - `paidOnAnotherProvider`: User has paid the notice using another PSP. diff --git a/docs/payment_request.md b/docs/payment_request.md new file mode 100644 index 0000000..f78a6db --- /dev/null +++ b/docs/payment_request.md @@ -0,0 +1,135 @@ +This endpoint enables clients to allow their users to pay with FlowPay using different payment methods. +By default, the client creates a Request To Pay for which they are the creditor. If a debtor isn't specified, the payer will be identified during the payment session. +The client can also specify a different creditor if they are not the intended recipient of the payment. + +The methods currently supported are PIS, Card, Wallet + +PIS (Payment Initiation Service) is a financial service that allows third-party providers to initiate a payment transaction from a user's bank account. PIS works by using APIs provided to securely connect to the user's bank account and initiate the payment. + +FlowPay is an authorized PIS Provider (PISP), meaning it can mediate between the user and their bank to initiate the payment upon the user's authorization. + +PIS APIs allow users to initiate various traditional payment types: + +- **Simple account-to-account payment**: The user can initiate a SEPA Credit Transfer (SCT) payment from one of their bank accounts. +- **Future-dated payment**: The payer can schedule a payment for a future date. +- **Recurring payment**: The payer can schedule a recurring payment with a fixed frequency. + +In addition, FlowPay extends traditional payment methods by providing value-added services, such as: + +- **Bulk payment**: The payer can initiate a single payment with a single Strong Customer Authentication (SCA) to pay multiple payment requests or documents at once. +- **Locked payment**: The user can authorize a payment to be executed only if a previous, related payment has been successfully received. The verification of the previous payment's status is performed by the client application that initiated the payment request. + +Each of these services uses the FlowPay technical account, but the payment retains the original payer and payee information. + +## Locked Payment + +If the lockedUntil field is used, the sum will be kept until the date is reached. Until then the client is able to issue a refund of this payment by using the "Refunds" endpoint, or unlock the payment earlier using the "Charge" endpoint. + +When a request to pay is locked a paymentMethod is returned that can be used on the "Charge" endpoint, to unlock the payment. When unlocking the payment no other field is necessary but a validation will be carried on in order to mantain data integrity. + +## Bulk Payment + +You can allow users to pay multiple payees in a single operation using the additionalPayees parameter when creating a payment request. + +[![](https://mermaid.ink/img/pako:eNqFk81um0AQx18FTQ-5kGgxsGAOkUzanqoqcqxWqris2cFZFXbdZYnsWL70efpUeZIuH66hiRVOszO_-c9_PzhArjhCArVhBj8KttGsyqRjPy405kYo6XxZ9pkUJRYiF0zvF8719a2z4Fy0BCvv2R6xThyPkJfff17h6QU8fJO-u0DPwrfoS1aiM_1_setYru77qg26xGrRjjg7WvXK3-052HG26I0kp8V0spVp7W4wDi5stOCQGN2gCxXqirVLOLRdGZhHrDCDxIZrVtvIHeW_MS3YusS6BQ79mAwKJc1nVoly3_ddLdVaGXXlOk-oOZPMddq-ctA6tTyI52GQR7e7UXGrRdWevyqV7oEPnKNf5K-ZVGmOekwG1M-LYkQy-3qeWHvu6c_NmCxmRTzRHJHvyw4GVrgzY84jPglwxNX4q0GZ49emWk8lT3tqyWMmj_Zmtkz-UKo6XY5WzeYRkoKVtV01W37-Of5lNcrOaiMNJPNo1olAcoAdJF4Y3ZCQ0HlMI0r9yPNd2EMyC2KbpkEc0LmtUhIeXXju5pKbICC2jYRhHAbRPPKpC6wx6mEv85Ot3sgn-5iVHnwc_wL-ZjBd?type=png)](https://mermaid.live/edit#pako:eNqFk81um0AQx18FTQ-5kGgxsGAOkUzanqoqcqxWqris2cFZFXbdZYnsWL70efpUeZIuH66hiRVOszO_-c9_PzhArjhCArVhBj8KttGsyqRjPy405kYo6XxZ9pkUJRYiF0zvF8719a2z4Fy0BCvv2R6xThyPkJfff17h6QU8fJO-u0DPwrfoS1aiM_1_setYru77qg26xGrRjjg7WvXK3-052HG26I0kp8V0spVp7W4wDi5stOCQGN2gCxXqirVLOLRdGZhHrDCDxIZrVtvIHeW_MS3YusS6BQ79mAwKJc1nVoly3_ddLdVaGXXlOk-oOZPMddq-ctA6tTyI52GQR7e7UXGrRdWevyqV7oEPnKNf5K-ZVGmOekwG1M-LYkQy-3qeWHvu6c_NmCxmRTzRHJHvyw4GVrgzY84jPglwxNX4q0GZ49emWk8lT3tqyWMmj_Zmtkz-UKo6XY5WzeYRkoKVtV01W37-Of5lNcrOaiMNJPNo1olAcoAdJF4Y3ZCQ0HlMI0r9yPNd2EMyC2KbpkEc0LmtUhIeXXju5pKbICC2jYRhHAbRPPKpC6wx6mEv85Ot3sgn-5iVHnwc_wL-ZjBd) + +The above diagram shows how bulk works. +We assume the case in which a user wants to pay Beneficiary A for 100€, Beneficiary B for 50€ and Beneficiary C for 25€ and again Beneficiary A for 75€. +The additionalPayees parameter will be populated like this (you can also specify a per-payee `remittanceInformation`; if omitted, the main request `remittanceInformation` is used): + +```json +{ + "additionalPayees": [ + { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A", + "amount": 100, + "remittanceInformation": "Order #A-100" + }, + { + "iban": "IT79Q0300203280941591243327", + "name": "Beneficiary B", + "amount": 50 + }, + { + "iban": "IT79Q0300203280941591243328", + "name": "Beneficiary C", + "amount": 25, + "remittanceInformation": "Service fee C" + }, + { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A", + "amount": 75 + } + ] +} +``` + +The payment request created will have an amount of 250€, payer can now pay it with a single payments. +The wire transfer allowed with the PIS on a bulk payment is addressed to the FlowPay technical account (TA). +When the TA receives the payment, it splits the amount among the beneficiaries, groups them by their IBANs and names, and sends the payments to them with wire transfers. + +In case of a bulk payment to the same beneficiary, the payment initiation effective beneficiary is the beneficiary itself, so the payer can easily recognize the transaction. Otherwise, the payment initiation effective beneficiary is FlowPay. +Wire transfers to beneficiaries are sent with the same original payer, so beneficiaries can easily identify the payer. + +
+
In case of pagoPA payments
+ The bulk service is not natively supported with pagoPA payment, please contact us for more information and workarounds. +
+ +## Split Payment + +[![](https://mermaid.ink/img/pako:eNqFk82OmzAQx18FTQ97YSM7QAIcKoV-nNpqlY1aqeLi4CFrFezUmFXYKJc-T5-qT1Jjwi50D-Vg2TO_-c8ff5yhUBwhhcYwg-8FO2hW59KzHxcaCyOU9D5th0iGEktRCKa7jXd7-9a7Yx1i6lFC_vz6_YrJHLPhXPQqrHJ0k3rRM_xvzhVsd3dD1sXmITtxgd2m7_oitBv8fLOWrYHN3NI8mY0GwIeDFhxSo1v0oUZds34J574sB_OANeaQ2umeNXbmT-JfmRZsX2HTA-ehTw6lkuYjq0XVDXU3W7VXRt343iNqziTzvb6uumqNJffi6dqIro6nSfKoRW038p2qlB6AN5xjUBavmUxpjnpKhqugKMsJyexpPrJ-v7MfhylZLst4pjkh_y97NbDDk5lylAQkxAnX4M8WZYFf2no_lxz_qScvubzYkzky-V2pejwcrdrDA6Qlqxq7ao_85bI-RzVKZ7WVBlJKAycC6RlOkK6TRZgkNIlCQqPlivrQWSYOF5QkwZoQe11WQRhcfHhyXckijgIax2s7LmMS0dgHtHdV6c_Dc3GvxgfWGnXfyWL0OTj74Mirsctf7aUOvw?type=png)](https://mermaid.live/edit#pako:eNqFk82OmzAQx18FTQ97YSM7QAIcKoV-nNpqlY1aqeLi4CFrFezUmFXYKJc-T5-qT1Jjwi50D-Vg2TO_-c8ff5yhUBwhhcYwg-8FO2hW59KzHxcaCyOU9D5th0iGEktRCKa7jXd7-9a7Yx1i6lFC_vz6_YrJHLPhXPQqrHJ0k3rRM_xvzhVsd3dD1sXmITtxgd2m7_oitBv8fLOWrYHN3NI8mY0GwIeDFhxSo1v0oUZds34J574sB_OANeaQ2umeNXbmT-JfmRZsX2HTA-ehTw6lkuYjq0XVDXU3W7VXRt343iNqziTzvb6uumqNJffi6dqIro6nSfKoRW038p2qlB6AN5xjUBavmUxpjnpKhqugKMsJyexpPrJ-v7MfhylZLst4pjkh_y97NbDDk5lylAQkxAnX4M8WZYFf2no_lxz_qScvubzYkzky-V2pejwcrdrDA6Qlqxq7ao_85bI-RzVKZ7WVBlJKAycC6RlOkK6TRZgkNIlCQqPlivrQWSYOF5QkwZoQe11WQRhcfHhyXckijgIax2s7LmMS0dgHtHdV6c_Dc3GvxgfWGnXfyWL0OTj74Mirsctf7aUOvw) + +The above diagram shows how split works. +In case we want to pay beneficiary A 100€ and beneficiary B takes a 5€ commission on that payment, all that is needed is to add the beneficiary B to the additional Payees array. + +```json +{ + "amount": 105, + "currency": "EUR", + "remittanceInformation": "Order #12345", + "payee": { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A" + }, + "additionalPayees": [ + { + "iban": "IT79Q0300203280941591243327", + "name": "Beneficiary B", + "amount": 5, + "remittanceInformation": "Fee for #12345" + } + ] +} +``` + +The total amount of the payment request must be specified, and the payee will receive the remaining amount after the split. +The wire transfer allowed with the PIS on a split payment is addressed to the FlowPay technical account (TA). +When the TA receives the payment, it splits the amount among the beneficiaries, groups them by their IBANs and names, and sends the payments to them with wire transfers. + +The Wire transfer to the payee is sent with the original payer, the additional Payees receive the commission as a wire transfer with the payee as the payer. + +### Split with customer payee + +You can also direct a part of the payment to a registered customer by providing an object with the `customerId` and optional `remittanceInformation`. + +```json +{ + "amount": 130, + "currency": "EUR", + "remittanceInformation": "Invoice #7890", + "payee": { + "iban": "IT79Q0300203280941591243326", + "name": "Beneficiary A" + }, + "additionalPayees": [ + { + "customerId": "f1a56c5b-6e2a-4af9-8f77-4c9b1f0c2a22", + "amount": 25, + "remittanceInformation": "Commission for #7890" + } + ] +} +``` diff --git a/docs/payment_webhook.md b/docs/payment_webhook.md new file mode 100644 index 0000000..7c696db --- /dev/null +++ b/docs/payment_webhook.md @@ -0,0 +1,153 @@ +## Overview + +FlowPay delivers real-time, server-to-server notifications whenever a payment request changes state. Notifications are sent as HTTP POST requests to your configured `callbackUrl` and include the authoritative status you should use to advance your business flow. When in doubt, you can always query the latest state via `GET /payment-requests/{requestId}`. + +## Event Semantics + +There is a single, stable event for Request-to-Pay: `payment.status_change`. The current state is provided in the `status` field and reuses the API enum `PaymentRequestStatus` (`created`, `inProgress`, `authorized`, `rejected`, `onHold`, `locked`, `forwarded`, `refunded`, `deleted`). If the change originates from a specific checkout attempt, the event includes a `sessionId`. Multiple attempts may occur during a request’s lifecycle, and events may be delivered more than once; your handler must therefore be idempotent and tolerant of out-of-order delivery within the same `requestId`. + +## Request Details + +Deliveries use HTTPS POST with a JSON payload encoded in UTF‑8. The request carries headers that establish identity, integrity and replay protection. In particular, `X-FlowPay-Event-Id` uniquely identifies the delivery, `X-FlowPay-Event-Type` is always `payment.status_change`, `X-FlowPay-Timestamp` contains Unix epoch seconds, `X-FlowPay-Signature` holds a detached Ed25519 signature, `X-FlowPay-Key-Id` identifies the public key to use for verification, and `X-FlowPay-Retry-Count` indicates the attempt number starting at zero. Any HTTP 2xx response acknowledges the event. + +## Security and Verification + +Every delivery is signed by FlowPay with a platform private key. The signature covers the exact bytes of the HTTP body prefixed by the timestamp. Specifically, construct `payload = "{timestamp}.{rawBody}"` and verify the Base64URL‑encoded Ed25519 signature from `X-FlowPay-Signature` using the public key indicated by `X-FlowPay-Key-Id`. + +To validate a notification, read `X-FlowPay-Timestamp`, `X-FlowPay-Signature` and `X-FlowPay-Key-Id`; fetch the matching key from our well‑known JWKS; rebuild the payload from the raw HTTP body; and verify the signature using a constant‑time comparison. Reject messages older than five minutes to mitigate replay attacks. + +Well‑known JWKS is published at `/.well-known/jwks.json`: + +Implementation notes: we use Ed25519 (EdDSA on Curve25519). The `X-FlowPay-Signature` header is Base64URL without padding. In Ed25519 JWKs, the public key is carried in the `x` parameter (also Base64URL without padding). + +### Verification examples + +Below are compact examples of signature verification using JWKS and Ed25519. Always use the raw HTTP request body, not a re‑serialized JSON string. + +#### Java (Java 17+, Nimbus JOSE for JWK) + +```java +// Maven deps: +// +// com.nimbusds +// nimbus-jose-jwt +// 9.37.3 +// + +import com.nimbusds.jose.jwk.*; +import com.nimbusds.jose.util.Base64URL; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.security.Signature; + +public class FlowPayWebhookVerifier { + public static boolean verify( + String jwksUrl, + String kid, + String timestamp, + String signatureB64Url, + byte[] rawBody + ) throws Exception { + JWKSet jwkSet = JWKSet.load(new URL(jwksUrl)); + JWK jwk = jwkSet.getKeyByKeyId(kid); + if (!(jwk instanceof OctetKeyPair) || !"Ed25519".equals(((OctetKeyPair) jwk).getCurve().getName())) { + throw new IllegalArgumentException("Unsupported JWK or curve"); + } + PublicKey publicKey = ((OctetKeyPair) jwk).toPublicKey(); + + byte[] message = (timestamp + ".").getBytes(StandardCharsets.UTF_8); + byte[] payload = new byte[message.length + rawBody.length]; + System.arraycopy(message, 0, payload, 0, message.length); + System.arraycopy(rawBody, 0, payload, message.length, rawBody.length); + + byte[] signature = Base64URL.from(signatureB64Url).decode(); + + Signature verifier = Signature.getInstance("Ed25519"); + verifier.initVerify(publicKey); + verifier.update(payload); + return verifier.verify(signature); + } +} +``` + +#### Node.js (Node 18+, tweetnacl) + +```js +// npm i tweetnacl +import nacl from "tweetnacl"; + +function b64urlToUint8(b64url) { + const b64 = + b64url.replace(/-/g, "+").replace(/_/g, "/") + + "=".repeat((4 - (b64url.length % 4)) % 4); + return new Uint8Array(Buffer.from(b64, "base64")); +} + +export async function verifyFlowPay({ + jwksUrl, + kid, + timestamp, + signatureB64Url, + rawBodyBytes, +}) { + const res = await fetch(jwksUrl); + const { keys } = await res.json(); + const jwk = keys.find( + (k) => k.kid === kid && k.kty === "OKP" && k.crv === "Ed25519" + ); + if (!jwk) throw new Error("Key not found"); + + const publicKey = b64urlToUint8(jwk.x); + const signature = b64urlToUint8(signatureB64Url); + + const prefix = new TextEncoder().encode(`${timestamp}.`); + const msg = new Uint8Array(prefix.length + rawBodyBytes.length); + msg.set(prefix); + msg.set(rawBodyBytes, prefix.length); + + return nacl.sign.detached.verify(msg, signature, publicKey); +} +``` + +#### Swift (CryptoKit) + +```swift +import Foundation +import CryptoKit + +func base64URLDecode(_ s: String) -> Data? { + var str = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + let padLen = 4 - (str.count % 4) + if padLen < 4 { str += String(repeating: "=", count: padLen) } + return Data(base64Encoded: str) +} + +func verifyFlowPay(jwksJson: Data, kid: String, timestamp: String, signatureB64Url: String, rawBody: Data) throws -> Bool { + let jwks = try JSONSerialization.jsonObject(with: jwksJson) as! [String: Any] + guard let keys = jwks["keys"] as? [[String: Any]], + let key = keys.first(where: { ($0["kid"] as? String) == kid && ($0["kty"] as? String) == "OKP" && ($0["crv"] as? String) == "Ed25519" }), + let xStr = key["x"] as? String, + let x = base64URLDecode(xStr), + let sig = base64URLDecode(signatureB64Url) else { + return false + } + + let publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: x) + var message = Data((timestamp + ".").utf8) + message.append(rawBody) + return publicKey.isValidSignature(sig, for: message) +} +``` + +## Delivery and Reliability + +FlowPay uses at‑least‑once delivery with exponential backoff and jitter over a multi‑hour window. Retries occur on network errors, timeouts and non‑2xx responses; the current attempt is provided in `X-FlowPay-Retry-Count` starting from zero. Ordering within the same `requestId` is best‑effort and not strictly guaranteed. As soon as you persist the event safely, reply with any HTTP 2xx; redirects are not followed. + +## Idempotency and Responses + +Use `X-FlowPay-Event-Id` as the natural idempotency key. Keep a record of processed IDs for at least seven days and return 2xx for already‑applied deliveries. Respond as soon as the event is durably stored; keep the response body empty or return a small acknowledgment such as `{ "ok": true }`. + +## Operational Guidance + +Prefer a dedicated, unguessable `callbackUrl` and, where possible, additional controls such as IP allow‑listing or mutual TLS. Keep your handler lean—enqueue downstream work and acknowledge quickly. Log the event headers and `id` to correlate deliveries with your internal processing during troubleshooting. diff --git a/docs/request_to_pay.md b/docs/request_to_pay.md new file mode 100644 index 0000000..61799b3 --- /dev/null +++ b/docs/request_to_pay.md @@ -0,0 +1,383 @@ +# Functional Overview + +This chapter explains how each Request To Pay (RTP) product works functionally: actors, lifecycle, routing, and constraints. Use it to understand behavior before jumping into payloads. + +## Simple RTP + +Simple RTP is the shortest path from a payment request to a successful bank transfer. Your backend creates a request that describes what the user is paying (title, description, remittance), who receives the funds (payee), and optional URLs for redirect and server‑to‑server callbacks. You receive a hosted checkout link where the user completes Strong Customer Authentication (SCA) with their bank. From the user’s perspective it’s a clean, guided flow; from your side you track status via callbacks and finalise your order by reading the request status from the API. + +![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gIGF1dG9udW1iZXJcbiAgcGFydGljaXBhbnQgQXBwIGFzIFlvdXIgQmFja2VuZFxuICBwYXJ0aWNpcGFudCBBUEkgYXMgRmxvd1BheSBBUElcbiAgcGFydGljaXBhbnQgVXNlciBhcyBQYXllclxuICBwYXJ0aWNpcGFudCBCYW5rIGFzIEJhbmsgKFNDQSlcbiAgQXBwLT4-QVBJOiBQT1NUIC9wYXltZW50LXJlcXVlc3RzXG4gIEFQSS0tPj5BcHA6IDIwMSB7IHJlcXVlc3RJZCwgbGluayB9XG4gIEFwcC0tPj5Vc2VyOiBSZWRpcmVjdCB0byBsaW5rIChIb3N0ZWQgQ2hlY2tvdXQpXG4gIFVzZXItPj5CYW5rOiBTQ0EgYW5kIGNvbnNlbnRcbiAgQmFuay0tPj5BUEk6IEF1dGhvcmlzYXRpb24gb3V0Y29tZVxuICBBUEktLT4-VXNlcjogU3VjY2Vzcy9FcnJvciBzY3JlZW5cbiAgQVBJLS0-PkFwcDogUE9TVCBjYWxsYmFja1VybCAoc3RhdHVzKVxuICBBcHAtPj5BUEk6IEdFVCAvcGF5bWVudC1yZXF1ZXN0cy97aWR9IChjb25maXJtKVxuIn0=) + +State evolution: a Simple RTP typically flows created → inProgress when the user starts checkout, then authorized on a successful SCA with the provider. Settlement goes straight to the payee (fast transition to forwarded); it passes through the Technical Account (onHold) only when the ultimateDebtor differs from the debtor (payer). User cancellations, OTP expirations, or bank denials keep the request non-terminal until a successful attempt occurs or the request expires. Refunds, when issued, move the request to refunded for the affected amount. + +What it enables: one‑off payments, invoice settlement, donations, deposits, and any flow where a user authorises a bank transfer to a known beneficiary. + +How to enable specific flavors (copy‑paste ready): + +### Fixed amount (default) + +Set `amount` to the exact value you want to charge. + +State evolution: most flows are linear. From created the first user attempt moves to inProgress; on a positive outcome the session is authorized and the request proceeds to forwarded. Settlement goes straight to the payee; the Technical Account (onHold) path applies only when ultimateDebtor ≠ debtor. Errors or cancellations do not change the aggregate state to terminal; a later successful attempt completes the flow. Post-settlement reversals are reflected as refunded. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/"; API_KEY="sk_test_xxx" +curl -sS -X POST "$BASE_URL/payment-requests" -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{ + "payer": "+39 333 1234567", + "title": "Invoice #1001", + "description": "Settlement for invoice 1001", + "remittanceInformation": "INV-1001", + "amount": 49.90, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback" +}' +``` + +### Open amount (donations or pay‑what‑you‑want) + +Omit `amount` entirely to let the payer enter the amount at checkout. Keep `allowPartialPayments` at its default (`false`). Enforce your business rules (min/max) when processing callbacks or before fulfilling the order. + +State evolution: identical to fixed-amount, with the value chosen during checkout. Expect multiple inProgress attempts as the payer can re-enter and adjust the amount; only attempts that reach authorized affect the financial outcome. Settlement behaves like Simple RTP (direct to payee; Technical Account hop only if ultimateDebtor ≠ debtor); any reversal is reflected as refunded. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/"; API_KEY="sk_test_xxx" +curl -sS -X POST "$BASE_URL/payment-requests" -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{ + "payer": "+39 333 1234567", + "title": "Donate to ACME Foundation", + "description": "Thank you for your support", + "remittanceInformation": "DON-2024-09", + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/thank-you", + "callbackUrl": "https://merchant.example.com/api/payment/callback" +}' +``` + +Optional: allow the payer to edit the remittance at checkout by setting `allowRemittanceChange: true`. + +### Partial settlement (multiple payments until total is reached) + +Set `allowPartialPayments: true` and provide the target `amount` to be fully settled across one or more sessions. + +State evolution: each successful session independently reaches authorized and then forwarded for its portion. The request remains non-terminal until the cumulative authorized amount meets the target; it can cycle back to inProgress for additional attempts. Temporary onHold/locked can appear per session when funds pause on the Technical Account. Once the target is covered, the request is effectively complete in forwarded. Refunds operate per session without reopening the request. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/"; API_KEY="sk_test_xxx" +curl -sS -X POST "$BASE_URL/payment-requests" -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{ + "payer": "+39 333 1234567", + "title": "Installments for Order #A2001", + "description": "Pay in multiple steps", + "remittanceInformation": "A2001", + "amount": 300.00, + "currency": "EUR", + "allowPartialPayments": true, + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback" +}' +``` + +To compute the outstanding: list sessions with `/payment-requests/{requestId}/sessions`, sum `succeeded` amounts, and subtract from the target `amount`. + +### Scheduled execution (future date) + +Set `executionDate` (ISO 8601). The user authenticates now; execution occurs at/after the scheduled time (bank support dependent). + +State evolution: after inProgress and authorized, the request remains pending execution until the scheduled time. You will not see forwarded until the bank executes. If execution fails or is revoked, the provider emits a negative outcome and the request converges to rejected. Refunds are available only after execution. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/"; API_KEY="sk_test_xxx" +EXEC_AT="2025-01-10T10:00:00Z" # choose your date/time (UTC) +curl -sS -X POST "$BASE_URL/payment-requests" -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{ + "payer": "+39 333 1234567", + "title": "Scheduled payment", + "description": "Executes later", + "remittanceInformation": "SCH-10JAN", + "amount": 120.00, + "currency": "EUR", + "executionDate": "'"$EXEC_AT"'", + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback" +}' +``` + +## Bulk payments + +Bulk turns many payouts into a single user authorisation. You compute a total amount and provide the allocation list with additional payees. The user performs SCA once; FlowPay collects the total and then splits it to the designated beneficiaries. + +![](https://mermaid.ink/img/pako:eNpVUlFr2zAQ_iuHQtnDnBDHcWJrMHBSNgZrGCV9WbyHm3xORGXJSDKrm-a_T3ZoadGLTt_dd9-nuzMTpiLGWa3MP3FC6-HnfakBHg4Pjixg50_GSkcOvPGoABvTaf8HptOvsC8O30LZL-xhT-KkpQgJhRBjxkCyL4a8l-_WdC387eHHpth91tjQCxSHDWmqpZBoeyjepcPmA7R5D20_QNsADeDNDdyRbVBW8GbDQWX0Jw-ua1sTXEmtpCbQxpOLwBlwsukUegLpB47d4Z4a6T1qQfBI1DoIvo9SB0st9uEramOB0PVgSRgtpJLopdGjvB3nfKB-lToLWndXcUKhc7dUj62hlkrxSZ0PJ3LemkfiE0SMhFHG8kmSJFFttJ86-Uw8XrRPX1jEjlZWjHvbUcSaq9MwsvNAXzJ_ooZKxsO1oho75UtW6ksoa1H_NqZ5rQxTOJ4Yr1G5EHVtFdzfSjxabN5eLemK7HaYIONxPnIwfmZPIYrz2TzLsyzOksUyXqWLiPWMr1ezfJWu8zyfp3G-yJeXiD2PXeezNEuTdLWOF8kyTubZKmJUSW_s3XXnxtW7_AeTHNGA) + +What it enables: mass invoice runs, bill aggregation, marketplace or platform settlements with a single frictionless checkout. Repeated beneficiaries in your allocation are grouped automatically. Keep `allowPartialPayments` disabled; if the sum of additional payees is less than the total, the remainder goes to the primary payee. + +State evolution: authorization is single from the user’s perspective; settlement is mediated. After authorized, funds always land on the Technical Account, are processed immediately, and re-sent to beneficiaries. The request may appear onHold briefly while dispatch allocates amounts, then moves to forwarded. Subsequent reversals are reflected as refunded and are applied proportionally to the original allocation map. + +## Conditional payment + +When outcomes depend on a later verification (delivery, inspection, return window), use Locked payments. You specify a `lockedUntil` date; after a successful checkout the funds are held in FlowPay’s technical account. Before the date, you can decide to release to the payee or refund the user. If you take no action by expiry, the platform automatically refunds the payer. + +![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gIGF1dG9udW1iZXJcbiAgcGFydGljaXBhbnQgVXNlclxuICBwYXJ0aWNpcGFudCBBUEkgYXMgRmxvd1BheSBBUElcbiAgcGFydGljaXBhbnQgQXBwIGFzIFlvdXIgQmFja29mZmljZVxuICBVc2VyLT4-QVBJOiBQYXlzIGF0IGNoZWNrb3V0IChzdWNjZWVkZWQpXG4gIEFQSS0tPj5BcHA6IENhbGxiYWNrIChzdWNjZWVkZWQsIGxvY2tlZClcbiAgTm90ZSBvdmVyIEFQSTogRnVuZHMgaGVsZCB1bnRpbCBsb2NrZWRVbnRpbFxuICBBcHAtPj5BUEk6IFJlbGVhc2UgdG8gcGF5ZWUgT1IgY3JlYXRlIHJlZnVuZFxuICBBUEktLT4-VXNlcjogQXV0b-KAkXJlZnVuZCBpZiBubyBkZWNpc2lvbiBieSBsb2NrZWRVbnRpbFxuIn0=) + +What it enables: escrow‑like experiences, milestone‑based projects, dispute/return windows with automatic safety fallback. + +State evolution: after a successful checkout the session is authorized and the request enters locked until either a release instructs forwarding to the payee or the lock expires and a refund is performed. If no release occurs, expiry leads to refunded. Additional user attempts do not alter an existing lock for already authorized funds. + +## Split payment + +Split payment directs portions of a single checkout to different parties — for example, retaining a platform fee while paying a vendor. You define a primary payee and one or more additional payees with explicit amounts. The platform collects once and allocates the proceeds accordingly, preserving the original payer identity for beneficiaries. + +![](https://mermaid.ink/img/pako:eNp9UtFq2zAU_ZXLhcIGbha7sRMrMCjr-tQMk_WpcR4U6zoWkS1PlmmyJK-Dve4b9mX9ksl2Wygbe9LV1Tk6uufoiJkWhAxzpR-zghsLd8u0AkhW99pyBdelbiu7hsvLj6cllVxWgswJFq5YJUaW3Bwg4Qeidc_qcZ_3tZKZtMB78glu_dUtEXwYkOD_FxusBlTgUB3u4gIWZJyyAEPfWmmoAVsQVNoSWA0bgoZqbrglAbnRpTsRBIJyWUkrddX0aoyxjnFdZYU2XeeL2y3ltrCrZP18eE97O4hmijfNDeXw0oZcKuVAFXmNNXpHQ51ppQ3bKJ7t5n_xBq1_M-evw93xDSmwncajtMUwV1Pwmt68sncrxa9t-Y4L0Q_GVe9U8x6efv52Vri85s6j55Dg6ccvqIeIUjxBgh5ujRTIrGnJw3Iw1WV_7IRSdKaWlCJzpeBml2JanR2n5tWD1uULzeh2WyDLuWrcrq2F8_1G8q3h5WvXUPeAT12gyIIo7C9BdsQ9Mt-PR-NZPJv5s6tg4kdh4OEB2TQaxVE4jeN4HPpxEE_OHn7vZcejcBZehdHU94NJ5Naph-QM0GYxfN3-B5__ANaT8N0?theme=default) + +What it enables: marketplaces, app stores, franchise models, partner revenue sharing with full transparency in remittances. + +State evolution: similar to Bulk, the user authorizes once and funds always pass via the Technical Account, are processed immediately, and re-forwarded according to the split. The request may show a short onHold during dispatch, then advances to forwarded. Errors on outbound legs are handled by the dispatch engine and do not return the request to inProgress. Refunds mirror the original allocation proportions. + +## PagoPA payment + +For public‑service payments in the Italian PagoPA ecosystem, RTP integrates a dedicated branch. You provide the entity tax code and payment notice number, and an email to receive the receipt. The hosted checkout guides the user through the PagoPA‑specific steps, while your integration pattern (redirects, callbacks, receipt download) remains the same. + +![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gIGF1dG9udW1iZXJcbiAgcGFydGljaXBhbnQgQXBwIGFzIFlvdXIgQmFja2VuZFxuICBwYXJ0aWNpcGFudCBBUEkgYXMgRmxvd1BheSBBUEkgKFBhZ29QQSlcbiAgcGFydGljaXBhbnQgVXNlciBhcyBQYXllclxuICBBcHAtPj5BUEk6IFBPU1QgL3BheW1lbnQtcmVxdWVzdHMgKFBhZ29QQSBmaWVsZHMpXG4gIEFQSS0tPj5BcHA6IDIwMSB7IHJlcXVlc3RJZCwgbGluayB9XG4gIEFwcC0tPj5Vc2VyOiBSZWRpcmVjdCB0byBsaW5rXG4gIFVzZXItPj5BUEk6IFBhZ29QQSBmbG93IGF0IGNoZWNrb3V0XG4gIEFQSS0tPj5Vc2VyOiBSZWNlaXB0IHNjcmVlbiArIGVtYWlsXG4gIEFQSS0tPj5BcHA6IENhbGxiYWNrICsgUERGIGF2YWlsYWJsZSB2aWEgR0VUXG4ifQ==) + +What it enables: PagoPA notice payments with consistent checkout and server‑side integration semantics. Bulk isn’t natively supported with PagoPA; contact FlowPay for options. + +State evolution: follows PagoPA’s own flow as defined in the Simplified Flow document. The request goes created → inProgress during the PagoPA journey, then to authorized on a positive PagoPA outcome, and to forwarded once remittance to the Creditor Entity completes. Invalid notices or cancellations converge to rejected. No Technical Account hop is used unless explicitly mandated by PagoPA reconciliation policies. + +### How to enable PagoPA payment + +Provide the PagoPA‑specific fields (`pagopaEcFiscalCode`, `pagopaPaymentNotice`, `email`) alongside your standard request data. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/"; API_KEY="sk_test_xxx" +curl -sS -X POST "$BASE_URL/payment-requests" -H "Content-Type: application/json" -H "X-API-Key: $API_KEY" -d '{ + "payer": "+39 333 1234567", + "title": "PagoPA payment", + "description": "PagoPA notice 1234567890", + "remittanceInformation": "PPA-123", + "amount": 49.90, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback", + "pagopaEcFiscalCode": "01234567890", + "pagopaPaymentNotice": "123456789012345678", + "email": "payer@example.com" +}' +``` + +## Payment State Machine + +This section explains the lifecycle of a payment request as defined in the Simplified Flow document. The payment goes through well-defined states from creation to completion or termination. + +- created: request created and visible to the user. +- inProgress: user starts a session and authenticates with the provider. +- authorized: provider confirms a positive outcome for the operation. +- rejected: provider rejects/cancels the operation. +- onHold: funds are on the FlowPay technical account (TA) awaiting dispatch rules. +- locked: waiting for partner’s explicit unlock to dispatch funds. +- forwarded: funds dispatched to the beneficiaries. +- refunded: full refund executed for the entire transaction. +- deleted: request removed by the partner (only if not in progress and not paid). + +Final states are: deleted, forwarded, rejected, refunded. + +![](https://mermaid.ink/svg/pako:eNp1VNuO0zAQ_RXLT4DaqkmTtpsHpNVeBBIItCteoDy49iQdiO3Kdgrbav9n_4MfY-I07XYpeYqP58w5c-xkx6VVwAvugwhwjaJyQg836cIwer69-c6Gw7fsygHtqg7cL-LGe_PZ2cqB9wX7EsAEYGKzQcE8QWgNdJRjWWRdNmFlHW5BFYw2NqjAMWlNCU4LthaV0NTKnuXewQ-Q4YTpsMQmnBA76lEnUj-Zd7Ym4q01CplvWslgWQBpUO7VuppY_sHKn63OZQjgaaJlbaW0TLVCjgZVUMc3A67jdoTIvbXul3AKjmoa24SRCWRLMFCiROGwd_qP-LMG97Bnx9mYaILV1EkexjzU7gMqGxOJd6iX1nlLc-p1DX2gL1I5BkpCSCP1sQpjmroWEeuTZb3m80twDdS8bXBTo0Yjtu3Bn82JvfLAjDUMDZkia6_PWjodvq3_80SnLFcInjyshfeiqtD-5wz3fmIrusEd2I_5Eu3COkVPAyWYD3jlUPEiuAYGXLf3tF3yXUtY8LACDQte0KuCUjR1WPCFeSTaWpiv1uqe6WxTrXhRitrTqlmr40d3QB2QI3dlGxN4Mc3z2IQXO_6bF2mWjLJkkubZbDxL0nwy4A-8SNLZKJtPkvGM8It0nmSPA76NsuPRPE3G83x6kWR5Ok6n2YC3N-j-wcjeVGfjRmGw7uAC4vJj93OI_4jHv6hVXpY) + +# Examples + +This chapter collects hands‑on Request To Pay (RTP) examples and the technical explanation of how each use case works end‑to‑end. It covers actors, data fields, settlement paths, and status lifecycles so you can reason about behavior beyond the JSON payloads. + +## Simple RTP — example + +- Actors: payer (user initiating payment), payee (final beneficiary), optional debtor (if different from payer). +- Flow: your backend creates a payment request and receives a hosted `link`. The user completes SCA with their bank at checkout. FlowPay sends `callbackUrl` events and redirects the user to `redirectUrl` with `?status`. +- Security: authenticate API calls with `X-API-Key`. +- Settlement: funds are transferred from the payer’s bank to the configured payee IBAN. If no payee is provided, the partner’s default configuration is used. +- Status: the request follows the Payment State Machine (`created` → `inProgress` → `authorized` → `onHold`/`locked` → `forwarded` or terminal `rejected`/`refunded`/`deleted`). Inspect attempts with `/payment-requests/{requestId}/sessions` (session statuses: `succeeded`, `pending`, `expired`, `failed`, `cancelled`). + +Inline example (Sandbox): + +1. Create a payment request and get `requestId` + `link`. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/" +API_KEY="sk_test_xxx" # replace with yours + +curl -sS -X POST "$BASE_URL/payment-requests" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{ + "payer": "+39 333 1234567", + "title": "Order #A1001", + "description": "Payment for order A1001", + "remittanceInformation": "A1001", + "amount": 49.90, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/checkout/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback" + }' +``` + +2. Redirect the customer to the returned `link` to complete payment. + +Verify and download receipt: + +```bash +REQUEST_ID="..." + +curl -sS -X GET "$BASE_URL/payment-requests/$REQUEST_ID" -H "X-API-Key: $API_KEY" + +curl -sS -X GET \ + -H "X-API-Key: $API_KEY" \ + -H "Accept: application/pdf" \ + "$BASE_URL/payment-requests/$REQUEST_ID" \ + -o receipt.pdf +``` + +3. Treat the `callbackUrl` as the primary source of truth; use the APIs for on‑demand confirmation. + +## Split payment (Simplified Flow) — example + +- Goal: distribute a single user payment across multiple beneficiaries (e.g., platform fee + vendor payout). +- Data model: set total `amount`, primary `payee`, and an array of `additionalPayees` with their individual `amount`s. The remainder flows to the primary payee. +- Settlement: the payer authorises once; funds are collected and then allocated. When multiple beneficiaries are involved, settlement routes via FlowPay’s Technical Account and fans out to beneficiaries (grouped by IBAN+name), preserving the original payer in remittance. +- Constraints: Sum(additionalPayees.amount) ≤ `amount`. Do not combine `additionalPayees` with `allowPartialPayments=true`. + +### How to enable Split payment + +Set the total `amount`, define a primary `payee`, and list `additionalPayees` with their explicit `amount`s. Keep `allowPartialPayments` set to `false`. Ensure the sum of all `additionalPayees.amount` is less than or equal to the total; the remainder goes to the primary `payee`. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/" +API_KEY="sk_test_xxx" + +curl -sS -X POST "$BASE_URL/payment-requests" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{ + "payer": "+39 333 1234567", + "title": "Order #SP-1001", + "description": "Split payment", + "remittanceInformation": "SP-1001", + "amount": 100.00, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback", + "payee": { "name": "ACME Vendor", "iban": "IT60X0542811101000000123456" }, + "additionalPayees": [ + { "amount": 5.00, "name": "Platform Fee", "iban": "IT60X0542811101000000654321" } + ], + "allowPartialPayments": false + }' +``` + +Notes: + +- Use `remittanceInformation` to correlate the whole order across all beneficiaries. +- Refunds operate per successful session; maintain your allocation map for proportional refunds if needed. + +## Locked payment — example + +- Goal: escrow‑like behavior. Funds are held in FlowPay’s Technical Account until `lockedUntil`. +- Data model: add `lockedUntil` (ISO 8601) at creation time. +- Lifecycle: on `succeeded`, funds are held. Before expiry you may release to the payee (per enabled business rules) or refund the payer (`POST /refunds`). At expiry, automatic refund occurs if no action was taken. + +### How to enable Locked payment + +Add `lockedUntil` (ISO 8601). After a `succeeded` session, funds are held until that date. Before expiry, either instruct a release (per partner configuration) or create a refund via the API. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/" +API_KEY="sk_test_xxx" +LOCK_UNTIL=$(date -u -v+3d +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "+3 days" +"%Y-%m-%dT%H:%M:%SZ") + +curl -sS -X POST "$BASE_URL/payment-requests" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d "{\n \"payer\": { \"phone\": \"+39 333 1234567\" },\n \"title\": \"Escrow order #L-2001\",\n \"description\": \"Locked until verification\",\n \"remittanceInformation\": \"L-2001\",\n \"amount\": 59.00,\n \"currency\": \"EUR\",\n \"redirectUrl\": \"https://merchant.example.com/return\",\n \"callbackUrl\": \"https://merchant.example.com/api/payment/callback\",\n \"payee\": { \"name\": \"ACME Vendor\", \"iban\": \"IT60X0542811101000000123456\" },\n \"lockedUntil\": \"$LOCK_UNTIL\"\n }" +``` + +Refund example (per session): + +```bash +SESSION_ID="..." # from sessions or callback +curl -sS -X POST "$BASE_URL/refunds" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{ + "sessionId": "'"$SESSION_ID"'", + "amount": 59.00, + "reason": "other" + }' +``` + +## Bulk payments — example + +- Goal: let a user pay many targets with a single SCA (single checkout), reducing friction. +- Data model: same allocation primitives as split (`additionalPayees`) but used for larger lists; total `amount` equals the sum of all components intended for beneficiaries. +- Settlement: FlowPay’s Technical Account receives the total, then issues outgoing wires by allocation, grouping repeated entries (same IBAN+name). The original payer is preserved in remittance data to ease reconciliation. + +### How to enable Bulk payments + +Compute the total `amount` as the sum of all components, set the primary `payee`, and provide the allocation list in `additionalPayees`. Keep `allowPartialPayments` set to `false`. Repeated beneficiaries are allowed; FlowPay groups them by IBAN+name during settlement. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/" +API_KEY="sk_test_xxx" + +curl -sS -X POST "$BASE_URL/payment-requests" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{ + "payer": "+39 333 1234567", + "title": "Bulk order #B-9001", + "description": "Bulk payout to A,B,C", + "remittanceInformation": "B-9001", + "amount": 250.00, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback", + "payee": { "name": "Beneficiary A", "iban": "IT79Q0300203280941591243326" }, + "additionalPayees": [ + { "amount": 50, "name": "Beneficiary B", "iban": "IT79Q0300203280941591243327" }, + { "amount": 25, "name": "Beneficiary C", "iban": "IT79Q0300203280941591243328" }, + { "amount": 75, "name": "Beneficiary A", "iban": "IT79Q0300203280941591243326" } + ], + "allowPartialPayments": false + }' +``` + +Notes: + +- Combine with attachments for richer payer context (e.g., PDF invoice). +- Rely on `callbackUrl` + `GET /payment-requests/{id}` for authoritative status. + +## Attachments and PDF receipt — example + +- Two kinds of attachments: `attachments` (visible to payer at checkout) and `privateAttachments` (compliance‑only, not shown to payer). +- Upload options: multipart/form‑data (preferred) or JSON with base64 data. +- Receipt: `GET /payment-requests/{requestId}` with `Accept: application/pdf` returns a PDF with details and a QR for device handoff; if paid, it acts as a receipt. + +```bash +BASE_URL="https://api.sandbox.flowpay.it/v2/" +API_KEY="sk_test_xxx" + +# Upload (multipart) +FILE_ID=$(curl -sS -X POST "$BASE_URL/files" -H "X-API-Key: $API_KEY" -F "file=@./invoice.pdf;type=application/pdf" | jq -r .id) + +# Create RTP with attachments +curl -sS -X POST "$BASE_URL/payment-requests" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{ + "payer": "+39 333 1234567", + "title": "Order #A1002", + "description": "Payment with attachment", + "remittanceInformation": "A1002", + "amount": 129.00, + "currency": "EUR", + "redirectUrl": "https://merchant.example.com/checkout/return", + "callbackUrl": "https://merchant.example.com/api/payment/callback", + "attachments": ["'"$FILE_ID"'"] + }' + +# Download receipt (PDF) +REQUEST_ID="..." +curl -sS -X GET -H "X-API-Key: $API_KEY" -H "Accept: application/pdf" "$BASE_URL/payment-requests/$REQUEST_ID" -o receipt.pdf +``` diff --git a/docs/ultimate_debtor.md b/docs/ultimate_debtor.md new file mode 100644 index 0000000..691c72e --- /dev/null +++ b/docs/ultimate_debtor.md @@ -0,0 +1,16 @@ +Determines who appears as the originator on the account statement or on the receipt. + + + +In the case of a PagoPA payment, this value is used to generate the receipt. + +
+
⚠️Warning
+
If different from debtor, the payment is routed to a technical account and re-issued after the funds are received.
+
diff --git a/openapi.json b/openapi.json index 2edce01..d8b5460 100644 --- a/openapi.json +++ b/openapi.json @@ -1,2087 +1,239 @@ { "openapi": "3.1.0", "info": { - "title": "FlowPay API", - "version": "2.0.0-alpha.4", + "title": "Payment Gateway API", + "version": "2.0.0+S", "description": { - "$ref": "docs/general.md" + "$ref": "./docs/general.md" }, - "termsOfService": "https://developer.flowpay.it/tos", "license": { - "name": "FlowPay SRL", - "url": "https://developer.flowpay.it/tos" + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" }, - "x-logo": { - "url": "https://images.flowpay.it/logo", - "altText": "FlowPay" - }, - "contact": { - "name": "API Support", - "url": "https://developer.flowpay.it", - "email": "api-support@flowpay.it" - }, - "x-json-schema-faker": { - "locale": "it-IT", - "omitNulls": true, - "fillProperties": true, - "reuseProperties": true + "x-repository": { + "url": "https://github.com/FlowPay/client-openapi", + "contributionGuidelines": "We invite partners to suggest changes by forking the repository and submitting a pull request." } }, "servers": [ { "url": "https://api.flowpay.it/v2", - "description": "Production server (Not implementend)" + "description": "Production server" + }, + { + "url": "https://api.sandbox.flowpay.it/v2", + "description": "Shared sandbox server for testing and development. Data is reset periodically, so do not use for production data." + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + { + "name": "Banks", + "description": "Operations related to banks and supported payment methods." }, { - "url": "https://mock.flowpay.it/v2", - "description": "Mock server" + "name": "Charge", + "description": "Operations related to charges on tokenised methods." }, { - "url": "https://sandbox.{customerID}.flowpay.it/v2", - "description": "Customer-assigned sandbox server", - "variables": { - "customerID": { - "default": "00000000-00000000-00000000-00000000", - "description": "Unique customer identifier assigned after contract signature" - } + "name": "Customers", + "description": "Operations related to registered customers." + }, + { + "name": "Files", + "description": "Operations related to managing files attached to payments." + }, + { + "name": "Payment Method", + "description": "Operations related to managing tokenised payment methods." + }, + { + "name": "Refunds", + "description": "Operations related to payment refunds" + }, + { + "name": "Request To Pay", + "description": { + "$ref": "./docs/request_to_pay.md" } }, { - "url": "http://localhost:5002", - "description": "Debug" + "name": "AIS", + "description": "Account Information Service: consent, accounts, balances, transactions." } ], - "components": { - "securitySchemes": { - "oAuth2": { - "type": "oauth2", - "description": "OAuth2 flow", - "flows": { - "authorizationCode": { - "authorizationUrl": "/openid/authenticate", - "tokenUrl": "/oauth/token", - "refreshUrl": "/oauth/token", - "scopes": { - "accounts:read": "Allow to read accounts", - "accounts:write": "Allow to mediate accounts creation and open banking consent renewal", - "invoices:read": "Allow to read invoices", - "invoices:write": "Allow to create invoices and manage lifecycle", - "bills:read": "Allow to read bills", - "bills:write": "Allow to create bills and manage lifecycle", - "constructions:read": "Allow to read information about construction sites", - "constructions:write": "Allow to create construction sites and manage the lifecycle", - "openid": "Allow to read user profile", - "pagopa:read": "Allow to retrieve users' PagoPA payment notices", - "pagopa:write": "Allow to create PagoPA payment notices", - "transfers:read": "Allow to read transfers", - "transfers:write": "Allow to create transfers and manage lifecycle", - "wallet:`document_type`": "Allow to manage wallet for the specified use case" - } + "paths": { + "/banks": { + "get": { + "summary": "Get banks", + "description": "Retrieve the list of banks supported by FlowPay", + "operationId": "getBanks", + "security": [], + "tags": [ + "Banks" + ], + "parameters": [ + { + "name": "country", + "in": "query", + "description": "Country code (ISO 3166-1 alpha-2) to filter banks by country", + "required": false, + "schema": { + "type": "string" + }, + "x-faker": "address.countryCode", + "example": "IT" }, - "clientCredentials": { - "tokenUrl": "/oauth/token", - "scopes": { - "ade": "Allow to interact with Agenzia delle Entrate services", - "accounts:read": "Allow to read accounts", - "accounts:write": "Allow to mediate accounts creation and open banking consent renewal", - "invoices:read": "Allow to read invoices", - "invoices:write": "Allow to create invoices and manage lifecycle", - "bills:read": "Allow to read bills", - "bills:write": "Allow to create bills and manage lifecycle", - "constructions:read": "Allow to read information about construction sites", - "constructions:write": "Allow to create construction sites and manage the lifecycle", - "openid": "Allow to read user profile", - "pagopa:read": "Allow to retrieve users' PagoPA payment notices", - "pagopa:write": "Allow to create PagoPA payment notices", - "transfers:read": "Allow to read transfers", - "transfers:write": "Allow to create transfers and manage lifecycle", - "wallet:`document_type`": "Allow to manage wallet for the specified use case" + { + "name": "recurring", + "in": "query", + "description": "Filter banks that support at least one recurring payment type", + "required": false, + "schema": { + "type": "boolean" } - } - } - } - }, - "schemas": { - "Address": { - "type": "object", - "properties": { - "city": { - "type": "string", - "description": "Name of the city where the property is located." - }, - "street": { - "type": "string", - "description": "This field contains the name of the street where the property is located. It must include the name with a type (e.g., Avenue, Street, Road, etc.) but not the number or any other information." - }, - "number": { - "type": "string", - "description": "This field refers to the numeric or alphanumeric value assigned to a property on a street." }, - "unitNumber": { - "type": "string", - "description": "his field is used when a single property has multiple units, such as apartments or office suites. It differentiates one unit from another within the same property." - }, - "postalCode": { - "type": "string", - "description": "Also known as ZIP code (or CAP in Italy)" - }, - "province": { - "type": "string", - "description": "It refers to the name of the state or province where the property is located." - }, - "country": { - "type": "string", - "description": "The name of the country where the property is located." - } - }, - "required": [ - "city", - "street", - "postalCode", - "province", - "country" - ] - }, - "Bank": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the bank", - "example": "Intesa Sanpaolo", - "x-faker": "Company.companyName" - }, - "nationalID": { - "type": "string", - "description": "National identifier of the bank", - "example": "03999", - "x-faker": "finance.bic" - }, - "id": { - "type": "string", - "description": "Unique identifier of the bank", - "example": "intesa_sanpaolo" - }, - "countries": { - "type": "array", - "items": { - "type": "string", - "description": "Countries where the bank operates in ISO 3166-1 alpha-2 format", - "example": "IT", - "x-faker": "address.countryCode" + { + "name": "recurringFrequency", + "in": "query", + "description": "Filter banks that support the specified recurring payment frequency", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "monthly", + "everyTwoMonths", + "quarterly", + "semiAnnual", + "daily", + "everyFourMonths", + "weekly", + "everyTwoWeeks", + "annual" + ] + } } }, - "logo": { - "type": "string", - "description": "URL of the bank logo", - "example": "https://flowpayproduction.blob.core.windows.net/img/84036981-d004-40c4-a872-c81dd37e4acc.png", - "x-faker": "image.imageUrl" - }, - "features": { - "type": "object", - "description": "Features supported by the bank", - "properties": { - "futurePayments": { - "type": "boolean", - "description": "True if the bank supports future payments", - "example": true - }, - "sct": { - "type": "array", - "description": "List of SCT payment types supported by the bank", - "items": { - "type": "string", - "enum": [ - "standard", - "instant" - ], - "description": "SCT payment type" - } - }, - "recurringPayments": { - "type": "array", - "description": "List of recurring payment types supported by the bank", - "items": { - "type": "string", - "enum": [ - "monthly", - "everyTwoMonths", - "quarterly", - "semiAnnual", - "daily", - "everyFourMonths", - "weekly", - "everyTwoWeeks", - "annual" - ], - "description": "Recurring payment frequency" + { + "name": "sctType", + "in": "query", + "description": "Filter banks that support the specified SCT type", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "standard", + "instant" + ] + } + } + } + ], + "responses": { + "200": { + "description": "List of banks", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bank" + } } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" } } - }, - "BankAccount": { - "type": "object", - "properties": { - "owner": { - "type": "string", - "format": "uuid", - "description": "Identifier of the owner of the bank account" - }, - "iban": { - "type": "string", - "description": "International Bank Account Number", - "x-faker": "finance.iban", - "example": "IT60X0542811101000000123456" - }, - "currencies": { - "type": "array", - "items": { - "type": "string", - "description": "Currencies supported by the bank account", - "x-faker": "finance.currencyCode" + } + }, + "/banks/{id}": { + "get": { + "summary": "Get bank details", + "description": "Retrieve details of a specific bank", + "operationId": "getBank", + "security": [], + "tags": [ + "Banks" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Bank identifier", + "required": true, + "schema": { + "type": "string" }, - "example": [ - "EUR", - "CHF", - "GBP" - ] - }, - "bankName": { - "type": "string", - "description": "Identifier of the bank", - "example": "intesa_sanpaolo" + "example": "cassa_di_risparmio_di_orvieto" + } + ], + "responses": { + "200": { + "description": "Bank details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Bank" + } + } + } }, - "owners": { - "type": "array", - "description": "List of owners of the bank account", - "items": { - "type": "string", - "description": "Name of the owner of the bank account", - "x-faker": "person.fullName" - }, - "example": [ - "Maria Fumagalli", - "Luigi Verdi" - ] + "400": { + "$ref": "#/components/responses/BadRequest" }, - "consentStatus": { - "$ref": "#/components/schemas/ConsentStatusEnum" + "404": { + "$ref": "#/components/responses/NotFound" } } - }, - "Bulk": { - "type": "object", - "properties": { - "fingerprint": { - "$ref": "#/components/schemas/Fingerprint" - }, - "amount": { - "type": "number", - "format": "double", - "example": 48223.07, - "description": "Total amount of the bulk" - }, - "allowWireTranfersAggregation": { - "type": "boolean", - "example": true, - "description": "Indicate if the wire transfers to the same beneficiary can be aggregated" - }, - "documents": { - "type": "object", - "description": "Dictionary of documents related to the bulk. Keys are the document type, values are the fingerprints of the documents.", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Fingerprint" - } - } - } - } - }, - "Chain": { - "description": "Chain of documents", - "type": "object", - "properties": { - "fingerprint": { - "$ref": "#/components/schemas/Fingerprint" - }, - "targetType": { - "$ref": "#/components/schemas/DocumentKindEnum" - }, - "targetFingerprint": { - "$ref": "#/components/schemas/Fingerprint" - }, - "triggerType": { - "$ref": "#/components/schemas/DocumentKindEnum" - }, - "triggerFingerprint": { - "$ref": "#/components/schemas/Fingerprint" - } - } - }, - "Checkout": { - "type": "object", - "properties": { - "code": { - "type": "string", - "pattern": "^[a-zA-Z\\d]{8}$", - "description": "Unique identifier of the checkout session. This code is secret and should never be shared with anyone who is not the payer." - }, - "fingerprint": { - "$ref": "#/components/schemas/Fingerprint" - }, - "type": { - "$ref": "#/components/schemas/DocumentKindEnum" - }, - "creditor": { - "type": "string", - "format": "uuid", - "description": "Identifier of the creditor", - "x-faker": "datatype.uuid" - }, - "debtor": { - "type": "string", - "format": "uuid", - "description": "Identifier of the debtor", - "x-faker": "datatype.uuid" - }, - "collectionMethods": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CollectionMethodEnum" - }, - "description": "Collection methods enabled for the payment.
Note that collection methods are the intersection of the collection methods techologies enabled for the creditor and the collection methods allowed by the client for checkout." - }, - "payments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Payment" - }, - "description": "List of payments related to the checkout" - } - }, - "required": [ - "code", - "fingerprint", - "type", - "creditor", - "debtor", - "collectionMethods" - ] - }, - "ConsentStatusEnum": { - "type": "string", - "enum": [ - "not_granted", - "granted", - "revoked", - "expired", - "all" - ], - "default": "all", - "description": "Status of the account information service (AIS) consent granted by account owner to FlowPay.
- `not_granted`: owner has never granted a consent
- `granted`: consent granted by account owner
- `revoked`: consent revoked by account owner
- `expired`: consent expired
- `all`: all types of consent" - }, - "CollectionMethodEnum": { - "type": "string", - "enum": [ - "sct", - "sct-inst", - "sdd", - "card", - "custom/