Embed secure card fields
Use @peakgateway/web-sdk to place Peak-hosted card fields in your checkout. Card numbers and security codes go directly to Peak; your page and merchant server do not receive them. This does not eliminate your responsibility to secure your checkout or meet applicable PCI requirements.
Server: create the payment context
Authenticate on your server and create either:
POST /api/v1/checkout/sessionsfor a payment. Persist itsIdempotency-Keyand original body before sending. Use the returnedcheckoutUrl.POST /api/v1/setup-intentsto save a card without charging it. Bind the customer, location and consent required by the request. For a ready-to-use hosted link, usePOST /api/v1/setup-intents/issue-linkwithdeliveryMode: "IFRAME"and usehostedUrl.
The hosted context URL has a /c/{sessionId} or /setup/{setupIntentId} path. Preserve the returned URL, including its authorization parameters; do not manufacture a client secret or use an API URL as the iframe URL.
Browser: mount the fields
npm install @peakgateway/web-sdk
import { mountSecureCardFields } from '@peakgateway/web-sdk'
// These elements belong to your checkout form.
const submitButton = document.querySelector<HTMLButtonElement>('#submit-payment')!
submitButton.disabled = true
let ready = false
let complete = false
const fields = mountSecureCardFields({
container: document.getElementById('card-fields')!,
url: hostedUrl, // returned by your server for this checkout or setup intent
style: { fontFamily: 'system-ui', fontSize: '16px', color: '#172033' },
onEvent(event) {
if (event.type === 'ready') ready = true
if (event.type === 'change') complete = event.complete
submitButton.disabled = !ready || !complete
if (event.type === 'succeeded') {
// Ask your server to read the authoritative session/setup-intent state.
refreshPaymentState()
}
},
})
// In your form's submit handler, after ready and complete:
const context = { cardHolderName, billingStreet, billingCity, billingState, billingCountry, billingZip }
const result = await fields.confirm({
amount, // authoritative checkout amount supplied by your server, in minor units
currency,
context,
})
// For a setup intent, use fields.confirm({ context }) without amount/currency.
// Destroy only when removing the fields from the page:
// fields.destroy()
hostedUrl, amount and currency come from your application's server response. Collect cardHolderName and the billing-address values in ordinary form inputs; never create merchant-owned card-number or security-code inputs. The context object supplies this non-card billing information. refreshPaymentState is your application's authenticated status refresh. Handle result.type without equating submission or a browser event with authoritative payment success. See card setup for the separate Elements setup flow.
Server: use the saved card
Read the completed setup intent or checkout session with authenticated server credentials. Setup intents expose paymentMethodId; use that identifier as savedPaymentMethodId when creating a subscription or card-on-file payment. Browser events are a prompt to refresh, not proof of payment or authority to attach a customer's card. Validate the customer, location and final status on your server.
ACH is a different flow
There are no embeddable bank-account fields in the web SDK. To keep bank entry off your server, redirect to hosted checkout and set allowedPaymentMethods: ["ach"] (or include "card" as well) when creating the session. ACH must be enabled for the location and currency.
Alternatively, POST /api/v1/saved-payment-methods/bank-accounts accepts customerId, accountHolderName, accountNumber, routingNumber, accountType, mandateAccepted and mandateVersion; isDefault is optional. This option sends bank details through your server. Obtain the customer's authorization and supply mandateAccepted: true and the supported customer authorization version ach-mandate-v1. Protect bank data and never log it. Use the reference for account-type values and validation.
ACH acceptance is not settlement; see checkout lifecycle.