This guide explains how to integrate Ratepay Pay in 3 across the buyer journey.
It covers:
- how to calculate Pay in 3 plan values
- how to display Pay in 3 on product, cart, and checkout pages
- how to connect the checkout flow to Ratepay APIs
- when to use client-side calculation and when to use
CALCULATION_REQUEST
This page focuses on technical integration. For mandatory legal disclosure requirements, see Pay in 3 legal requirements.
Recommended approach: calculate Pay in 3 plan details client-side for display purposes, and use the standard Ratepay checkout API flow for payment processing.
| Topic | Recommended approach | Alternative approach |
|---|---|---|
| Product page plan display | Client-side calculation | CALCULATION_REQUEST |
| Cart page plan display | Client-side calculation | CALCULATION_REQUEST |
| Checkout plan display | Client-side calculation | CALCULATION_REQUEST |
| Payment processing | PAYMENT_INIT + PAYMENT_REQUEST | Same |
| Final order confirmation | PAYMENT_CONFIRM, if required for your setup | Same |
The examples below illustrate the main integration contexts and UI states in the buyer journey.
| Screen | Stage | Purpose | Notes |
|---|---|---|---|
![]() | Product page | Show Pay in 3 teaser near the product price | Optional from a legal perspective, but recommended for transparency and conversion |
![]() | Cart page | Recalculate and show Pay in 3 based on the current cart total | Update whenever quantity or cart contents change |
![]() | Product / Cart | Show detailed plan values in a modal or detail view | Typically opened through a “Learn more” interaction |
![]() | Checkout | Show Pay in 3 as the selected payment method in the default checkout state | Example of the main payment method view before expanding additional instalment details |
![]() | Checkout | Show the expanded checkout state with additional instalment breakdown details | Example of an expanded buyer-facing checkout view |
| Label in UI | German label | Description |
|---|---|---|
| Per month | Monatliche Rate | Monthly instalment amount |
| Duration | Laufzeit | Number of instalments and duration |
| Effective Interest | Effektiver Jahreszins | Annual percentage rate of charge (APR) |
| Interest | Zinsbetrag | Total interest amount |
| Total | Gesamtbetrag | Total amount payable |
| Monthly instalment amount | Monatliche Rate | Regular instalment amount shown to the buyer |
| Final instalment amount | Letzte Rate | Final instalment amount, which may differ slightly because of rounding |
For Pay in 3, these values are typically:
- Duration =
3 months - Effective Interest =
0% - Interest =
0 - Total = basket amount
- Monthly instalment amount = regular instalment amount calculated from the basket amount
- Final instalment amount = last instalment amount, which may differ slightly because of rounding
Before showing Pay in 3 to the buyer, you need to obtain the plan values for the current amount.
This is the recommended approach for display purposes.
Pay in 3 is a fixed product:
3monthly instalments0%interest0fees
Because most parameters are static, the display values can be calculated client-side from the basket amount. The final instalment absorbs any rounding remainder.
The calculation function can also accept an optional shipmentDate (YYYY-MM-DD). If your shop has a reliable delivery estimate, you can use it to generate a more accurate estimated instalment schedule. If omitted, the function uses today’s date and returns a general estimate.
Use the following helper function to calculate Pay in 3 plan values directly in your frontend or backend application code.
function calculatePayIn3(amount, shipmentDate) {
// --- Configuration: Pay in 3 is a 0% interest, 0-fee product ---
const NUMBER_OF_INSTALLMENTS = 3;
const DURATION_UNIT = "months";
const INTEREST_RATE = 0;
const INTEREST_AMOUNT = 0;
const FEES_AMOUNT = 0;
const HAS_INTEREST = false;
const HAS_FEES = false;
// --- Calculate installment amounts ---
// Split evenly; any rounding remainder goes to the final installment
const regularAmount = Math.round((amount / NUMBER_OF_INSTALLMENTS) * 100) / 100;
const finalAmount = Math.round((amount - regularAmount * (NUMBER_OF_INSTALLMENTS - 1)) * 100) / 100;
// --- Determine the first due date ---
// First instalment is due on the 2nd of the month following shipment,
// provided there are at least 28 days between shipment and that date.
// Otherwise, it is pushed to the 2nd of the subsequent month.
function getFirstDueDate(shipment) {
let next2nd = new Date(Date.UTC(shipment.getUTCFullYear(), shipment.getUTCMonth() + 1, 2));
const shipmentUTC = Date.UTC(shipment.getUTCFullYear(), shipment.getUTCMonth(), shipment.getUTCDate());
const next2ndUTC = Date.UTC(next2nd.getUTCFullYear(), next2nd.getUTCMonth(), next2nd.getUTCDate());
const diffDays = Math.floor((next2ndUTC - shipmentUTC) / (1000 * 60 * 60 * 24));
if (diffDays >= 28) return next2nd;
return new Date(Date.UTC(shipment.getUTCFullYear(), shipment.getUTCMonth() + 2, 2));
}
// Parse shipmentDate string (YYYY-MM-DD) or default to today, using UTC for consistency
let shipment;
if (shipmentDate) {
const parts = shipmentDate.split("-");
shipment = new Date(Date.UTC(+parts[0], +parts[1] - 1, +parts[2]));
} else {
const today = new Date();
shipment = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()));
}
const firstDue = getFirstDueDate(shipment);
// --- Build the installment schedule ---
const schedule = [];
let remaining = amount;
for (let i = 1; i <= NUMBER_OF_INSTALLMENTS; i++) {
const dueDate = new Date(Date.UTC(firstDue.getUTCFullYear(), firstDue.getUTCMonth() + (i - 1), 2));
const payment = i < NUMBER_OF_INSTALLMENTS ? regularAmount : finalAmount;
remaining = Math.round((remaining - payment) * 100) / 100;
schedule.push({
installmentNumber: i,
estimatedDueDate:
dueDate.getUTCFullYear() + "-" +
String(dueDate.getUTCMonth() + 1).padStart(2, "0") + "-" +
String(dueDate.getUTCDate()).padStart(2, "0"),
paymentAmount: payment,
principalAmount: payment,
interestAmount: INTEREST_AMOUNT,
feesAmount: FEES_AMOUNT,
remainingPrincipalBalanceAmount: remaining,
});
}
// --- Return the complete plan object ---
return {
planMetadata: { hasInterest: HAS_INTEREST, hasFees: HAS_FEES },
repaymentPlan: {
numberOfInstallments: NUMBER_OF_INSTALLMENTS,
durationOfInstallments: DURATION_UNIT,
regularInstallmentAmount: regularAmount,
finalInstallmentAmount: finalAmount,
},
creditTerms: {
totalPayableAmount: amount,
totalCostOfCreditAmount: INTEREST_AMOUNT + FEES_AMOUNT,
totalInterestAmount: INTEREST_AMOUNT,
totalFeesAmount: FEES_AMOUNT,
nominalInterestRatePercentage: INTEREST_RATE,
annualPercentageRate: INTEREST_RATE,
},
fees: { serviceFeeAmount: FEES_AMOUNT },
installmentSchedule: schedule,
};
}Example function usage
const productAmount = 200;
const plan = calculatePayIn3(productAmount);| JSON field | UI label | Meaning |
|---|---|---|
repaymentPlan.regularInstallmentAmount | Per month | Regular monthly instalment amount |
repaymentPlan.numberOfInstallments + repaymentPlan.durationOfInstallments | Duration | Number and duration of instalments |
creditTerms.annualPercentageRate | Effective Interest | Effective interest rate shown to the buyer |
creditTerms.totalInterestAmount | Interest | Total interest amount |
creditTerms.totalPayableAmount | Total | Total amount payable |
repaymentPlan.finalInstallmentAmount | Final instalment | Final instalment amount, if it differs due to rounding |
installmentSchedule[].estimatedDueDate | — | Estimated due date for each instalment, if a shipment date is used to generate the schedule |
Use the Ratepay XML API CALCULATION_REQUEST operation if you prefer to retrieve plan values from the server instead of calculating them client-side.
The response contains the core instalment values needed for display and checkout mapping.
See also: CALCULATION_REQUEST
For Pay in 3, both approaches can support the same buyer-facing disclosure values. Choose the option that best fits your architecture.
<?xml version="1.0"?>
<request version="1.0" xmlns="urn://www.ratepay.com/payment/1_0">
<head>
<system-id>MyTestsystem</system-id>
<operation subtype="calculation-by-time">CALCULATION_REQUEST</operation>
<credential>
<profile-id>INTEGRATION_TE_DACH</profile-id>
<securitycode>4c0a11923fa3433fb168f9c7176429e9</securitycode>
</credential>
</head>
<content>
<installment-calculation>
<amount>200.00</amount>
<calculation-time>
<month>3</month>
</calculation-time>
</installment-calculation>
</content>
</request>For Pay in 3, you can ignore <service-charge> and <monthly-debit-interest> because these values are always 0.
| XML field | UI label | Meaning |
|---|---|---|
<rate> | Per month | Regular monthly instalment amount |
<number-of-rates> | Duration | Number of monthly instalments |
<annual-percentage-rate> | Effective Interest | Effective interest rate shown to the buyer |
<interest-amount> | Interest | Total interest amount |
<total-amount> | Total | Total amount payable |
<last-rate> | Final instalment | Final instalment amount, if it differs due to rounding |
<payment-firstday> | — | Day of month used for direct debit collection and for installment schedule logic |
On the product page, you may show a Pay in 3 teaser near the product price.
- calculate Pay in 3 plan details using chosen calculation approach (see Calculation Approaches)
- show the monthly instalment amount
- show the duration
- provide a Learn more link or similar interaction
- open a modal or detail view with the full plan values
- update values dynamically when the product price changes, for example after variant selection
| Label | Description |
|---|---|
| Per month | Monthly instalment amount |
| Duration | Number and duration of instalments |
| Effective Interest | Annual percentage rate |
| Interest | Total interest amount |
| Total | Total amount payable |
| Product page teaser | Product page modal |
|---|---|
![]() | ![]() |
The cart page follows the same principle as the product page, but the amount must always reflect the current cart total.
- calculate Pay in 3 plan details using chosen calculation approach (see Calculation Approaches)
- update the values whenever quantity or items change
- show the teaser in the cart summary area
- provide a “Learn more” interaction with the detailed values
| Cart page teaser | Cart page modal |
|---|---|
![]() | ![]() |
| Step | Purpose |
|---|---|
PAYMENT_INIT | Initialize the Ratepay transaction and receive transaction-id |
| Plan calculation | Generate the display values and instalment details |
| Buyer-facing checkout display | Show the selected Pay in 3 plan and required inputs |
PAYMENT_REQUEST | Send customer, basket, and payment data to Ratepay |
PAYMENT_CONFIRM | Finalize the transaction if required for your setup |
The first step of the checkout workflow is PAYMENT_INIT. This operation initializes the transaction and returns a transaction-id that must be included in subsequent API calls.
See also: PAYMENT_INIT
<?xml version="1.0" encoding="UTF-8"?>
<request version="1.0" xmlns="urn://www.ratepay.com/payment/1_0">
<head>
<system-id>myshop</system-id>
<operation>PAYMENT_INIT</operation>
<credential>
<profile-id>INTEGRATION_TE_DACH</profile-id>
<securitycode>4c0a11923fa3433fb168f9c7176429e9</securitycode>
</credential>
</head>
</request>Use the final checkout basket amount to generate the Pay in 3 plan values using chosen calculation approach.
When the buyer selects Pay in 3 as the payment method, your checkout should render the Pay in 3 plan values and collect the required payment data.
Checkout UI should include
- credit term values for the selected plan
- instalment amount breakdown
- date of birth input
- IBAN input
- bank account owner input
- SEPA mandate text and consent checkbox
- Terms of Payment and Privacy / Risk Check links near the order button
The legal requirements for what must be shown at checkout are defined on the Pay in 3 legal requirements page.
By default, checkout should avoid fixed due dates because the actual schedule depends on shipment timing. A buyer-friendly simplified breakdown is acceptable, or you may show estimated due dates only if they are based on a reliable shipment date.
| Checkout compact view | Checkout expanded view |
|---|---|
![]() | ![]() |
After the buyer selects Pay in 3 and provides the required data, send PAYMENT_REQUEST.
PAYMENT_REQUEST field | Source from calculatePayIn3() |
|---|---|
<installment-number> | repaymentPlan.numberOfInstallments |
<installment-amount> | repaymentPlan.regularInstallmentAmount |
<last-installment-amount> | repaymentPlan.finalInstallmentAmount |
<interest-rate> | creditTerms.nominalInterestRatePercentage |
<payment-firstday> | Constant value 2 |
<amount> | creditTerms.totalPayableAmount |
PAYMENT_REQUEST field | Source from XML response |
|---|---|
<installment-number> | <number-of-rates> |
<installment-amount> | <rate> |
<last-installment-amount> | <last-rate> |
<interest-rate> | <interest-rate> |
<payment-firstday> | <payment-firstday> |
<amount> | <total-amount> |
PAYMENT_REQUEST is the main checkout operation. It sends the customer details, basket data, and selected Pay in 3 payment details to Ratepay.
See also: PAYMENT_REQUEST
| Area | Examples |
|---|---|
| Customer data | Name, gender, date of birth, IP address, email, phone |
| Addresses | Billing and shipping addresses |
| Bank account | IBAN, BIC, account holder |
| Shopping basket | Items, discounts, shipping, total amount |
| Payment | Installment details and debit pay type |
<?xml version="1.0" encoding="UTF-8"?>
<request version="1.0" xmlns="urn://www.ratepay.com/payment/1_0">
<head>
<system-id>MyTestsystem</system-id>
<transaction-id>xx-xxxxxxxxxxxxxx</transaction-id>
<operation>PAYMENT_REQUEST</operation>
<credential>
<profile-id>INTEGRATION_TE_DACH</profile-id>
<securitycode>4c0a11923fa3433fb168f9c7176429e9</securitycode>
</credential>
<external>
<order-id>O-1234-HDZ</order-id>
<merchant-consumer-id>12345</merchant-consumer-id>
</external>
<customer-device>
<device-token>xxxxxxxxxxxxxxxxxxxxxxxx</device-token>
</customer-device>
</head>
<content>
<customer>
<first-name>Max</first-name>
<last-name>Mustermann</last-name>
<gender>M</gender>
<date-of-birth>1982-10-31</date-of-birth>
<ip-address>127.0.0.1</ip-address>
<contacts>
<email>test@test.de</email>
<phone>
<direct-dial>030123456</direct-dial>
</phone>
</contacts>
<addresses>
<address type="BILLING">
<street>Nicht-Versenden-Strasse</street>
<street-number>1</street-number>
<zip-code>12345</zip-code>
<city>Testhausen</city>
<country-code>DE</country-code>
</address>
<address type="DELIVERY">
<street>Strasse der Lieferadresse</street>
<street-number>2</street-number>
<zip-code>54321</zip-code>
<city>Testhausen</city>
<country-code>DE</country-code>
</address>
</addresses>
<bank-account>
<owner>Max Mustermann</owner>
<iban>DE44100500001654698497</iban>
<bic-swift>BELADEBEXXX</bic-swift>
</bank-account>
<nationality>DE</nationality>
<customer-allow-credit-inquiry>yes</customer-allow-credit-inquiry>
</customer>
<shopping-basket amount="200.00" currency="EUR">
<items>
<item article-number="A001" quantity="1" unit-price-gross="200.00" tax-rate="19">Example Product</item>
</items>
<shipping unit-price-gross="0.00" tax-rate="19">Versandkosten</shipping>
</shopping-basket>
<payment currency="EUR" method="INSTALLMENT">
<amount>200.00</amount>
<installment-details>
<installment-number>3</installment-number>
<installment-amount>66.67</installment-amount>
<last-installment-amount>66.66</last-installment-amount>
<interest-rate>0</interest-rate>
<payment-firstday>2</payment-firstday>
</installment-details>
<debit-pay-type>DIRECT-DEBIT</debit-pay-type>
</payment>
</content>
</request>After a successful PAYMENT_REQUEST, finalize the transaction with PAYMENT_CONFIRM if this step is required for your integration setup.
See also: PAYMENT_CONFIRM
The necessity of PAYMENT_CONFIRM depends on your integration setup. Please confirm with your Ratepay counterpart whether this step is required.
<?xml version="1.0" encoding="UTF-8"?>
<request version="1.0" xmlns="urn://www.ratepay.com/payment/1_0">
<head>
<system-id>MyTestsystem</system-id>
<transaction-id>xx-xxxxxxxxxxxxxx</transaction-id>
<operation>PAYMENT_CONFIRM</operation>
<credential>
<profile-id>INTEGRATION_TE_DACH</profile-id>
<securitycode>4c0a11923fa3433fb168f9c7176429e9</securitycode>
</credential>
<external>
<order-id>O-1234-HDZ</order-id>
</external>
</head>
</request>If you want a pragmatic setup with low complexity, use this pattern:
| Stage | Suggested implementation |
|---|---|
| Product page | Client-side calculation + teaser + “Learn more” modal |
| Cart page | Reuse the same function with cart total |
| Checkout display | Reuse the same function with final basket amount |
| Checkout API flow | PAYMENT_INIT → PAYMENT_REQUEST → PAYMENT_CONFIRM if required |
| Legal compliance | Keep the displayed fields aligned with the legal requirements page |




