PaymentRequest
Open the browser's own payment sheet, filled from the cards and wallets the user already has - and, on the other side of the boundary, register your installed app as a payment handler other sites can pay through.
@inject Bit.Butil.PaymentRequest paymentRequestMDN reference
Show resolves while the sheet is still on screen showing "processing". You then
process PaymentResponse.Details on your server and call Complete with
the outcome - that is what dismisses it. Forget the second call and the user is left staring at
a spinner.
allow="payment"). The demo below uses the basic-card method
identifier, which most engines have retired - expect IsSupported to be true and the
sheet to refuse to open anyway. A real integration names its processor's method identifier, e.g.
https://google.com/pay.
Returns true when the runtime exposes window.PaymentRequest. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.
@inject Bit.Butil.PaymentRequest paymentRequest
var supported = await paymentRequest.IsSupported();Asks whether any of the given payment methods could be used, without showing UI and without needing a gesture. It answers about the methods, not about the user having a usable card behind them - and browsers rate-limit it, so call it once per page rather than per render.
var methods = new[]
{
new PaymentMethod { SupportedMethods = "https://google.com/pay" }
};
var details = new PaymentDetails
{
Total = new PaymentItem
{
Label = "Total",
Amount = new PaymentCurrencyAmount { Currency = "USD", Value = "19.99" }
}
};
var canPay = await paymentRequest.CanMakePayment(methods, details);Show opens the sheet from a click and resolves with what the user authorized, or null if they dismissed it. The response carries the processor payload as raw JSON in Details - send that to your server. Complete dismisses the sheet with the result; Abort closes it while the user is still deciding.
var response = await paymentRequest.Show(methods, details, new PaymentOptions
{
RequestPayerName = true,
RequestPayerEmail = true
});
if (response is not null)
{
// Post response.Details to the server, then close the sheet with the answer.
var ok = await ProcessOnServer(response.Details);
await paymentRequest.Complete(response.Id,
ok ? PaymentCompleteResult.Success : PaymentCompleteResult.Fail);
}The other side of the boundary: what an installed app registers so other sites can pay through it. The handling itself happens in the service worker's canmakepayment and paymentrequest events; what a page controls is the account hint shown next to your app, and which fields your handler collects itself. Needs an active service worker registration.
Bit.Butil.PaymentHandler paymentHandler
{
// What a page controls: the account shown next to this app in the payment sheet, and which
// fields the handler collects itself rather than letting the browser ask for them.
private async Task Configure()
{
await paymentHandler.SetUserHint("[email protected]");
var hint = await paymentHandler.GetUserHint();
var enabled = await paymentHandler.EnableDelegations(
new[] { "shippingAddress", "payerEmail" });
}
}// The handling itself. Neither event can be answered from a page: the browser wakes the worker with
// no tab necessarily open, which is why an installed handler needs an active registration.
self.addEventListener('canmakepayment', event => {
// Answered before the sheet is drawn, to decide whether this app is offered at all.
event.respondWith(true);
});
self.addEventListener('paymentrequest', event => {
// Opening a window is how a handler shows its own UI; the response is whatever that window
// resolves with, and its shape is the payment method's business rather than the browser's.
event.respondWith(new Promise(resolve => {
event.openWindow('/wallet/confirm').then(client => {
self.addEventListener('message', message => resolve(message.data));
});
}));
});{
"//": "Served at the payment method's own URL, which is the payment method identifier itself.",
"default_applications": ["https://wallet.example/manifest.json"],
"supported_origins": ["https://wallet.example"]
}API reference
ValueTask<bool> IsSupported()ValueTask<bool> CanMakePayment(PaymentMethod[] methods, PaymentDetails details)ValueTask<PaymentResponse?> Show(PaymentMethod[] methods, PaymentDetails details, PaymentOptions? options = null)ValueTask Complete(string responseId, PaymentCompleteResult result = PaymentCompleteResult.Success)ValueTask<bool> Abort()string SupportedMethods, object? Datastring? Id, PaymentItem Total, PaymentItem[]? DisplayItems, PaymentShippingOption[]? ShippingOptions, PaymentDetailsModifier[]? Modifiersstring Label, PaymentCurrencyAmount Amount, bool Pendingstring Currency, string Valuestring Id, string Label, PaymentCurrencyAmount Amount, bool Selectedstring SupportedMethods, PaymentItem? Total, PaymentItem[]? AdditionalDisplayItems, object? Databool RequestPayerName, bool RequestPayerEmail, bool RequestPayerPhone, bool RequestShipping, string? ShippingTypestring Id, string RequestId, string MethodName, JsonElement Details, string? PayerName, string? PayerEmail, string? PayerPhone, string? ShippingOption, PaymentAddress? ShippingAddressstring[] AddressLine, string? Country, string? City, string? Region, string? PostalCode, string? DependentLocality, string? SortingCode, string? Organization, string? Recipient, string? Phoneenum { Success, Fail, Unknown }ValueTask<bool> IsSupported()ValueTask<string> GetUserHint()ValueTask SetUserHint(string userHint)ValueTask<bool> EnableDelegations(string[] delegations)