# Polling Polling is a way to retrieve the list of payments that both senders and receivers need to process. ## Overview flow chart A typical workflow for polling for payments that are ready for processing, is as follows: ![Polling flowchart](/assets/rri-polling-payments-flowchart.19e8beb5ad80d5d6a41243661809113506a232d312af0071e988196dc58e6740.a81c1264.svg) ## Step-by-step implementation 1. [Payment states](#1-payment-states) 2. [Create the request](#2-create-the-request) 3. [Read the response](#3-read-the-response) 4. [Send payments for processing](#4-send-payments-for-processing) 5. [Create the pollers independently from each other](#5-create-the-pollers-independently-from-each-other) ### 1. Payment states You can use the same technique to poll for payments in any state or substate. Senders poll for payments in the following states: * `EXECUTED` (with sub-states) * `FAILED` * `COMPLETED` Receivers poll for payments in the following states: * `ACCEPTED` * `EXECUTED` * `FAILED` For more information on Ripple Payments states, see [Payment states](/products/payments-direct/introduction/concepts/payment-states) . A *substate* is a label that provides communication between the `SENDING` and `RECEIVING` RippleNet instances. A substate label is only present when the payment is in the `EXECUTED` state. Note: For more information on Ripple Payments states, see [Payment states](/products/payments-direct/introduction/concepts/payment-states) . In the following section, we'll focus on building the poller for payments in the `ACCEPTED` state. ### 2. Create the request Use the `GET /v4/payments` operation to poll the payments by **states**. **Request example:** ```http GET /v4/payments?page=0&size=100&states=ACCEPTED&sort_field=CREATED_AT&sort_direction=DESC Authorization: Bearer eyJhbGci...zI1NiOlsi ``` The `GET /v4/payments` operation accepts many parameters. Find all the parameters and their descriptions in the API reference documentation. It's important to use them as they allow you to filter the payments you poll. In the request example considered here, these are parameter values we'll use: - `page=0` - `size=100` - `states=ACCEPTED` - `sort_field=CREATED_AT` - `sort_direction=DESC` #### page The `page` parameter controls the *pagination* process, which we explain in [3. Read the response](#3-read-the-response) below. The default `page` value is 0. #### size The `size` parameter indicates the number of payments on one page. The default `size` value is 10 and its maximum is 100. As a best practice, set the `size` value to 100 in your middleware. #### states The `states` parameter lets you specify a list of payment states that you want to poll. In our example, to poll for `ACCEPTED` payments, we set `states=ACCEPTED`. Best practice: Set `states` to one state only, as each poller needs to be independent from other pollers. See also [5. Create the pollers independently from each other](#5-create-the-pollers-independently-from-each-other). **Note:** The `state` parameter is deprecated, use `states`. #### sort_field and sort_direction The `sort_field` and `sort_direction` parameters control the sorting of the returned list of payments. The default value for `sort_field` is `MODIFIED_AT`. The default value for `sort_direction` is `DESC`. Best practice: Use these two fields with the following values `sort_field=CREATED_AT` and `sort_direction=DESC`. This causes the oldest payments to appear first in the response list as you need to process the oldest payments before they expire. ### 3. Read the response Before you send the payments for processing (which is a different action than polling), read and analyze the response. Here is a sample response from `GET /v4/payments`: details summary Click to view the full JSON response ... pre ```json { "first": true, "last": true, "number": "0", "numberOfElements": "1", "size": "100", "totalElements": "1", "totalPages": "1", "sort": [ { "direction": "DESC", "property": "CREATED_AT", "ignoreCase": false, "nullHandling": "NATIVE", "ascending": false, "descending": true } ], "content": [ { "payment_id": "f293947c-9067-47d7-a69e-6658070ed482", "contract_hash": "c6db748bf84b7ed38b0fdc3bd6bdcca8d072af27b473ad0df47aa450b6c70396", "payment_state": "ACCEPTED", "modified_at": "2021-03-03T04:40:58.629Z", "contract": { "sender_end_to_end_id": "test", "created_at": "2021-03-03T04:40:57.812Z", "expires_at": "2021-03-26T08:14:14.839Z", "quote": { "quote_id": "642fd286-d3a7-4a75-a14b-86ca9a504922", "created_at": "2021-03-03T04:40:54.839Z", "expires_at": "2021-03-26T08:14:14.839Z", "type": "SENDER_AMOUNT", "price_guarantee": "FIRM", "sender_address": "trans_usd_sender@test.cloud.blueprint1", "receiver_address": "trans_gbp_receiver@test.cloud.blueprint2", "amount": "10.000000000", "currency_code": "USD", "currency_code_filter": null, "service_type": null, "quote_elements": [ { "quote_element_id": "23b21542-4b54-4c3c-a2dd-f551fe026194", "quote_element_type": "TRANSFER", "quote_element_order": "1", "sender_address": "trans_usd_sender@test.cloud.blueprint1", "receiver_address": "conct_usd_sender2@test.cloud.blueprint1", "sending_amount": "10.000000000", "receiving_amount": "10.000000000", "sending_fee": "0.000000000", "receiving_fee": "0.000000000", "sending_currency_code": null, "receiving_currency_code": null, "fx_rate": null, "transfer_currency_code": "USD" }, { "quote_element_id": "3e31fa45-731a-4a35-8cd9-0100b19f7b66", "quote_element_type": "TRANSFER", "quote_element_order": "2", "sender_address": "conct_usd_sender@test.cloud.blueprint2", "receiver_address": "alias_usd_receiver@test.cloud.blueprint2", "sending_amount": "10.000000000", "receiving_amount": "10.000000000", "sending_fee": "0.000000000", "receiving_fee": "0.000000000", "sending_currency_code": null, "receiving_currency_code": null, "fx_rate": null, "transfer_currency_code": "USD" }, { "quote_element_id": "66b80732-66a7-4a77-ab2d-3853d6849e98", "quote_element_type": "EXCHANGE", "quote_element_order": "3", "sender_address": "alias_usd_receiver@test.cloud.blueprint2", "receiver_address": "conct_gbp_receiver@test.cloud.blueprint2", "sending_amount": "10.000000000", "receiving_amount": "12.000000000", "sending_fee": "0.000000000", "receiving_fee": "0.000000000", "sending_currency_code": "USD", "receiving_currency_code": "GBP", "fx_rate": { "rate": "1.200000000", "base_currency_code": "USD", "counter_currency_code": "GBP", "type": "buy" }, "transfer_currency_code": null }, { "quote_element_id": "051885bf-a4be-4661-800e-40e05ac1f349", "quote_element_type": "TRANSFER", "quote_element_order": "4", "sender_address": "conct_gbp_receiver@test.cloud.blueprint2", "receiver_address": "trans_gbp_receiver@test.cloud.blueprint2", "sending_amount": "12.000000000", "receiving_amount": "12.000000000", "sending_fee": "0.000000000", "receiving_fee": "0.000000000", "sending_currency_code": null, "receiving_currency_code": null, "fx_rate": null, "transfer_currency_code": "GBP" } ], "liquidity_warning": null, "payment_method": null, "payment_method_fields": null, "payout_method_info": null }, "fee_info": null }, "ripplenet_info": [], "execution_condition": "PrefixSha256Condition{subtypes=[ED25519-SHA-256], type=PREFIX-SHA-256, fingerprint=VK9hSv6zUK9czMZCiZ9n_BWZrDGEbRQumi641B76VFQ, cost=132360}", "crypto_transaction_id": null, "validator": "test.cloud.blueprint1", "payment_type": "REGULAR", "returns_payment_with_id": null, "returned_by_payment_with_id": null, "execution_results": [], "internal_info": { "connector_role": "RECEIVING", "labels": [], "internal_id": null }, "user_info": [ { "node_address": "test.cloud.blueprint1", "accepted": [ { "json": { "end_to_end_id": "20201018041040", "DbtrAcct": {}, "DbtrAgt": {}, "DbtrAgtAcct": {}, "InstForCdtrAgt": {}, "Purp": {}, "RgltryRptg": {}, "RltdRmtInf": {}, "UltmtCdtr": {}, "Cdtr": { "Nm": "Peppa Potts", "PstlAdr": { "AdrLine": "Somewhere in Malaysia", "Ctry": "MY", "PstCd": "12345" }, "Id": { "PrvtId": { "Othr": { "Id": "AA8423234", "SchmeNm": { "Cd": "ID" } } } } }, "CdtrAcct": { "Id": { "Othr": { "Id": "0123456789" } }, "Tp": { "Cd": "Saving" } }, "CdtrAgt": { "FinInstnId": { "BICFI": "ABCDEF", "Nm": "Bank A" } }, "Dbtr": { "Nm": "Robert Downey", "PstlAdr": { "AdrLine": "Tampines St 81", "Ctry": "SG", "PstCd": "543210" }, "Id": { "PrvtId": { "Othr": { "Id": "AA8463912", "SchmeNm": { "Cd": "ID" } } } } }, "RmtInf": { "Ustrd": "20201018041010" }, "PmtTpInf": { "CtgyPurp": { "Prtry": "IR02836" } } }, "created_at": "2021-03-03T04:40:58.449Z", "subState": "" } ], "locked": [], "lock_declined": [], "retry_accept": [], "retry_settlement": [], "settlement": [], "settlement_declined": [], "failed": [], "executed": [], "completed": [], "forwarded": [], "returned": [] } ] } ] } ``` The first fields are important as they tell you if you need to use *pagination* to read the full results, as described next. #### Pagination In the example below, the values differ from the example above to illustrate the concept of *pagination*: ```json { "first": true, "last": false, "number": "0", "numberOfElements": "100", "size": "100", "totalElements": "300", "totalPages": "3", ... } ``` This tells us the RippleNet API is returning three pages (numbered from 0 to 2) with 100 payments per page, so we need to use pagination to retrieve all the payments. If `totalElements` is higher than `size`, then `totalPages` will be higher than 1. The `last` field, if `true`, indicates that the page is the last one. This is the field you can use to programmatically handle the pagination. Implement logic in your middleware to keep calling `GET /v4/payments`, incrementing `page` by 1 for each call, until `last=true`. ![Pagination flowchart](/assets/rri-polling-payments-pagination-flowchart.51905a052b9a590d86580a2ac48bdca538e507b2a1959ab69efc43c6e819e529.a81c1264.svg) details summary Click to view example Java implementation of pagination ... pre ```java Payments payments = paymentsServices.getPaymentsFromAllPages(getPaymentsParams); ... @Override public Payments getPaymentsFromAllPages(GetPaymentsParams getPaymentsParams) throws RippleNetProblemException, RnAuthException { int page = 0; getPaymentsParams.setPage(page); // We retrieve the payments on page 0 first Payments payments = getPayments(getPaymentsParams); int numberOfElements = payments.getNumberOfElements(); // If the page is not the last one while (!payments.isLast()) { // We increase the page by 1 page++; getPaymentsParams.setPage(page); // We retrieve the payments on the incremented page Payments paymentsTemporary = getPayments(getPaymentsParams); // And for each payment on the new page for (Payment payment : paymentsTemporary.getContent()) { // we add it to the global list of Payments payments.addContentItem(payment); // we also update the total number of payments/elements in the payments object numberOfElements++; payments.setNumberOfElements(numberOfElements); } // Finally, at the end of the while loop, we update the first/last booleans // If page 1, for example, is the last, then the while loop will exit payments.setLast(paymentsTemporary.isLast()); payments.setFirst(paymentsTemporary.isFirst()); } LOGGER.info("Total amount of payments retrieved: {}", numberOfElements); LOGGER.info("Last page where payments were retrieved is page {}", getPaymentsParams.getPage()); // Return the list of Payments return payments; } ``` ### 4. Send payments for processing After retrieving all the payments with your poller, send the payments for processing. For example, for a payment in `ACCEPTED` state: - Send the payment for compliance review. - Validate that its `user_info` has been received. - Lock, reject, or fail the payment. details summary Click to view example Java implementation of payment processing ... pre ```java if (payments != null) { if (payments.getNumberOfElements() > 0) { paymentsProcessor.processPayments(payments, hasSubStates); } else { LOGGER.info("No payments in {} states retrieved", getPaymentsParams.getStates()); } } ``` ### 5. Create the pollers independently from each other Best practice: To make it easier to control the execution of the pollers independently, a suggested poller-based workflow starts with a separate poller for each state, where each poller is in a separate file. To avoid confusion (and for clarity), name your pollers based on the state the payment is in when the poller processes the payment. For example, for the poller for `EXECUTED` payments, name it *ExecutedPaymentsPoller*. The following actions need to be possible on one poller without affecting the others: - Enable or disable a poller. - Define an *initial delay* after startup. - Define a *fixed delay* between two executions of the same poller. Create your pollers as follows: 1. Create a separate poller for each state by duplicating the `ACCEPTED` poller, and adapting the parameters (`states`, etc.) for each poller. Place each poller in its own file. For example: - *AcceptedPaymentsPoller* - *ExecutedPaymentsPoller* - *CompletedPaymentsPoller* - *FailedPaymentsPoller* 2. For each poller, implement a property to enable or disable the poller. For example, (in Java): ```java @ConditionalOnProperty(value = "ripplenet.poller.accepted.enabled", matchIfMissing = false, havingValue = "true") ``` 3. Implement a property to define an *initial delay* after startup (including a default value as well): ```java @Scheduled(..., initialDelayString = "${ripplenet.poller.accepted.initial.delay:30000}") ``` 4. Implement a property to define a *fixed delay* between two executions of the same poller (including a default value as well). The `fixedDelay` property makes sure that there is a delay of *n* millisecond between the time a poller finishes executing, and the start time of the next execution of the same poller: ```java @Scheduled(fixedDelayString = "${ripplenet.poller.accepted.fixed.delay:30000}", ...) ``` 5. Finally, include all these properties in `application.properties` to easily update their values without recompiling your code: ```properties ripplenet.poller.accepted.enabled=false ripplenet.poller.accepted.fixed.delay=30000 ripplenet.poller.accepted.initial.delay=30000 ``` #### Design considerations You can adjust the *fixed delay* between two executions of the same poller, depending on the number of payments you have to process. Here are some aspects to consider: | Design type | Pros | Cons | | --- | --- | --- | | Fixed delay < 30 seconds | Process payments quickly | May overload RippleNet and your internal systemsIf high frequency is required to process a lot of payments, consider using MQ | | 60 seconds > Fixed delay >= 30 seconds - Recommended | Good "payments processing/system load" ratioEnough time to process all the payments | | | Fixed delay > 60 seconds | Enough time to process all the payments | Processes payments more slowly | ## Error handling Here are some considerations with respect to handling HTTP errors: ### HTTP errors ```text GET /v4/payments ``` | HTTP Status Code | Error Message | Description | | --- | --- | --- | | `400` | `invalid_request` | Invalid Request parameters | | `401` | Error 401 Unauthorized | Bad credentials, wrong authentication method, OR the token may be expired | | `403` | Forbidden | No handler found for GET | | `500` | Server not available | Service is temporarily unavailable. | ### Handlers | Name | Code | Action | | --- | --- | --- | | Unauthorized | 401 | **Payments will stop, throw an alert**. Credentials could have been altered by an administrator or renew the token. | | Server not available | 500 | **Payments will stop**. Retry three times every two seconds. If it fails, raise an alert. | #### Handlers sample code To handle retries with a 500 HTTP status code, the RippleNet Reference Implementation uses the Spring [@Retryable](https://www.baeldung.com/spring-retry#using-spring-retry) annotation. ```java @Retryable(value = {RippleNetProblemTemporaryException.class}, maxAttempts = 3, backoff = @Backoff(maxDelay = 2000, random = true)) Payments getPayments(GetPaymentsParams getPaymentsParams) throws RippleNetProblemException, RnAuthException; ```