payment-integration

Installation
SKILL.md

HitPay Payment Integration

Payment gateway integration for APAC businesses using Next.js and JavaScript/TypeScript. Supports card payments via redirect, QR-based payments (PayNow, GrabPay, etc.) via embedded QR codes, and HitPay Drop-In UI for an all-in-one checkout.

When to Apply

Reference this skill when:

  • Integrating HitPay payment gateway
  • Creating payment checkout flows
  • Implementing QR code payments (PayNow, GrabPay, ShopeePay)
  • Handling payment webhooks
  • Building checkout pages

Need the Drop-In UI? For HitPay.js embedded checkout modal, see the drop-in-ui skill. Operating payments via MCP tools? For provider-currency mapping and QR operations, use the payment-methods skill.

Step 1: Determine Payment Methods

Ask which payment methods the developer wants to support:

Card-Only Methods

  • card (Visa, Mastercard, Amex)

QR-Only Methods

  • paynow_online (Singapore)
  • grabpay_direct
  • shopee_pay
  • wechat
  • alipay
  • fpx (Malaysia)
  • promptpay (Thailand)
  • truemoney (Thailand)
  • vietqr (Vietnam)
  • qris (Indonesia)
  • upi (India)
  • gcash (Philippines)

Step 2: Choose Frontend Approach

Payment Methods Recommended Frontend
Card only Option A: Redirect to HitPay checkout
QR only Option B: Embedded QR on your site
Both card + QR Option C: Payment method selector
Full checkout experience Option D: HitPay Drop-In UI (see drop-in-ui skill)

API Basics

Environment Base URL
Sandbox https://api.sandbox.hit-pay.com
Production https://api.hit-pay.com

Authentication

const headers = {
  'X-BUSINESS-API-KEY': process.env.HITPAY_API_KEY,
  'Content-Type': 'application/json',
};

Get API keys from Settings > Payment Gateway > API Keys in your HitPay dashboard.

Frontend Option A: Redirect (Cards)

Best for card payments or when you want HitPay to handle the full checkout UI.

Flow

  1. Backend creates payment request
  2. Frontend redirects to response.url
  3. Customer pays on HitPay hosted page
  4. Customer returns to your redirect_url
  5. Backend confirms via webhook

Next.js API Route

// app/api/payments/create/route.ts
import { NextResponse } from 'next/server';

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

  const response = await fetch('https://api.sandbox.hit-pay.com/v1/payment-requests', {
    method: 'POST',
    headers: {
      'X-BUSINESS-API-KEY': process.env.HITPAY_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount,
      currency,
      payment_methods: ['card'],
      reference_number: orderId,
      redirect_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/complete`,
      webhook: `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/hitpay`,
    }),
  });

  const data = await response.json();
  return NextResponse.json({ url: data.url, paymentRequestId: data.id });
}

Frontend Component

// components/CheckoutButton.tsx
'use client';

export function CheckoutButton({ amount, currency, orderId }: Props) {
  const handleCheckout = async () => {
    const response = await fetch('/api/payments/create', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ amount, currency, orderId }),
    });

    const { url } = await response.json();
    window.location.href = url; // Redirect to HitPay
  };

  return <button onClick={handleCheckout}>Pay with Card</button>;
}

Frontend Option B: Embedded QR Code

Best for QR-based payments where customer stays on your site.

Flow

  1. Backend creates payment request with generate_qr: true
  2. Frontend renders QR code from response.qr_code_data
  3. Customer scans with payment app
  4. Frontend polls for status or listens for webhook
  5. Backend confirms via webhook

Next.js API Route

// app/api/payments/create-qr/route.ts
import { NextResponse } from 'next/server';

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

  const response = await fetch('https://api.sandbox.hit-pay.com/v1/payment-requests', {
    method: 'POST',
    headers: {
      'X-BUSINESS-API-KEY': process.env.HITPAY_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount,
      currency,
      payment_methods: [paymentMethod],
      generate_qr: true,
      reference_number: orderId,
      webhook: `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/hitpay`,
    }),
  });

  const data = await response.json();
  return NextResponse.json({
    paymentRequestId: data.id,
    qrCodeData: data.qr_code_data,
  });
}

Frontend Component

// components/QRPayment.tsx
'use client';

import { useEffect, useRef } from 'react';
import QRCode from 'qrcode';

export function QRPayment({ amount, currency, orderId, paymentMethod }: Props) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const createQR = async () => {
      const response = await fetch('/api/payments/create-qr', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ amount, currency, orderId, paymentMethod }),
      });

      const { qrCodeData } = await response.json();

      if (canvasRef.current && qrCodeData) {
        await QRCode.toCanvas(canvasRef.current, qrCodeData, { width: 256 });
      }
    };

    createQR();
  }, [amount, currency, orderId, paymentMethod]);

  return (
    <div>
      <canvas ref={canvasRef} />
      <p>Scan with your payment app</p>
    </div>
  );
}

Frontend Option C: Payment Method Selector

Best for supporting both cards and QR methods.

// components/PaymentMethodSelector.tsx
'use client';

import { useState } from 'react';
import { QRPayment } from './QRPayment';

const PAYMENT_METHODS = [
  { id: 'card', label: 'Credit/Debit Card', type: 'redirect' },
  { id: 'paynow_online', label: 'PayNow', type: 'qr' },
  { id: 'grabpay_direct', label: 'GrabPay', type: 'qr' },
  { id: 'shopee_pay', label: 'ShopeePay', type: 'qr' },
];

export function PaymentMethodSelector({ amount, currency, orderId }: Props) {
  const [selected, setSelected] = useState<string | null>(null);

  const handlePayment = async (method: typeof PAYMENT_METHODS[0]) => {
    if (method.type === 'redirect') {
      const response = await fetch('/api/payments/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ amount, currency, orderId }),
      });
      const { url } = await response.json();
      window.location.href = url;
    } else {
      setSelected(method.id);
    }
  };

  if (selected) {
    return (
      <QRPayment
        amount={amount}
        currency={currency}
        orderId={orderId}
        paymentMethod={selected}
      />
    );
  }

  return (
    <div>
      <h3>Select Payment Method</h3>
      {PAYMENT_METHODS.map((method) => (
        <button key={method.id} onClick={() => handlePayment(method)}>
          {method.label}
        </button>
      ))}
    </div>
  );
}

Webhook Handling

Always verify webhook signatures before processing. See the webhook-handler skill for full details.

// app/api/webhooks/hitpay/route.ts
import { NextResponse } from 'next/server';
import crypto from 'crypto';

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('Hitpay-Signature');

  const expectedSignature = crypto
    .createHmac('sha256', process.env.HITPAY_SALT!)
    .update(body)
    .digest('hex');

  if (signature !== expectedSignature) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const payload = JSON.parse(body);

  if (payload.status === 'completed') {
    await markOrderAsPaid(payload.reference_number);
  }

  return NextResponse.json({ received: true });
}

Checking Payment Status

See references/payment-request-api.md for the full API reference.

// app/api/payments/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const response = await fetch(
    `https://api.sandbox.hit-pay.com/v1/payment-requests/${params.id}`,
    {
      headers: {
        'X-BUSINESS-API-KEY': process.env.HITPAY_API_KEY!,
      },
    }
  );

  const data = await response.json();
  return NextResponse.json(data);
}

Environment Variables

# .env.local
HITPAY_API_KEY=your_api_key
HITPAY_SALT=your_webhook_salt
NEXT_PUBLIC_APP_URL=http://localhost:3000

Test Cards (Sandbox)

Result Number Expiry CVC
Success 4242 4242 4242 4242 Any future Any 3 digits
Declined 4000 0000 0000 0002 Any future Any 3 digits

Resources

Related skills
Installs
1
First Seen
Apr 21, 2026