Skip to main content
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.

How It Works

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.

Key Concepts

ComponentPurpose
Stripe PaymentIntentTracks the payment intent on Stripe’s side. Remains “incomplete” for external payments.
HitPay Payment RequestProcesses the actual payment via local methods (PayNow, ShopeePay, etc.) and generates QR codes.
Stripe Payment RecordsRecords the external HitPay payment back in Stripe for unified reporting and reconciliation.

API References


Payment Flow

API Calls Summary

StepAPIEndpoint / MethodPurpose
1StripepaymentIntents.create()Create PaymentIntent for checkout
2HitPayPOST /payment-requestsCreate payment request & generate QR code
3HitPayWebhook to your serverNotify payment completion
4StripepaymentMethods.create()Create CPM PaymentMethod
5StripepaymentRecords.reportPayment()Record payment for dashboard visibility

Step 1: Configure Custom Payment Methods

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-sideMap your Stripe CPM Type IDs to HitPay payment methods:
// config/payment-methods.ts

interface CustomPaymentMethodConfig {
  id: string;                     // Stripe CPM Type ID (cpmt_xxx)
  hitpayMethod: string;           // HitPay payment method
  displayName: string;
}

export const CUSTOM_PAYMENT_METHODS: CustomPaymentMethodConfig[] = [
  {
    id: 'cpmt_YOUR_PAYNOW_ID',      // Replace with your CPM Type ID
    hitpayMethod: 'paynow_online',
    displayName: 'PayNow',
  },
  {
    id: 'cpmt_YOUR_SHOPEEPAY_ID',
    hitpayMethod: 'shopee_pay',
    displayName: 'ShopeePay',
  },
  {
    id: 'cpmt_YOUR_GRABPAY_ID',
    hitpayMethod: 'grabpay',
    displayName: 'GrabPay',
  },
];

export function getHitpayMethod(cpmTypeId: string): string | undefined {
  return CUSTOM_PAYMENT_METHODS.find(pm => pm.id === cpmTypeId)?.hitpayMethod;
}
3

Setup Stripe Client

Client-sideLoad Stripe.js with the beta flag and configure the Payment Element with your CPMs:
// lib/stripe-client.ts
import { loadStripe } from '@stripe/stripe-js';

export const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
  {
    betas: ['custom_payment_methods_beta_1'],
  }
);
// Add CPMs to Elements provider
import { CUSTOM_PAYMENT_METHODS } from '@/config/payment-methods';

const elementsOptions = {
  clientSecret,
  customPaymentMethods: CUSTOM_PAYMENT_METHODS.map(pm => ({
    id: pm.id,
    options: { type: 'static' as const },
  })),
};

<Elements stripe={stripePromise} options={elementsOptions}>
  <CheckoutForm />
</Elements>

Add CPM to Elements

Learn more about configuring Custom Payment Methods in the Payment Element.

Step 2: Create Payment Request

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.
# Stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx

# HitPay
HITPAY_API_KEY=xxx
HITPAY_SALT=xxx
NEXT_PUBLIC_HITPAY_ENV=sandbox  # or 'production'
NEXT_PUBLIC_BASE_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:
// app/api/hitpay/create/route.ts
import { NextResponse } from 'next/server';

const HITPAY_API_URL = process.env.NEXT_PUBLIC_HITPAY_ENV === 'production'
  ? 'https://api.hit-pay.com/v1'
  : 'https://api.sandbox.hit-pay.com/v1';

export async function POST(request: Request) {
  const { amount, currency, paymentMethod, paymentIntentId } = await request.json();

  const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;

  const response = await fetch(`${HITPAY_API_URL}/payment-requests`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-BUSINESS-API-KEY': process.env.HITPAY_API_KEY!,
    },
    body: JSON.stringify({
      amount: (amount / 100).toFixed(2),  // Convert cents to dollars
      currency,
      payment_methods: [paymentMethod],
      reference_number: paymentIntentId,
      webhook: `${baseUrl}/api/hitpay/webhook`,
      generate_qr: true,
    }),
  });

  const data = await response.json();

  return NextResponse.json({
    paymentRequestId: data.id,
    qrCode: data.qr_code_data?.qr_code,
    checkoutUrl: data.url,
  });
}
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.

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.
// components/CheckoutForm.tsx
'use client';

import { useState } from 'react';
import { PaymentElement } from '@stripe/react-stripe-js';
import { QRCodeSVG } from 'qrcode.react';
import { CUSTOM_PAYMENT_METHODS, getHitpayMethod } from '@/config/payment-methods';

export function CheckoutForm({ amount, currency, paymentIntentId }) {
  const [qrCode, setQrCode] = useState<string | null>(null);
  const [selectedCpm, setSelectedCpm] = useState<string | null>(null);

  const handlePaymentElementChange = async (event: any) => {
    const paymentType = event.value?.type;

    // Check if selected payment method is a CPM
    const isCpm = CUSTOM_PAYMENT_METHODS.some(pm => pm.id === paymentType);

    if (isCpm) {
      setSelectedCpm(paymentType);

      // Create HitPay payment request
      const hitpayMethod = getHitpayMethod(paymentType);
      const response = await fetch('/api/hitpay/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          amount,
          currency,
          paymentMethod: hitpayMethod,
          paymentIntentId,
        }),
      });

      const data = await response.json();
      setQrCode(data.qrCode);
    } else {
      setSelectedCpm(null);
      setQrCode(null);
    }
  };

  return (
    <div>
      <PaymentElement onChange={handlePaymentElementChange} />

      {qrCode && (
        <div className="qr-container">
          <h3>Scan to Pay</h3>
          <QRCodeSVG value={qrCode} size={256} />
          <p>Waiting for payment...</p>
        </div>
      )}
    </div>
  );
}

Step 3: Record Payment Confirmation

Once the customer completes payment via HitPay, you need to:
  1. Listen for payment confirmation from HitPay (via webhook or polling)
  2. Record the payment in Stripe using the Payment Records API
This ensures all HitPay transactions appear in your Stripe Dashboard alongside native Stripe payments, enabling unified reporting and easier reconciliation.
Why record payments in Stripe? Without this step, HitPay payments would 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 visible 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

Testing

Sandbox Testing

  1. Set NEXT_PUBLIC_HITPAY_ENV=sandbox in your environment
  2. Use HitPay sandbox API credentials
  3. When a QR code is displayed, HitPay provides a “Complete Mock Payment” link
  4. Click this link to simulate a successful payment

Production Testing

  1. Switch to production credentials
  2. Use real payment apps to scan QR codes
  3. Verify payments appear in both HitPay and Stripe dashboards
PaymentIntents will show as “Incomplete” in Stripe - this is expected. External payments are tracked via Payment Records (prec_* IDs).

Troubleshooting

  • Verify the CPM Type is enabled in Stripe Dashboard
  • Check that the CPM ID in your config matches the Dashboard
  • Ensure you’re loading Stripe.js with custom_payment_methods_beta_1
  • Not all HitPay methods support QR codes (e.g., GrabPay uses redirect)
  • Check the checkoutUrl fallback is being displayed
  • Verify the payment method is enabled in HitPay Dashboard
  • The payment method may not be enabled in your HitPay account
  • Some methods aren’t available in sandbox mode
  • Check the error message for specific details
  • Check server logs for Payment Records API errors
  • Verify your Stripe API version includes the beta flag
  • The payment still succeeded if HitPay shows completed