Use this file to discover all available pages before exploring further.
One-time payments are single, non-recurring transactions where customers pay once for a product or service. The customer selects a HitPay payment method, completes the payment via QR code or redirect, and the transaction is recorded in Stripe.
This integration connects Stripe’s Payment Element with HitPay’s Payment Request API, allowing customers to pay using local payment methods while keeping all transaction records in Stripe.
Register each HitPay payment method as a Custom Payment Method (CPM) type in your Stripe Dashboard, map those CPM Type IDs to their HitPay method identifiers in a config file, and load them into the Stripe Elements provider so they appear as options in your checkout.
1
Create Custom Payment Methods on Stripe Dashboard
Stripe DashboardCreate Custom Payment Method types in your Stripe Dashboard for each HitPay payment method you want to offer.After creating the custom payment method, the Dashboard displays the custom payment method ID (beginning with cpmt_) that you need for the next step.
Create CPM in Stripe Dashboard
Follow Stripe’s guide to create Custom Payment Method types and get your CPM Type IDs.
Download Payment Icons
Download official HitPay payment method icons (PayNow, ShopeePay, GrabPay, FPX, and more) optimized for Stripe Custom Payment Methods.
2
Create Configuration File
Server-sideThis configuration file maps the Custom Payment Methods you created in the Stripe Dashboard to their corresponding HitPay payment method identifiers. Each entry links a Stripe CPM Type ID (e.g. cpmt_xxx) to the HitPay API method name used when creating a payment request.See the HitPay Payment Methods Reference for the full list of supported payment method names and their identifiers.The chargeAutomatically flag indicates whether a payment method supports tokenized or recurring payments — these methods can be used to charge customers automatically without requiring re-authorization each time.The code below is a sample — adapt it to the payment methods you’ve configured:
config/payment-methods.ts
// config/payment-methods.tsinterface CustomPaymentMethodConfig { id: string; // Stripe CPM Type ID (cpmt_xxx) hitpayMethod: string; // HitPay one-time payment method hitpayRecurringMethod?: string; // Example only: used when the recurring method name differs from the one-time name displayName: string; chargeAutomatically: boolean; // Supports auto-charge subscriptions via HitPay tokenization}export const CUSTOM_PAYMENT_METHODS: CustomPaymentMethodConfig[] = [ { id: 'cpmt_YOUR_PAYNOW_ID', // Replace with your CPM Type ID hitpayMethod: 'paynow_online', displayName: 'PayNow', chargeAutomatically: false, // QR-based, no tokenization }, { id: 'cpmt_YOUR_SHOPEEPAY_ID', hitpayMethod: 'shopee_pay', hitpayRecurringMethod: 'shopee_recurring', displayName: 'ShopeePay', chargeAutomatically: true, }, { id: 'cpmt_YOUR_GRABPAY_ID', hitpayMethod: 'grabpay', hitpayRecurringMethod: 'grabpay_direct', displayName: 'GrabPay', chargeAutomatically: true, },];export function getHitpayMethod(cpmTypeId: string): string | undefined { return CUSTOM_PAYMENT_METHODS.find(pm => pm.id === cpmTypeId)?.hitpayMethod;}
You’ll have different CPM Type IDs in sandbox and production — they are separate Stripe accounts. Consider using environment variables or an ids: { sandbox: string, production: string } structure so you can switch between environments without code changes.
3
Add Custom Payment Method Type to Stripe Elements Configuration
Client-sideNext, add the custom payment method type to your Stripe Elements configuration. In your checkout.js file where you initialise Stripe Elements, specify the customPaymentMethods to add to the Payment Element. Provide the custom payment method ID from the previous step, the options.type and an optional subtitle.Load Stripe.js with the beta flag:
Step 2: Display Payment on Stripe Payment Elements
When a customer selects a HitPay payment method in the Payment Element, your backend creates a HitPay payment request and returns a QR code or deep link for the customer to complete payment on their mobile device.
1
Configure Environment Variables
Server-sideSet up the required API keys and configuration for both Stripe and HitPay. These credentials authenticate your server with both payment platforms.
# StripeNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxSTRIPE_SECRET_KEY=sk_test_xxx# HitPayHITPAY_API_KEY=xxxHITPAY_SALT=xxxNEXT_PUBLIC_HITPAY_ENV=sandbox # or 'production'NEXT_PUBLIC_SITE_URL=https://your-domain.com
Never expose STRIPE_SECRET_KEY or HITPAY_API_KEY to the client.
2
Create HitPay Payment Request
Server-sideWhen a user selects a CPM in the Payment Element, create a HitPay payment request and display the QR code:
Displaying QR Codes: The generate_qr: true parameter returns QR code data in qr_code_data.qr_code. For detailed guidance on rendering QR codes and handling different payment methods, see our Embedded QR Code Payments guide.
App-based methods (ShopeePay, GrabPay): Some payment methods don’t generate a QR code — instead, HitPay returns a direct_link.direct_link_url that opens the payment app directly. When directLinkUrl is present, redirect the customer to that URL instead of displaying a QR code.
Embedded QR Code Payments
Learn how to embed and display QR codes for PayNow, ShopeePay, and other supported payment methods.
3
Display QR Code in Payment Element
Client-sideRender the QR code in your checkout form when a customer selects a HitPay payment method. The customer scans the QR code with their mobile app to complete the payment.
Once the customer pays via HitPay, your backend receives a webhook, verifies the payment, and records it in Stripe using the Payment Records API — making it visible in your Stripe Dashboard alongside native card payments.
Without this step, HitPay payments only appear in your HitPay Dashboard. By recording them via Stripe’s Payment Records API, you get a single source of truth for all transactions — cards, PayNow, ShopeePay, and more — all in one place.
Stripe Payment Records API
Learn how Payment Records work and how they appear in your Stripe Dashboard.
1
Listen for Payment Confirmation
Server-side
Webhooks (Recommended)
Polling (Not Recommended)
Use webhooks for production. HitPay automatically sends a POST request to your server when payment is completed.How it works:
Customer completes payment via HitPay (scans QR, etc.)
HitPay sends webhook to your /api/hitpay/webhook endpoint
Your server verifies the signature and confirms payment status
Your server creates a Payment Record in Stripe
Transaction now appears in Stripe Dashboard for reconciliation
Advantage
Description
Reliable
Payments recorded even if user closes browser
Real-time
Instant notification on payment completion
Secure
HMAC signature verification prevents fraud
Unified Dashboard
All transactions visible in Stripe
Frontend completion detection: Even when using webhooks for payment recording, your frontend still needs a way to know when the payment is complete so it can redirect the customer. The recommended pattern is to have the frontend poll a status endpoint (e.g., /api/payment/check-status) every few seconds — this endpoint checks whether the webhook has already recorded the payment. Use an idempotency key (e.g., prec-{paymentRequestId}) when creating the Payment Record to prevent double-recording if both webhook and polling race.
Server-sideOnce HitPay confirms the payment is complete, call stripe.paymentRecords.reportPayment() to create a Payment Record (prec_xxx) in Stripe. This is what makes the transaction visible in your Stripe Dashboard alongside native card payments.
The original PaymentIntent remains "Incomplete" — this is expected for external payments. The Payment Record is the canonical record of the transaction and what Stripe uses for revenue reporting.
Each field in the payload serves a specific purpose:
Field
Value
Why
amount_requested
PaymentIntent amount + currency
Ties the record to the original order amount
payment_method_details.payment_method
Custom PaymentMethod ID (pm_xxx)
Links the CPM type (PayNow, ShopeePay, etc.) to this record
processor_details.custom.payment_reference
HitPay payment ID
External transaction reference for reconciliation
initiated_at
Unix timestamp
When the payment was initiated
customer_presence
'on_session'
Customer was actively present at time of payment
outcome
'guaranteed'
HitPay has confirmed settlement — funds are guaranteed
guaranteed.guaranteed_at
Unix timestamp
When settlement was confirmed by HitPay
metadata
HitPay IDs, PI ID
Searchable references for support and debugging
Record payment — stripe.paymentRecords.reportPayment()
// After HitPay confirms payment is completed:// 1. Create a PaymentMethod instance for your CPM typeconst paymentMethod = await stripe.paymentMethods.create({ type: 'custom', custom: { type: 'cpmt_xxx' }, // your CPM Type ID from Stripe Dashboard});// 2. Report the payment — creates a prec_xxx record in Stripeconst paymentRecord = await stripe.paymentRecords.reportPayment({ amount_requested: { value: paymentIntent.amount, // in cents, matches the original PaymentIntent currency: paymentIntent.currency, }, payment_method_details: { payment_method: paymentMethod.id, // links the CPM type to this record }, processor_details: { type: 'custom', custom: { payment_reference: hitpayPaymentId, // HitPay's payment ID for reconciliation }, }, initiated_at: Math.floor(Date.now() / 1000), customer_presence: 'on_session', outcome: 'guaranteed', guaranteed: { guaranteed_at: Math.floor(Date.now() / 1000) }, metadata: { hitpay_payment_id: hitpayPaymentId, hitpay_payment_request_id: hitpayPaymentRequestId, stripe_payment_intent_id: paymentIntentId, },});// 3. Update PaymentIntent metadata for easy lookupawait stripe.paymentIntents.update(paymentIntentId, { metadata: { external_payment_status: 'completed', stripe_payment_record_id: paymentRecord.id, // prec_xxx },});