mpac-pgw Integration Guide
This guide explains how to integrate with the MPAC Payment Gateway using the available SDKs.
Overview
mpac-pgw uses a two-step payment flow inspired by Stripe's PaymentIntent pattern:
- Backend creates a PaymentIntent (server-to-server, HMAC-authenticated)
- Frontend confirms the payment (client SDK, payment_token-authenticated)
Backend (HMAC-SHA256) mpac-pgw Frontend (payment_token)
───────────────────── ───────── ──────────────────────────
│ │ │
│ POST /v1/payment_intents │ │
│ ────────────────────────>│ │
│ │ │
│ { id, payment_token } │ │
│ <────────────────────────│ │
│ │ │
│ pass payment_token to frontend │
│ ──────────────────────────────────────────────────> │
│ │ │
│ │ GET /v1/payment_intents/:id│
│ │ <──────────────────────────│
│ │ │
│ │ POST .../confirm │
│ │ <──────────────────────────│
│ │ │
│ │ { qr_content, status } │
│ │ ──────────────────────────>│Authentication
Server-to-Server (HMAC-SHA256)
All backend API calls use HMAC-SHA256 authentication:
Authorization: HMAC-SHA256 <api_key>:<timestamp>:<signature>
Message = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + HEX(SHA256(BODY))
Key = bytes(HEX(SHA256(secret_key)))
Signature = HEX(HMAC-SHA256(Key, Message))Note: The HMAC key is the byte representation of the hex-encoded SHA-256 of the secret key (64 ASCII bytes), matching the
secret_key_hashcolumn in the database.
The timestamp must be within a 5-minute window of the server's clock.
Client/Frontend (payment_token)
Frontend SDKs authenticate using a payment_token returned when creating a PaymentIntent:
- GET requests: Token is sent as a query parameter
?payment_token=... - POST requests: Token is included in the request body
{ "payment_token": "..." }
Payment tokens are single-use, time-limited (5 minutes), and scoped to a single PaymentIntent.
Integration Steps
1. Backend Setup
Install the server-side SDK for your language:
Go:
go get github.com/mp-solution-inc/mpac-pgw/sdks/sdk-goPython:
pip install mpac-pgw[http]2. Create a PaymentIntent
Go:
client := mpspgw.NewClient(mpspgw.ClientConfig{
BaseURL: "https://pgw.example.com",
APIKey: "ak_01JXXXXXXXXXXXXXXXXXXXXXX",
SecretKey: "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx",
})
intent, err := client.CreatePaymentIntent(ctx, mpspgw.CreatePaymentIntentRequest{
Amount: 1000,
Currency: "JPY",
PaymentMethodTypes: []string{"qr_mpm"},
MerchantRefID: "order_12345",
MerchantStoreID: "store_001",
})
// Pass intent.PaymentToken to the frontendPython:
from mpac_pgw import Client, ClientConfig, CreatePaymentIntentRequest
client = Client(ClientConfig(
base_url="https://pgw.example.com",
api_key="ak_01JXXXXXXXXXXXXXXXXXXXXXX",
secret_key="sk_live_xxxxxxxxxxxxxxxxxxxxxxxx",
))
intent = client.create_payment_intent(CreatePaymentIntentRequest(
amount=1000,
currency="JPY",
payment_method_types=["qr_mpm"],
merchant_ref_id="order_12345",
merchant_store_id="store_001",
))
# Pass intent.payment_token to the frontend3. Frontend Payment Confirmation
JavaScript/TypeScript:
import { MPACGateway } from '@cloudsky/mpac-pgw-sdk-js';
const mps = new MPACGateway({
publicKey: 'pk_test_xxx',
environment: 'sandbox',
});
// Load the PaymentIntent with the token from your backend
const intent = await mps.retrievePaymentIntent(paymentToken);
// Confirm QR payment
const qr = await mps.confirmQRPayment('PAYPAY');
// Render qr.qr_content as a QR code for the customer to scan
// Listen for completion
mps.on('payment_success', (data) => {
// Payment completed successfully
});
mps.on('payment_failed', (data) => {
// Payment failed
});
// Clean up when done
mps.destroy();4. Webhook Handling (Optional)
PGW sends webhooks for asynchronous payment events. Configure your webhook URL when setting up your organization.
Webhook payloads include the PaymentIntent ID and the updated status. Always verify the webhook signature before processing.
Payment Processing Modes
| Mode | Methods | Description |
|---|---|---|
pgw_processed | QR (MPM/CPM) | PGW processes the payment directly via provider APIs |
external | Credit Card, E-Money | PGW records the intent; processing happens via external apps |
manual | Cash | PGW records the intent; staff confirms payment received |
Merchant/Store Sync
Before processing payments, merchant and store data must be synced from the upstream system:
err := client.SyncMerchants(ctx, mpspgw.SyncMerchantsRequest{
Merchants: []mpspgw.SyncMerchant{
{
ID: "merchant_001",
Name: "Example Restaurant",
Stores: []mpspgw.SyncStore{
{
ID: "store_001",
Name: "Tokyo Branch",
PaymentProcessorConfigs: []mpspgw.PaymentProcessorConfig{
{
Provider: "PAYPAY",
IsActive: true,
Credentials: map[string]interface{}{
"merchant_id": "paypay_merchant_123",
},
},
},
},
},
},
},
})PaymentIntent Lifecycle
requires_payment_method --> requires_action --> processing --> succeeded
\--> failed
\--> cancelled| Status | Description |
|---|---|
requires_payment_method | Initial state after creation |
requires_action | Awaiting user action (e.g., 3DS challenge) |
processing | Payment is being processed by the provider |
succeeded | Payment completed successfully |
failed | Payment failed |
cancelled | Payment was cancelled |
Error Handling
All SDKs return structured errors with a code and message:
| Code | Description |
|---|---|
invalid_token | Payment token is invalid or expired |
invalid_request | Request validation failed |
provider_not_supported | QR provider is not yet supported |
provider_error | Error from the payment provider |
rate_limit_exceeded | Too many requests (1000/min per merchant) |
idempotency_conflict | Conflicting idempotent request |
Rate Limits
- Per merchant: 1,000 requests/minute
- Global: 10,000 requests/minute
- Rate limit headers are included in all responses