# Pagination Some Ripple Payments API operations paginate their responses to make the result set easier to handle. For example, if you request a list of objects that is potentially too large to handle efficiently, the operation returns the first batch of results along with a marker that you use to access the next batch of results. In Ripple Payments, this marker is a URL parameter, `page`, indicating the starting point. For example, if you call `GET /v4/payments`, the default response includes five payments (the default `size`) on `page=0` (the default `page`). If there are more payments to fetch (indicated by `last=false` in the API response), fetch the next set of payments by using the same endpoint with `page=1`. We recommend that you fetch all the results on all pages before processing them. If you paginate over a moving list, you may miss some results. ## Pagination flowchart ![Pagination flowchart](/assets/pagination-flowchart.1836b7a1da52ed43134968fc9391359788cd8978a381218efe5681b4e4ddbe7f.a81c1264.svg) ## Step-by-step implementation 1. [Create the request](#1-create-the-request) 2. [Read the response](#2-read-the-response) 3. [Process data](#3-process-data) hr ### 1. Create the request Choose the data you need to retrieve (payments, etc.). The HTTP method for all the data types is `GET`. For example: ```json GET {ripplepayments_base_url}/v4/payments Authorization: Bearer eyJhbGciO...l0ZSIwIm ``` To use pagination, add the `page` parameter to the request. Its default value is 0. Your request becomes: ```json GET {ripplepayments_base_url}/v4/payments?page=X Authorization: Bearer eyJhbGciO...l0ZSIwIm ``` #### Logging checklist | Action | Completed | | --- | --- | | The full request | | #### Pseudocode | Action | Completed | | --- | --- | | Choose the data to retrieve (payments, ...) | | | Include the `page` parameter in the request | | hr ### 2. Read the response Before processing the data you received with `page=0`, read and analyze the response. This is a sample response from `GET /v4/payments?page=0&size=3`: details summary Click to show complete response ```json { "first": true, "last": false, "number": 0, "numberOfElements": 3, "size": 3, "totalElements": 30, "totalPages": 10, "sort": [ { "direction": "ASC", "property": "string", "ignoreCase": true, "nullHandling": "NULLS_FIRST", "ascending": true, "descending": false } ], "content": [ { "payment_id": "d485f100-2af7-4e48-9ab1-3c7e28775691", "contract_hash": "ccb23bd87f13cc13b9d616a9723f76e112aeac8628b2082e0f8bf3b8c670b103", "payment_state": "COMPLETED", "modified_at": "2019-10-01T18:25:47.347Z", "contract": { ... }, "ripplenet_info": [ ... ], "execution_condition": "PrefixSha256Condition{subtypes=[ED25519-SHA-256], type=PREFIX-SHA-256, fingerprint=sfGGHCrkyaMsLQNB62w_4zarlPChHKm47JkXVQbs1z0, cost=132360}", "crypto_transaction_id": "4e05da26-7872-4a1f-b9b7-db7604757c37", "validator": "rn.us.ca.san_francisco", "payment_type": "DIRECT", "execution_results": [ ... ], "liquidation_execution_results": [ ... ], "liquidation_details": { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "status": "string", "failure_reason": "string", "failure_count": 0 }, "push_forward_execution_results": [ ... ], "accepted_at": "2019-10-01T18:25:47.347Z", "executed_at": "2019-10-01T18:25:47.347Z", "completed_at": "2019-10-01T18:25:47.347Z", "internal_info": { "connector_role": "RECEIVING", "labels": [ { "label": "string" } ], "internal_id": "string" }, "user_info": [ ... ] }, { "payment_id": "d1234567-2abc-4e48-9ab1-3c7e2877abcd", ... }, { "payment_id": "dabcdefs-2123-4e48-9ab1-3c7e2877efgh", ... } ] } ``` The first fields of the response are important: ```json { "first": true, "last": false, "number": 0, "numberOfElements": 3, "size": 3, "totalElements": 30, "totalPages": 10, } ... ``` The above response indicates that 30 payments (`totalElements`) are available on 10 pages (`totalPages`). `last` is a boolean indicating whether this is the last page of the response data. Implement logic in your middleware to keep retrieving your data until `last=true`, and increment `page` by 1 for each call. #### Logging checklist | Action | Completed | | --- | --- | | The full request | | hr #### Pseudocode | Action | Completed | | --- | --- | | Receive the successful response | | | Read `last` boolean field | | | If `last=false`, use pagination | | | Else, process data | | Example Java code for pagination of `GET /v4/payments` response: ```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; } ``` hr ### 3. Process data After retrieving all the data on all pages, process the list containing your data in the `content` response field. #### Pseudocode For example for payments. | Action | Completed | | --- | --- | | If total number of payments > 0, then send them for processing | | | else do nothing, wait for next polling | | ```java if (payments != null) { if (payments.getNumberOfElements() > 0) { paymentsProcessor.processPayments(payments, hasSubStates); } else { LOGGER.info("No payments in {} states retrieved", getPaymentsParams.getStates()); } } ``` #### Logging checklist | Action | Completed | | --- | --- | | Total number of data to process | | | Last `page` used | | hr ### HTTP errors | HTTP Status Code | Error Message | Description | | --- | --- | --- | | `400` | `invalid_request` | Invalid Request parameters | | `401` | Error 401 Unauthorized | Bad credentials, wrong authentication method OR the token can be expired | | `403` | Forbidden | No handler found for GET | | `500` | Server not available | Service is temporarily unavailable. | ### Handlers | Name | Code | Action | | --- | --- | --- | | Unauthorized | `401` | Credentials could have been altered by an administrator or renew the token. | | Server not available | `500` | 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; ```