Implement a custom payment plan simulator
The Product.paymentPlans core GraphQL field returns the BNPL installment plans
available for a given product. The amount is derived from the product's final
price (incl. or excl. tax depending on the shop's theme.priceDisplay.variants
configuration), and the result is aggregated from any number of registered
simulators.
How aggregation works
loaders.Payment exposes a registry similar to registerEmbeddedPaymentMethod:
- Each PSP package registers its simulator in its GraphQL module's
contextEnhancer. loaders.Payment.simulatePaymentPlans(amount)calls every registered simulator in parallel withPromise.allSettled.- Plans returned by a fulfilled simulator are concatenated; rejections are logged and skipped, so a single PSP outage cannot prevent the others from rendering.
Implement the simulator
Result
Once registered, the simulator contributes to Product.paymentPlans without any
further wiring. Add the field to the product page query and your simulator shows
up alongside every other registered one:
Product.paymentPlans is resolved per product, and simulator calls are not
batched across products in a single GraphQL request. A query that returns N
products fires N HTTP calls per registered simulator, in parallel.
Treat the field as a product-detail-page field. Avoid querying it on listings
(PLP, cart, search results, related products). If your PSP exposes a batched
simulation endpoint, share the call across the request with a DataLoader
inside simulate(amount).
query ProductPaymentPlans($sku: String!) {
product(sku: $sku) {
paymentPlans {
id
type
provider {
id
label
logoUrl
}
installments {
dueDate
amount {
amount
currency
}
fees {
amount
currency
}
}
apr
debitRate
}
}
}