Skip to content

Latest commit

 

History

History
462 lines (357 loc) · 27.3 KB

sep-0031.md

File metadata and controls

462 lines (357 loc) · 27.3 KB

Preamble

SEP: 0031
Title: Direct Payments
Author: SDF
Status: Active
Created: 2020-04-07
Updated: 2020-12-15
Version 1.2.0

Simple Summary

This SEP defines a protocol for enabling direct fiat to fiat payments between two financial accounts that exist outside of the Stellar network. The payments are facilitated by two anchors.

Abstract

This proposal facilitates the ability for anchors to build a rail between two regions, allowing end users to send fiat from one bank account directly into another end user's bank account. In this flow, neither user needs to deal with the Stellar network as the two anchors take care of everything for them.

Example

Alice in Nigeria wants to send money to Bob in Europe. Alice signs up with NigeriaPay to make this payment to send money directly into Bob’s bank account. Bob doesn’t need to do anything, or know anything about this payment, besides letting Alice know what his bank account information is. Alice only needs to deal with her anchor (NigeriaPay).

NigeriaPay will utilize its European rail, enabled with EuroPay Anchor service, to move the money to EuroPay in order to deposit it into Bob’s bank account.

Prerequisites

  • An anchor must define the location of their DIRECT_PAYMENT_SERVER in their stellar.toml. Anchors will find each other's servers by pulling this TOML file from their home domains.
  • Anchors will create bi-lateral agreements to interoperate with each other. This differs from other protocols in that there is no concept of 'discoverability'. Anchors should keep a mapping of their partnerships in different regions to their home domains.
  • Each anchor registers a Stellar key with each counterparty anchor they interact with in order to identify themselves via SEP-10 Web Authentication.

Authentication

Anchors should support SEP-10 web authentication to ensure the counterparty they're interoperating with is actually who they say they are. Clients must submit the JWT obtained via the SEP-10 authentication flow to all API endpoints except /info.

The JWT should be included as a request header:

Authorization: Bearer <JWT>

Any API request that fails to meet proper authentication should return a 403 Forbidden response.

HTTPS Only

This protocol involves the transfer of value, and so HTTPS is required for all endpoints for security. Anchors should refuse to interact with any insecure HTTP endpoints.

Content Type

All endpoints accept in requests the following Content-Types:

  • application/json

All endpoints respond with content type:

  • application/json

API Endpoints

Implementation Notes

Entities Involved

  • Sending Client: The end user who is initiating a payment via the sending anchor.
  • Sending Anchor: The business offering outbound payment services. Takes fiat in from the sending client, and has a business relationship with the receiving anchor.
  • Receiving Anchor: The business offering inbound payment processing. Deposits fiat in the receiving client's bank account, and has a business relationship with the sending anchor.
  • Receiving Client: The owner of the destination bank account.

Setting up rails

  1. To create a rail, find a counterparty who implements this SEP in the region you wish to provide access to and agrees to do business with you.
  2. Trade public keys with each other in order to identify and securely interoperate with each other.
  3. Keep a mapping of region to home_domain, using home_domain as your initial entry point to interoperating.

Sender Flow

  1. The Sending Client (user) iniates a direct payment to the Receiving Client.
  2. The Sending Anchor identifies the Receiving Anchor it will use for the payment based on the receipient's location and desired currency.
  3. The Sending Anchor makes a request to the Receiving Anchor's /info endpoint to collect asset information and the transaction.fields describing the pieces of information required by the Receiving Anchor.
  4. If the Receiving Anchor has a sender_sep12_type and/or a receiver_sep12_type attribute in the /info endpoint response,
    1. The Sending Anchor must collect the Receiving Anchor's KYC_SERVER URI from the Receiving Anchor's SEP-1 stellar.toml file.
    2. The Sending Anchor must make a GET KYC_SERVER/customer request for each _sep12_type value listed in the /info response. Each /customer response contains SEP-9 fields required by the Receiving Anchor for that user.
  5. The Sending Anchor collects all required information from the Sending Client. This includes the custom fields listed in the Receiving Anchor's /info response as well as the KYC fields described in the /customer responses for both the Sending and Recieving Clients. How the Sending Anchor collects this information from the Sending Client is out of the scope of this document, but the Receving Client should not be required to take any action in order for the Sending Client to make the payment to the Receiving Anchor.
  6. The Sending Anchor makes PUT KYC_SERVER/customer requests containing the SEP-9 values listed in the GET KYC_SERVER/customer responses to register the Clients with the Receiving Anchor.
  7. On successful registration (202 HTTP status), the Sending Anchor makes a POST DIRECT_PAYMENT_SERVER/transactions request to create a pending_sender transaction with the Receiving Anchor.
    1. Note that this request contains the ids returned by the PUT /customer requests, the transaction.fields values collected from the Sending Client, as well as the transaction amount and asset information.
  8. Once the transaction is included in the GET DIRECT_PAYMENT_SERVER/transactions response and the transaction's status is pending_sender, the Sending Anchor submits the path payment transaction to Stellar.
    1. The destination account of the transaction must be the stellar_account_id returned in the POST /transactions response.
    2. The memo of the transaction must match the stellar_memo and stellar_memo_type returned in the POST /transactions response.
  9. The Sending Anchor polls the Receiving Anchor's GET DIRECT_PAYMENT_SERVER/transactions endpoint until the transaction's status changes to completed, error, pending_customer_info_update, or pending_transaction_info_update.
  10. If completed, the job is done and the Sending Anchor should notify the Sending Client.
  11. If error, the Receiving Anchor should be contacted to resolve the situation.
  12. If pending_transaction_info_update, the transaction.fields values collected from the Sending Client were invalid and must be corrected by the Sending Client
    1. This requires the Sending Anchor to detect which fields were invalid from the required_info_updates object on the /transactions record.
    2. Then the Sending Anchor must reach out the Sending Client again, collect valid values, and make a PATCH DIRECT_PAYMENT_SERVER/transactions request to the Receiving Anchor
  13. If pending_customer_info_update, the SEP-9 KYC values collected were invalid and must be corrected by the Sending Client
    1. This requires the Sending Anchor to make a GET KYC_SERVER/customer request for each customer associated with the transaction to determine which fields need to be updated
    2. The Sending Anchor then reaches out to the Sending Client again, collects valid values for the invalid fields, and makes a PUT KYC_SERVER/customer request to the Receiving Anchor
  14. After providing the Receiving Anchor with updated values, the status should ultimately change to completed

Receiver Flow

  1. The Sending Anchor makes a request to the Receiving Anchor's DIRECT_PAYMENT_SERVER/info endpoint.
  2. The Sending Anchor makes a GET KYC_SERVER/customer request for each _sep12_type attribute included in the response.
  3. The Sending Anchor makes a PUT KYC_SERVER/customer request for each _sep12_type attribute.
    1. The Receiving Anchor must validate the KYC data provided and reject the request with useful error messages if invalid. This is critical for limiting the number of times a transaction ends up in a pending_customer_info_update status.
  4. The Sending Anchor makes a POST DIRECT_PAYMENT_SERVER/transactions request.
    1. The Receiving Anchor must validate the asset, amount, transaction fields, and customers.
    2. The Recieving Anchor must create a transaction record in their database and expose it via /transactions.
    3. Transactions should initially be pending_sender. If any preprocessing is required before receiving a payment, mark the transaction as pending_receiving until ready.
  5. The Receiving Anchor then waits to receive the payment identified by the stellar_memo returned in the POST /transactions response.
  6. Once the Stellar payment has been received and matched with the internal transaction record, the Receiving Anchor must attempt to transfer an equivalent amount of the asset (minus fees) off-chain to the Receiving Client using the KYC and rails data collected by the Sending Anchor
  7. If the off-chain payment succeeds, the transaction's status should be updated to completed
  8. If the off-chain payment cannot be received by the Recieving Client almost immediately, the transaction's status should be updated to pending_external until received. Then, completed.
  9. If the off-chain payment fails, the Recieving Anchor must determine why, which is outside the scope of this document. Once determined, the Reciving Anchor must either correct it themselves (internal error) or receive updated values from the Sending Anchor for the fields that were discovered to be invalid.
    1. If the invalid values were described in /info's transaction.fields object, the transaction's status should be updated to pending_transaction_info_update and required_info_updates should contain an object describing the errors.
    2. If the invalid values were described in GET /customer responses, the transaction's status should be updated to pending_customer_info_update and the invalid field names should be returned in the next GET /customer?id= request for each Client.
    3. The Sending Client will detect transaction's status and invalid fields, collect the info from the Sending Client, and make requests to the Recieving Anchor containing the updated information.
    4. Once the passed information is validated, the Receiving Anchor should update the transaction's status to pending_receiver and retry the off-chain transfer. This loop of attempting the transfer and waiting for updated information should continue until the transfer is successful.

Info

Request

GET DIRECT_PAYMENT_SERVER/info

Allows an anchor to communicate basic info about what currencies their DIRECT_PAYMENT_SERVER supports receiving from partner anchors.

Request parameters:

Name Type Description
lang string (optional) Defaults to en. Language code specified using ISO 639-1. description fields in the response should be in this language.

Response

The response should be a JSON object like:

{
  "receive":{
    "USD":{
      "enabled":true,
      "fee_fixed":5,
      "fee_percent":1,
      "min_amount":0.1,
      "max_amount":1000,
      "sep12": {
        "sender": {
          "types": {
            "sep31-sender": {
              "description": "U.S. citizens limited to sending payments of less than $10,000 in value"
            },
            "sep31-large-sender": {
              "description": "U.S. citizens that do not have sending limits"
            },
            "sep31-foreign-sender": {
              "description": "non-U.S. citizens sending payments of less than $10,000 in value"
            }
          }
        },
        "receiver": {
          "types": {
            "sep31-receiver": {
              "description": "U.S. citizens receiving USD"
            },
            "sep31-foreign-receiver": {
              "description": "non-U.S. citizens receiving USD"
            }
          }
        }
      },
      "fields":{
        "transaction":{
          "receiver_routing_number":{
            "description": "routing number of the destination bank account"
          },
          "receiver_account_number":{
            "description": "bank account number of the destination"
          },
          "type":{
            "description": "type of deposit to make",
            "choices":[
              "SEPA",
              "SWIFT"
            ]
          }
        }
      }
    }
  }
}

The JSON object contains an entry for each asset that the anchor supports for receiving and completing a direct payment.

For each asset available for receiving, response contains:

  • sep12: an object containing sender and receiver keys.
    • sender: an object containing a types key if KYC information is required for the sending client, empty otherwise.
      • types: (optional) an object containing the accepted values for the type parameter in SEP-12 requests for sending clients. Each key should map to an object value with a human-readable description. This field can be omitted if no KYC is necessary for the sending client.
    • receiver: an object containing a types key if KYC information is required for the receiving client, empty otherwise.
      • types: (optional) an object containing the accepted values for the type parameter in SEP-12 requests for receiving clients. Each key should map to an object value with a human-readable description. This field can be omitted if no KYC is necessary for the receiving client.
  • min_amount: (optional) minimum amount. No limit if not specified.
  • max_amount: (optional) maximum amount. No limit if not specified.
  • fee_fixed: (optional) fixed (flat) fee for deposit. In units of the received asset. Leave blank if there is no fee or the fee schedule is complex.
  • fee_percent: (optional) percentage fee for deposit. In percentage points. Leave blank if there is no fee or the fee schedule is complex.
  • sender_sep12_type: (deprecated, optional) value of the type parameter the sending anchor should use for a SEP-12 GET /customer request. This field can be omitted if no KYC is necessary. Use the values in sep12.sender.types instead if it is present.
  • receiver_sep12_type: (deprecated, optional) value of the type parameter the sending anchor should use for a SEP-12 GET /customer request. This field can be omitted if no KYC is necessary. Use the values in sep12.receiver.types instead if it is present.
  • fields: as explained below.

The fields object allows an anchor to describe fields that must be passed into POST /transactions. Only fields related to the transaction should be described in the fields object. In the example above, the receiving anchor requires the account and routing number of the receiving client's bank account.

Each fields sub-object contains a key for each field name and an object with the following fields as the value:

  • description: (required) description of field to show to user.
  • choices: (optional) list of possible values for the field.
  • optional: (optional) false if not specified.

If KYC is required for a sending or receiving client in some cases but not others, it is recommended to provide values in the respective types object for all cases and return an empty fields object from SEP-12 GET /customer for the cases where no KYC is necessary.

Transactions

Request

POST DIRECT_PAYMENT_SERVER/transactions
Content-Type: application/json

{
  "amount": 100,
  "asset_code": "USD",
  "asset_issuer": "GDRHDSTZ4PK6VI3WL224XBJFEB6CUXQESTQPXYIB3KGITRLL7XVE4NWV",
  "sender_id": "d2bd1412-e2f6-4047-ad70-a1a2f133b25c",
  "receiver_id": "137938d4-43a7-4252-a452-842adcee474c",
  "fields": {
    "transaction": {
      "receiver_routing_number": "442928834",
      "receiver_account_number": "0029483242",
      "type": "SEPA"
    }
  }
}

This post requests attempts to initiate a payment through this anchor. It should provide the amount and all the required fields (specified in the /info endpoint). The values for sender_id and receiver_id are from the receiving anchor's SEP-12 PUT /customer responses.

If the request describes a valid transaction that this anchor can fulfill, we return a success response with details on what to send. If the request is not valid, or we need more info, we can return with an error response and expect the sending anchor to try again with updated values.

Request Parameters
Name Type Description
amount number Amount of payment in destination currency.
asset_code string Code of the asset the sending anchor intends to send. This must match one of the entries listed in the receiving anchor's /info endpoint.
asset_issuer string (optional) The issuer of the Stellar asset the sending anchor intends to send. If not specified, the asset sent must be issued by the receiving anchor.
sender_id string (optional) The ID included in the SEP-12 PUT /customer response for the sending client. If sender_sep12_type was included in the /info response, this field is required.
receiver_id string (optional) The ID included in the SEP-12 PUT /customer response for the receiving client. If receiver_sep12_type was included in the /info response, this field is required.
fields object A key-pair object containing the values requested by the receiving anchor in their /info endpoint containing a single "transaction" object.
lang string (optional) Defaults to en. Language code specified using ISO 639-1. Any human-readable error codes or field descriptions will be returned in this language.

Responses

Success (201 Created)

This is the successful case where a receiving anchor confirms that they can fulfill this payment as described. The response body should be a JSON object with the following values

Name Type Description
id string Persistent identifier to check the status of this payment
stellar_account_id string Stellar account to send payment to
stellar_memo_type string Type of memo to attach to the Stellar payment (text, hash, or id)
stellar_memo string The memo to attach to the Stellar payment
Customer Info Needed (400 Bad Request)

In the case where the sending anchor didn't provide all the KYC information requested in GET /customer, or where the receiver requires additional KYC information after learning of the transaction amount, the request should fail with a 400 status code and the following body in JSON format. The sender should then retry both the GET /customer request to collect the additional fields and the PUT /customer request including all fields described in the GET /customer response.

Name Type Description
error string customer_info_needed
type string (optional) A string for the type URL argument the sending anchor should use when making the GET /customer request. The value should be included in the sender or receiver types response object from /info.
Transaction Info Needed (400 Bad Request)

In the case where the sending anchor didn't provide all the information requested in /info, or if the transaction requires extra information, the request should fail with a 400 status code and the following body in JSON format. The sender should then retry the entire request including all the previously sent fields plus the fields described in the response.

Name Type Description
error string transaction_info_needed
fields object A key-value pair of missing fields in the same format as fields described in /info.
Error (400 Bad Request)

In the case where the transaction just cannot be completed, return an error response with a JSON object containing an error key describing the error in human-readable format in the language indicated in the request.

{
  'error': "The amount was above the maximum limit"
}

{
  'error': "That bank account is restricted via AML laws"
}

Transaction

The transaction endpoint enables senders to query/validate a specific transaction at a receiving anchor.

GET DIRECT_PAYMENT_SERVER/transactions/:id

Request parameters:

Name Type Description
id string The id of the transaction.

On success the endpoint should return 200 OK HTTP status code and a JSON object with the following fields:

Name Type Description
transaction object The transaction that was requested by the client.

The transaction object should be of the following schema.

Name Type Description
id string Unique, anchor-generated id for the deposit/withdrawal.
status string Processing status of deposit/withdrawal.
status_eta number (optional) Estimated number of seconds until a status change is expected
amount_in string (optional) Amount received by anchor at start of transaction as a string with up to 7 decimals. Excludes any fees charged before the anchor received the funds.
amount_out string (optional) Amount sent by anchor to user at end of transaction as a string with up to 7 decimals.
amount_fee string (optional) Amount of fee charged by anchor.
stellar_account_id string Stellar account to send payment to.
stellar_memo_type string Type of memo to attach to the Stellar payment: text, hash, or id.
stellar_memo string The memo to attach to the Stellar payment.
started_at UTC ISO 8601 string (optional) Start date and time of transaction.
completed_at UTC ISO 8601 string (optional) Completion date and time of transaction.
stellar_transaction_id string (optional) transaction_id on Stellar network of the transfer that initiated the payment.
external_transaction_id string (optional) ID of transaction on external network that completes the payment into the receivers account.
refunded boolean (optional) Should be true if the transaction was refunded. Not including this field means the transaction was not refunded.
required_info_message string (optional) A human-readable message indicating any errors that require updated information from the sender.
required_info_updates object (optional) A set of fields that require update from the sender, in the same format as described in /info. This field is only relevant when status is pending_transaction_info_update.

status should be one of:

  • pending_sender -- awaiting payment to be initiated by sending anchor.
  • pending_stellar -- transaction has been submitted to Stellar network, but is not yet confirmed.
  • pending_customer_info_update -- certain pieces of information need to be updated by the sending anchor. See pending customer info update section
  • pending_transaction_info_update -- certain pieces of information need to be updated by the sending anchor. See pending transaction info update section
  • pending_receiver -- payment is being processed by the receiving anchor
  • pending_external -- payment has been submitted to external network, but is not yet confirmed.
  • completed -- deposit/withdrawal fully completed.
  • error -- catch-all for any error not enumerated above.

Example response:

{
  "transaction": {
      "id": "82fhs729f63dh0v4",
      "status": "pending_external",
      "status_eta": 3600,
      "external_transaction_id": "ABCDEFG1234567890",
      "amount_in": "18.34",
      "amount_out": "18.24",
      "amount_fee": "0.1",
      "started_at": "2017-03-20T17:05:32Z"
    }
}
{
  "transaction": {
      "id": "82fhs729f63dh0v4",
      "status": "pending_info_update",
      "status_eta": 3600,
      "external_transaction_id": "ABCDEFG1234567890",
      "amount_in": "18.34",
      "amount_out": "18.24",
      "amount_fee": "0.1",
      "started_at": "2017-03-20T17:05:32Z",
      "required_info_message": "The bank reported an incorrect account number for the receiver, please ensure the account matches legal documents",
      "required_info_updates": {
         "transaction": {
            "receiver_account_number": {
               "description": "The receiver's bank account number"
            }
         }
      }
    }
}

If the transaction cannot be found, the endpoint should return a 404 NOT FOUND result.

Pending customer info update

In certain cases the receiver might need to request updated information. For example, if the bank tells the anchor that the provided receiver's name is incorrect or missing a middle initial. Since this information was sent via SEP-12, the transaction should go into the pending_customer_info_update state until the sender makes another PUT /customer request to update. The sending anchor can check which fields need to be updated by making a GET /customer request including the id or account & memo parameters. The receiving anchor should respond with a NEEDS_INFO status and last_name included in the fields described.

Pending transaction info update

Another possibility is that the bank tells the receiving anchor that the provided account or routing number is incorrect. Since this information was sent via a POST /transactions request, the transaction should go into the pending_transaction_info_update state until the sender makes a request to the endpoint outlined below.

Update

This endpoint should only be used when the receiver requests more info via the pending_transaction_info_update status. The required_info_updates transaction field should contain the fields required for the update. If the sender tries to update at a time when no info is requested the receiver should fail with an error response.

PATCH DIRECT_PAYMENT_SERVER/transactions/:id

Request parameters:

Name Type Description
id string The id of the transaction.
fields object A key-pair object containing the values requested to be updated by the receiving anchor in the same format as /transactions.

Example

PATCH DIRECT_PAYMENT_SERVER/transactions/82fhs729f63dh0v4

{
   "fields": {
      "transaction": {
         "receiver_bank_account": "12345678901234",
         "receiver_routing_number": "021000021"
      }
   }
}

Success 200 OK

If the information was successfully updated, respond with a 200 status code, and return the transaction JSON in the body. The transaction should return to pending_receiver, though it is possible that the information could still need to be updated again.

Not Found 404

If the transaction specified by "id" does not exist, return a 404 response.

Error 400

If the information was malformed, or if the sender tried to update data that isn't updatable, return a 400 with an object containing an error message.

{
   "error": "Supplied fields do not allow updates, please only try to updates the fields requested"
}