Skip to content

PaymentIntent Integration Guide

This guide explains how to integrate with mpac-pgw using the PaymentIntent API, following Stripe-style patterns.

Overview

A PaymentIntent represents a payment transaction from creation to completion. It tracks the lifecycle of a payment and provides a payment_token for secure frontend operations.

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ Merchant Backend│────▶│    mpac-pgw      │────▶│    Providers    │
│ (svc-smarttab)  │HMAC │                 │     │ (PayPay, etc.)  │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │                       ▲
        │ payment_token         │ payment_token
        ▼                       │
┌─────────────────┐     ┌───────┴─────────┐
│  Terminal App   │────▶│  @cloudsky/mpac-pgw-sdk-js   │
│ (MPACQR, etc.)│     │                 │
└─────────────────┘     └─────────────────┘

Authentication

Server-to-Server (HMAC-SHA256)

For backend-to-backend communication, use HMAC-SHA256 authentication:

Authorization: HMAC-SHA256 <merchant_id>:<timestamp>:<signature>

Signature Calculation:

typescript
const crypto = require('crypto');

function generateHMACSignature(
  secretKey: string,
  method: string,
  path: string,
  timestamp: number,
  body: string
): string {
  const bodyHash = crypto.createHash('sha256').update(body || '').digest('hex');
  const message = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
  return crypto.createHmac('sha256', secretKey).update(message).digest('hex');
}

// Example usage
const timestamp = Math.floor(Date.now() / 1000);
const signature = generateHMACSignature(
  'your_secret_key',
  'POST',
  '/v1/payment_intents',
  timestamp,
  JSON.stringify(requestBody)
);

const authHeader = `HMAC-SHA256 ${merchantId}:${timestamp}:${signature}`;

Frontend SDK (payment_token)

The frontend SDK uses the payment_token returned from PaymentIntent creation:

  • GET requests: Query parameter ?payment_token=...
  • POST requests: In request body { "payment_token": "..." }

Creating a PaymentIntent (Server-to-Server)

Endpoint

POST /v1/payment_intents
Authorization: HMAC-SHA256 <merchant_id>:<timestamp>:<signature>
Idempotency-Key: <unique-uuid>
Content-Type: application/json

Request Body

json
{
  "merchant_ref_id": "order_2025_001",
  "merchant_store_id": "store_tokyo_001",
  "merchant_terminal_id": "pos_001",
  "amount": 2500,
  "currency": "JPY",
  "payment_method_types": ["qr_mpm", "credit_card", "cash"],
  "capture_method": "automatic"
}
FieldTypeRequiredDescription
merchant_ref_idstringYour unique order/transaction reference
merchant_store_idstringOptional store identifier for multi-store merchants
merchant_terminal_idstringOptional terminal/POS identifier
amountintegerAmount in minor units (e.g., yen for JPY)
currencystringISO 4217 currency code (default: JPY)
payment_method_typesstring[]Allowed payment methods
capture_methodstringautomatic (default) or manual

Payment Method Types:

  • qr_mpm - QR code displayed by merchant (PayPay, LINE Pay, etc.)
  • qr_cpm - QR code scanned from customer
  • credit_card - Credit card via MPACCreditCard app (external)
  • cash - Cash payment (manual confirmation)

Response

json
{
  "id": "pi_01HXY...",
  "object": "payment_intent",
  "status": "requires_payment_method",
  "amount": 2500,
  "currency": "JPY",
  "payment_method_types": ["qr_mpm", "credit_card", "cash"],
  "payment_token": "pi_01HXY..._secret_abc123...",
  "capture_method": "automatic",
  "merchant_store_id": "store_tokyo_001",
  "merchant_terminal_id": "pos_001",
  "expires_at": "2025-12-08T10:30:00Z",
  "created_at": "2025-12-08T10:00:00Z"
}

Important: Pass the payment_token to your frontend/terminal app. This token allows the app to confirm/cancel the payment without exposing your API credentials.

Code Examples

Node.js / TypeScript

typescript
import crypto from 'crypto';

interface CreatePaymentIntentRequest {
  merchant_ref_id: string;
  amount: number;
  currency?: string;
  payment_method_types: string[];
  capture_method?: 'automatic' | 'manual';
}

async function createPaymentIntent(
  request: CreatePaymentIntentRequest
): Promise<PaymentIntent> {
  const baseUrl = 'https://api.pgw.mpac-cloud.com';
  const path = '/v1/payment_intents';
  const method = 'POST';
  const body = JSON.stringify(request);

  const timestamp = Math.floor(Date.now() / 1000);
  const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
  const message = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
  const signature = crypto
    .createHmac('sha256', process.env.MPS_SECRET_KEY!)
    .update(message)
    .digest('hex');

  const response = await fetch(`${baseUrl}${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `HMAC-SHA256 ${process.env.MPS_MERCHANT_ID}:${timestamp}:${signature}`,
      'Idempotency-Key': crypto.randomUUID(),
    },
    body,
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`mpac-pgw Error: ${error.code} - ${error.message}`);
  }

  return response.json();
}

// Usage
const paymentIntent = await createPaymentIntent({
  merchant_ref_id: 'order_123',
  amount: 1500,
  currency: 'JPY',
  payment_method_types: ['qr_mpm', 'credit_card', 'cash'],
});

// Pass payment_token to terminal app
sendToTerminal(paymentIntent.payment_token);

Python

python
import hashlib
import hmac
import time
import uuid
import requests
import json
import os

def create_payment_intent(
    merchant_ref_id: str,
    amount: int,
    payment_method_types: list[str],
    currency: str = "JPY",
    capture_method: str = "automatic"
) -> dict:
    base_url = "https://api.pgw.mpac-cloud.com"
    path = "/v1/payment_intents"
    method = "POST"

    body = json.dumps({
        "merchant_ref_id": merchant_ref_id,
        "amount": amount,
        "currency": currency,
        "payment_method_types": payment_method_types,
        "capture_method": capture_method,
    })

    timestamp = int(time.time())
    body_hash = hashlib.sha256(body.encode()).hexdigest()
    message = f"{method}\n{path}\n{timestamp}\n{body_hash}"

    signature = hmac.new(
        os.environ["MPS_SECRET_KEY"].encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(
        f"{base_url}{path}",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"HMAC-SHA256 {os.environ['MPS_MERCHANT_ID']}:{timestamp}:{signature}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        data=body,
    )

    response.raise_for_status()
    return response.json()

# Usage
payment_intent = create_payment_intent(
    merchant_ref_id="order_123",
    amount=1500,
    payment_method_types=["qr_mpm", "credit_card", "cash"],
)

# Pass payment_token to terminal app
print(f"Payment Token: {payment_intent['payment_token']}")

Go

go
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"

    "github.com/google/uuid"
)

type CreatePaymentIntentRequest struct {
    MerchantRefID      string   `json:"merchant_ref_id"`
    Amount             int64    `json:"amount"`
    Currency           string   `json:"currency"`
    PaymentMethodTypes []string `json:"payment_method_types"`
    CaptureMethod      string   `json:"capture_method,omitempty"`
}

type PaymentIntent struct {
    ID                 string   `json:"id"`
    Object             string   `json:"object"`
    Status             string   `json:"status"`
    Amount             int64    `json:"amount"`
    Currency           string   `json:"currency"`
    PaymentMethodTypes []string `json:"payment_method_types"`
    PaymentToken       string   `json:"payment_token"`
    CaptureMethod      string   `json:"capture_method"`
    ExpiresAt          string   `json:"expires_at"`
    CreatedAt          string   `json:"created_at"`
}

func createPaymentIntent(req CreatePaymentIntentRequest) (*PaymentIntent, error) {
    baseURL := "https://api.pgw.mpac-cloud.com"
    path := "/v1/payment_intents"
    method := "POST"

    body, _ := json.Marshal(req)
    timestamp := strconv.FormatInt(time.Now().Unix(), 10)

    // Calculate signature
    bodyHash := sha256.Sum256(body)
    message := fmt.Sprintf("%s\n%s\n%s\n%s", method, path, timestamp, hex.EncodeToString(bodyHash[:]))

    mac := hmac.New(sha256.New, []byte(os.Getenv("MPS_SECRET_KEY")))
    mac.Write([]byte(message))
    signature := hex.EncodeToString(mac.Sum(nil))

    // Create request
    httpReq, _ := http.NewRequest(method, baseURL+path, bytes.NewReader(body))
    httpReq.Header.Set("Content-Type", "application/json")
    httpReq.Header.Set("Authorization", fmt.Sprintf("HMAC-SHA256 %s:%s:%s",
        os.Getenv("MPS_MERCHANT_ID"), timestamp, signature))
    httpReq.Header.Set("Idempotency-Key", uuid.New().String())

    resp, err := http.DefaultClient.Do(httpReq)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var pi PaymentIntent
    json.NewDecoder(resp.Body).Decode(&pi)
    return &pi, nil
}

func main() {
    pi, _ := createPaymentIntent(CreatePaymentIntentRequest{
        MerchantRefID:      "order_123",
        Amount:             1500,
        Currency:           "JPY",
        PaymentMethodTypes: []string{"qr_mpm", "credit_card", "cash"},
    })

    fmt.Printf("Payment Token: %s\n", pi.PaymentToken)
}

Payment Flow by Method

QR MPM (PayPay, LINE Pay, etc.)

1. Backend creates PaymentIntent → returns payment_token
2. Terminal app confirms with SDK → generates QR code
3. Customer scans QR and pays in their app
4. Provider webhook → PGW updates status
5. SDK polls and detects success → emits payment_success

Confirm Request (from SDK):

json
POST /v1/payment_intents/{id}/confirm
{
  "payment_token": "pi_..._secret_...",
  "payment_method_type": "qr_mpm",
  "provider": "PAYPAY"
}

Response:

json
{
  "object": "payment_intent",
  "status": "requires_action",
  "amount": 2500,
  "currency": "JPY",
  "payment_id": "pi_01HXY...",
  "payment_intent_id": "pi_01HXY...",
  "qr_content": "https://qr.paypay.ne.jp/...",
  "expires_at": "2025-12-08T10:05:00Z",
  "provider": "PAYPAY",
  "qr_code_url": "https://..."
}

Credit Card (External via MPACCreditCard)

1. Backend creates PaymentIntent → returns payment_token
2. Terminal app confirms via Native Bridge → launches MPACCreditCard app
3. MPACCreditCard processes payment externally
4. Native Bridge receives result → confirms with PGW
5. PGW records the payment (processing_mode: external)

Confirm Request (from Native Bridge):

json
POST /v1/payment_intents/{id}/confirm
{
  "payment_token": "pi_..._secret_...",
  "payment_method_type": "credit_card",
  "provider": "VISA",
  "external_ref_id": "APPROVAL_CODE_123"
}

Response:

json
{
  "object": "payment_intent",
  "status": "succeeded",
  "amount": 2500,
  "currency": "JPY",
  "payment_id": "pi_01HXY...",
  "payment_intent_id": "pi_01HXY...",
  "provider": "VISA"
}

Cash (Manual Confirmation)

1. Backend creates PaymentIntent → returns payment_token
2. Customer pays cash to staff
3. Staff confirms cash received via app
4. App confirms with PGW (processing_mode: manual)

Confirm Request:

json
POST /v1/payment_intents/{id}/confirm
{
  "payment_token": "pi_..._secret_...",
  "payment_method_type": "cash"
}

Response:

json
{
  "object": "payment_intent",
  "status": "succeeded",
  "amount": 2500,
  "currency": "JPY",
  "payment_id": "pi_01HXY...",
  "payment_intent_id": "pi_01HXY...",
  "provider": "CASH"
}

PaymentIntent Status Flow

requires_payment_method ──▶ requires_action ──▶ processing ──▶ succeeded
         │                        │                │
         └────────────────────────┴────────────────┴──▶ failed
         │                        │
         └────────────────────────┴──────────────────▶ cancelled
StatusDescription
requires_payment_methodInitial state, waiting for payment method selection
requires_actionQR code generated, waiting for customer action
processingPayment is being processed by provider
succeededPayment completed successfully
failedPayment failed
cancelledPayment was cancelled

Processing Modes

ModePayment MethodsDescription
pgw_processedQR (MPM/CPM)mpac-pgw processes the payment directly
externalCredit CardRecorded by PGW, processed by external app
manualCashRecorded by PGW, confirmed manually by staff

Idempotency

Always include an Idempotency-Key header for POST requests:

Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
  • Same key within 24 hours → returns cached response
  • merchant_ref_id is also unique per merchant → prevents duplicate orders

Error Handling

Error Response Format

json
{
  "error": {
    "code": "invalid_request",
    "message": "Amount must be greater than 0",
    "param": "amount"
  }
}

Common Error Codes

CodeHTTP StatusDescription
invalid_request400Invalid request parameters
authentication_error401Invalid HMAC signature or credentials
payment_not_found404PaymentIntent not found
invalid_payment_state409Invalid status transition
provider_error502Error from payment provider
internal_error500Internal server error

Webhooks

Configure your webhook URL in the merchant settings. mpac-pgw sends POST requests for payment events:

json
{
  "id": "evt_01HXY...",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_01HXY...",
      "status": "succeeded",
      "amount": 2500,
      "currency": "JPY"
    }
  },
  "created": 1702000000
}

Webhook Event Types

  • payment_intent.succeeded
  • payment_intent.failed
  • payment_intent.cancelled

Best Practices

  1. Always use Idempotency-Key for create operations
  2. Store payment_token securely - it grants access to modify the payment
  3. Handle webhook events for reliable payment status updates
  4. Set appropriate timeouts - PaymentIntents expire after 30 minutes by default
  5. Use merchant_ref_id to link payments to your order system

MPAC — MP-Solution Advanced Cloud Service