waffo / waffo-php
Official Waffo PHP SDK for payments, refunds, subscriptions, wallet inquiry, and webhooks.
Requires
- php: ^8.0
- ext-curl: *
- ext-json: *
- ext-openssl: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.87
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
This package is auto-updated.
Last update: 2026-08-27 13:59:56 UTC
README
English | 中文
Latest Release Highlights
- Node.js v2.5.0: Added
subscription().update()withamount,trialPeriodAmount, andscheduledAmounts, plus E2E coverage proving scheduled amounts take effect on the next billing period. - Java v2.0.0: Changed subscription
cashierLanguagerequest and response fields toString, while retaining and expandingCashierLanguageto the latest 22 convenience constants. - Go v1.4.0: Added subscription update support and openapi-aligned scheduled amount fields across create, change, update, and inquiry flows.
- Python v0.2.0b0: Added
subscription.update()and generated subscription models covering scheduled amount fields. - Shared reliability improvements: Auto-injected capture timestamps, expanded nested schema field validation, and kept SDK types aligned with
openapi.json.
Official Waffo PHP SDK for payments, refunds, subscriptions, configuration, wallet inquiry, and webhook verification.
Introduction
Waffo SDK is the official SDK collection for Waffo Payment Platform, providing one-stop global payment solutions for AI products, SaaS services, and more.
Core Features
- Global Payments: Support for credit cards, debit cards, e-wallets, virtual accounts, and more payment methods covering mainstream global payment channels
- Subscription Management: Complete subscription lifecycle management with trial periods, recurring billing, and subscription upgrades/downgrades
- Refund Processing: Flexible full/partial refund capabilities with refund status tracking
- Webhook Notifications: Real-time payment result push notifications for payments, refunds, subscription status changes, and more
- Security & Reliability: PCI DSS certified, RSA signature verification, enforced TLS 1.2+ encryption
Use Cases
| Scenario | Description |
|---|---|
| AI Products | ChatGPT-like applications, AI writing tools, AI image generation with usage-based billing or subscriptions |
| SaaS Services | Enterprise software subscriptions, online collaboration tools, cloud services with periodic payments |
| Content Platforms | Membership subscriptions, paid content, tipping scenarios |
Table of Contents
- Latest Release Highlights
- System Architecture
- Available SDKs
- Requirements
- Installation
- Quick Start
- Configuration
- API Usage
- Webhook Handling
- Payment Method Types
- Advanced Configuration
- Configuring DNS Cache TTL
- Handling New API Fields (ExtraParams)
- Error Handling
- Development
- SDK Development Guide
- Support
- License
System Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Waffo SDK │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Waffo (Entry Class) │ │
│ │ ┌────────────┐ ┌───────────────────┐ ┌──────────────┐ │ │
│ │ │ order() │ │ subscription() │ │ refund() │ │ │
│ │ └────────────┘ └───────────────────┘ └──────────────┘ │ │
│ │ ┌────────────────────┐ ┌───────────────────────────────┐ │ │
│ │ │ merchantConfig() │ │ payMethodConfig() │ │ │
│ │ └────────────────────┘ └───────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ WaffoHttpClient (Core) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ RSA Signing │ │ JSON │ │ Request/Response │ │ │
│ │ │ (SHA256) │ │Serialization│ │ Processing │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ HttpTransport (Transport Layer) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Default Implementation (Built-in HTTP Client) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Extensible: Custom HTTP Client Implementations │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
└────────────────────────────────────────│───────────────────────────┘
▼
┌────────────────────┐
│ Waffo API Server │
│ SANDBOX / PROD │
└────────────────────┘
Module Description
| Module | Description |
|---|---|
Waffo |
SDK main entry class, providing direct access to business resources |
WaffoHttpClient |
Core HTTP client handling signing, serialization, and communication |
HttpTransport |
Transport layer interface, supporting custom HTTP client implementations |
resources/* |
Business resource classes (orders, subscriptions, refunds, etc.) |
types/* |
Request/response type definitions |
utils/* |
Utility classes (RSA signing, webhook processing, etc.) |
Available SDKs
| Language | Package | Status | Documentation |
|---|---|---|---|
| Java | com.waffo:waffo-java | ✅ Stable | English / 中文 |
| Node.js/TypeScript | @waffo/waffo-node | ✅ Stable | English / 中文 |
| Go | waffo-go | ✅ Stable | English / 中文 |
| Python | waffo | ✅ Stable | English / 中文 |
| PHP | waffo/waffo-php | ✅ Stable | English / 中文 |
| Ruby | waffo-ruby | 🚧 Planned | - |
Note: For detailed installation instructions, code examples, and framework integration, please refer to each SDK's README documentation.
Requirements
Common Requirements
- API Key (from Waffo Dashboard)
- RSA Key Pair (merchant private key + Waffo public key)
- Merchant ID
Language-Specific Requirements
| Language | Version | Notes |
|---|---|---|
| Java | 8+ (8, 11, 17, 21, 25) | Maven 3.6+ or Gradle 6.0+ |
| Node.js | 18+ | npm, yarn, or pnpm |
| Python | 3.9+ | pip |
| Go | 1.20+ | go modules |
| PHP | 8.0+ | Composer |
Version Compatibility
Installation
PHP
Requires PHP 8.0 or newer. PHP 8.0 and 8.1 are SDK-compatible but no longer receive upstream PHP security fixes; use a currently supported PHP runtime in production.
composer require waffo/waffo-php
Quick Start
1. Initialize the SDK
<?php require __DIR__ . '/vendor/autoload.php'; use Waffo\Config\Environment; use Waffo\Config\WaffoConfig; use Waffo\Waffo; $waffo = new Waffo(new WaffoConfig( apiKey: 'your-api-key', privateKey: 'your-base64-private-key', waffoPublicKey: 'waffo-base64-public-key', environment: Environment::Sandbox, merchantId: 'your-merchant-id', ));
2. Create a Payment Order
<?php $response = $waffo->order()->create(['paymentRequestId' => bin2hex(random_bytes(16)), 'merchantOrderId' => 'ORDER-123', 'orderCurrency' => 'HKD', 'orderAmount' => '100.00', 'orderDescription' => 'Test order', 'notifyUrl' => 'https://example.com/webhooks/waffo', 'userInfo' => ['userId' => 'user-123'], 'paymentInfo' => ['productName' => 'ONE_TIME_PAYMENT']]); if ($response->isSuccess()) { var_dump($response->data); }
3. Query Order Status
<?php $response = $waffo->order()->inquiry(['paymentRequestId' => 'payment-request-id']); if ($response->isSuccess()) { var_dump($response->data); }
Configuration
Full Configuration Options
<?php use Waffo\Config\Environment; use Waffo\Config\WaffoConfig; $config = new WaffoConfig('api-key', 'private-key', 'public-key', Environment::Sandbox, 'merchant-id', 10_000, 30_000);
Environment Variables
All SDKs support configuration via environment variables:
# Set environment variables export WAFFO_API_KEY=your-api-key export WAFFO_PRIVATE_KEY=your-private-key export WAFFO_PUBLIC_KEY=waffo-public-key # Waffo public key export WAFFO_ENVIRONMENT=SANDBOX # Required: SANDBOX or PRODUCTION export WAFFO_MERCHANT_ID=your-merchant-id # Required
export WAFFO_API_KEY="your-api-key" export WAFFO_PRIVATE_KEY="your-base64-private-key" export WAFFO_PUBLIC_KEY="waffo-base64-public-key" export WAFFO_MERCHANT_ID="your-merchant-id" export WAFFO_ENVIRONMENT="sandbox"
Environment URLs
| Environment | Base URL | Description |
|---|---|---|
SANDBOX |
https://api-sandbox.waffo.com |
Test environment |
PRODUCTION |
https://api.waffo.com |
Production environment |
Important: Environment must be explicitly specified. SDKs do not default to any environment to prevent accidental requests to wrong environments.
Request-Level Configuration
<?php use Waffo\Config\RequestOptions; $response = $waffo->order()->inquiry(['paymentRequestId' => 'payment-request-id'], new RequestOptions(5_000, 15_000));
Framework Integration
PHP Framework Integration
All PHP frameworks must pass the unmodified request body and X-SIGNATURE header to WebhookHandler::handleWebhook(), then return HTTP 200 with the signed response body and X-SIGNATURE produced by the SDK.
Laravel Integration:
<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Waffo\Waffo; Route::post('/webhook', function (Request $request, Waffo $waffo) { $result = $waffo->webhook()->handleWebhook( $request->getContent(), (string) $request->header('X-SIGNATURE', ''), ); return response($result->responseBody, 200) ->header('Content-Type', 'application/json') ->header('X-SIGNATURE', $result->responseSignature); });
Symfony Integration:
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Waffo\Waffo; function waffoWebhook(Request $request, Waffo $waffo): Response { $result = $waffo->webhook()->handleWebhook( $request->getContent(), (string) $request->headers->get('X-SIGNATURE', ''), ); return new Response($result->responseBody, 200, [ 'Content-Type' => 'application/json', 'X-SIGNATURE' => $result->responseSignature, ]); }
Slim Integration:
<?php use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; $app->post('/webhook', function ( ServerRequestInterface $request, ResponseInterface $response, ) use ($waffo): ResponseInterface { $result = $waffo->webhook()->handleWebhook( (string) $request->getBody(), $request->getHeaderLine('X-SIGNATURE'), ); $response->getBody()->write($result->responseBody); return $response ->withStatus(200) ->withHeader('Content-Type', 'application/json') ->withHeader('X-SIGNATURE', $result->responseSignature); });
API Usage
Order Management
Create Order
<?php $response = $waffo->order()->create(['paymentRequestId' => 'request-1', 'merchantOrderId' => 'order-1', 'orderCurrency' => 'USD', 'orderAmount' => '10.00', 'orderDescription' => 'Example', 'notifyUrl' => 'https://example.com/webhooks/waffo', 'userInfo' => ['userId' => 'user-1'], 'paymentInfo' => ['productName' => 'ONE_TIME_PAYMENT']]); if ($response->isSuccess()) { var_dump($response->data); }
Query Order
<?php $response = $waffo->order()->inquiry(['acquiringOrderId' => 'A123']); if ($response->isSuccess()) { var_dump($response->data); }
Cancel Order
<?php $response = $waffo->order()->cancel(['paymentRequestId' => 'request-1']); if ($response->isSuccess()) { var_dump($response->data); }
Refund Order
<?php $response = $waffo->order()->refund(['refundRequestId' => 'refund-1', 'acquiringOrderId' => 'A123', 'refundAmount' => '10.00', 'refundReason' => 'Customer request']); if ($response->isSuccess()) { var_dump($response->data); }
Capture Order
Capture a pre-authorized payment (manual capture flow). The capture amount must not exceed the authorized amount.
<?php $response = $waffo->order()->capture(['paymentRequestId' => 'request-1', 'acquiringOrderId' => 'A123', 'captureAmount' => '10.00']); if ($response->isSuccess()) { var_dump($response->data); }
Subscription Management
Create Subscription
<?php $response = $waffo->subscription()->create(['subscriptionRequest' => 'sub-request-1', 'merchantSubscriptionId' => 'sub-1', 'currency' => 'USD', 'amount' => '9.99', 'notifyUrl' => 'https://example.com/webhooks/waffo', 'productInfo' => ['description' => 'Monthly plan', 'periodType' => 'MONTHLY', 'periodInterval' => '1'], 'userInfo' => ['userId' => 'user-1', 'userEmail' => 'user-1@example.com'], 'paymentInfo' => ['productName' => 'SUBSCRIPTION']]); if ($response->isSuccess()) { var_dump($response->data); }
Query Subscription
<?php $response = $waffo->subscription()->inquiry(['subscriptionRequest' => 'sub-request-1']); if ($response->isSuccess()) { var_dump($response->data); }
Cancel Subscription
<?php $response = $waffo->subscription()->cancel(['subscriptionId' => 'subscription-id']); if ($response->isSuccess()) { var_dump($response->data); }
Get Subscription Management URL
<?php $response = $waffo->subscription()->manage(['subscriptionId' => 'subscription-id']); if ($response->isSuccess()) { var_dump($response->data); }
Update Subscription
Update the billing amounts of an active subscription without re-authorization. Provide subscriptionRequest or subscriptionId, and at least one of amount or productInfo.scheduledAmounts. Changes take effect from the next billing period.
<?php $response = $waffo->subscription()->update(['subscriptionRequest' => 'sub-request-1', 'amount' => '8.99']); if ($response->isSuccess()) { var_dump($response->data); }
Subscription Change (Upgrade/Downgrade)
Change an existing subscription to a new plan (upgrade or downgrade).
Change Subscription
<?php $response = $waffo->subscription()->change(['subscriptionRequest' => 'new-sub-request', 'originSubscriptionRequest' => 'old-sub-request', 'remainingAmount' => '50.00', 'currency' => 'USD', 'notifyUrl' => 'https://example.com/webhooks/waffo', 'productInfoList' => [['description' => 'Annual plan', 'periodType' => 'MONTHLY', 'periodInterval' => '12', 'amount' => '99.00']], 'userInfo' => ['userId' => 'user-1', 'userEmail' => 'user-1@example.com'], 'goodsInfo' => ['goodsId' => 'annual', 'goodsName' => 'Annual plan'], 'paymentInfo' => ['productName' => 'SUBSCRIPTION']]); if ($response->isSuccess()) { var_dump($response->data); }
Subscription Change Status Values
| Status | Description |
|---|---|
IN_PROGRESS |
Change is being processed |
AUTHORIZATION_REQUIRED |
User needs to authorize the change (redirect to webUrl) |
SUCCESS |
Change completed successfully |
CLOSED |
Change was closed (timeout or failed) |
Query Subscription Change Status
<?php $response = $waffo->subscription()->changeInquiry(['subscriptionRequest' => 'new-sub-request', 'originSubscriptionRequest' => 'old-sub-request']); if ($response->isSuccess()) { var_dump($response->data); }
Refund Query
<?php $response = $waffo->refund()->inquiry(['refundRequestId' => 'refund-1']); if ($response->isSuccess()) { var_dump($response->data); }
Merchant Configuration
Query Merchant Configuration
<?php $response = $waffo->merchantConfig()->inquiry(['merchantId' => 'merchant-id']); if ($response->isSuccess()) { var_dump($response->data); }
Query Available Payment Methods
The payMethodDetails response includes the payment method category, supported currency, order amount limits, and refund policy. currentStatus can be absent when real-time availability is temporarily unavailable.
<?php $response = $waffo->payMethodConfig()->inquiry(['merchantId' => 'merchant-id']); if ($response->isSuccess()) { var_dump($response->data); }
Wallet Address Inquiry (x402)
Inquire the on-chain stablecoin receiving address for an x402 payment. The response returns the target network, the on-chain token contract, and the Waffo platform wallet address to send funds to. Phase 1 supports USDC.
<?php $response = $waffo->wallet()->inquiry(['paymentRequestId' => 'request-1', 'orderCurrency' => 'USDC']); if ($response->isSuccess()) { var_dump($response->data); }
Wallet Inquiry Response Fields
| Field | Description |
|---|---|
network |
CAIP-2 network id (Phase 1: eip155:8453, Base mainnet) |
asset |
On-chain token contract address for the network + currency |
payTo |
Waffo platform receiving wallet address (send funds here) |
Webhook Handling
Waffo pushes payment results, refund results, subscription status changes, and more via webhooks.
Webhook Handler Example
<?php $result = $waffo->webhook() ->onPayment(static function (array $event): void {}) ->onRefund(static function (array $event): void {}) ->onSubscriptionStatus(static function (array $event): void {}) ->onSubscriptionPeriodChanged(static function (array $event): void {}) ->onSubscriptionChange(static function (array $event): void {}) ->handleWebhook(file_get_contents('php://input'), $_SERVER['HTTP_X_SIGNATURE'] ?? null); header('X-SIGNATURE: ' . $result->responseSignature); echo $result->responseBody;
Webhook Notification Types
| Event Type | Handler Method | Description |
|---|---|---|
PAYMENT_NOTIFICATION |
onPayment() |
Payment result notification (triggered on every payment attempt, including retries) |
REFUND_NOTIFICATION |
onRefund() |
Refund result notification |
SUBSCRIPTION_STATUS_NOTIFICATION |
onSubscriptionStatus() |
Subscription status change notification (triggered when subscription main record status changes) |
SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION |
onSubscriptionPeriodChanged() |
Subscription period change notification (final result of each period) |
SUBSCRIPTION_CHANGE_NOTIFICATION |
onSubscriptionChange() |
Subscription change (upgrade/downgrade) result notification |
Subscription Notification Types Explained
| Notification Type | Trigger Condition | Scope | Includes Retry Events | Typical Use Case |
|---|---|---|---|---|
SUBSCRIPTION_STATUS_NOTIFICATION |
Subscription main record status changes | Subscription level | No | Track subscription lifecycle: first payment success activation (ACTIVE), cancellation (MERCHANT_CANCELLED, CHANNEL_CANCELLED), first payment failure close (CLOSE), etc. |
SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION |
Subscription period reaches final state | Period level | No (only final result) | Only need final result of each period, no intermediate retry events |
SUBSCRIPTION_CHANGE_NOTIFICATION |
Subscription change (upgrade/downgrade) completes | Change request level | No (only final result) | Track subscription change results: SUCCESS or CLOSED |
PAYMENT_NOTIFICATION |
Every payment order | Payment order level | Yes (includes all retries) | Need complete details of every payment attempt, including failure reasons, timestamps, retry details |
Selection Guide:
- If you only care about subscription activation/cancellation, use
SUBSCRIPTION_STATUS_NOTIFICATION- If you only care about final renewal result of each period, use
SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION- If you only care about subscription change (upgrade/downgrade) final result, use
SUBSCRIPTION_CHANGE_NOTIFICATION- If you need to track every payment attempt (including retries), use
PAYMENT_NOTIFICATION
Subscription Payment Note: Each period's payment (including first payment and renewals) triggers
PAYMENT_NOTIFICATIONevents. You can get subscription-related info (subscriptionId, period, etc.) fromsubscriptionInfo.
Subscription Change (Upgrade/Downgrade) Webhook Note: When a subscription change is processed, the following notifications are triggered:
SUBSCRIPTION_CHANGE_NOTIFICATION: When subscription change completes (SUCCESS or CLOSED)SUBSCRIPTION_STATUS_NOTIFICATION: When original subscription status changes toMERCHANT_CANCELLEDSUBSCRIPTION_STATUS_NOTIFICATION: When new subscription status changes toACTIVEPAYMENT_NOTIFICATION: If upgrade requires additional payment (price difference)
Webhook Notification Payload Examples
The following examples show the actual payload structure for each notification type.
PAYMENT_NOTIFICATION
{
"eventType": "PAYMENT_NOTIFICATION",
"result": {
"acquiringOrderId": "A2026xxxxxxxxxxxxxxxxxxxx",
"orderStatus": "PAY_SUCCESS",
"orderAmount": "109.00",
"orderCurrency": "USD",
"finalDealAmount": "109.00",
"userCurrency": "USD",
"orderDescription": "Sample Product",
"orderRequestedAt": "2026-02-28T10:05:58.000Z",
"orderCompletedAt": "2026-02-28T10:06:10.000Z",
"orderUpdatedAt": "2026-02-28T10:06:10.000Z",
"refundExpiryAt": "2026-08-26T23:59:59.999Z",
"userInfo": {
"userId": "user@example.com",
"userEmail": "user@example.com"
},
"merchantInfo": {
"merchantId": "YOUR_MERCHANT_ID"
},
"goodsInfo": {
"goodsId": "goods_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"goodsName": "Sample Product"
},
"addressInfo": {},
"paymentInfo": {
"productName": "SUBSCRIPTION",
"payMethodType": "CREDITCARD",
"payMethodName": "CC_VISA",
"payMethodProperties": "{\"cardToken\":\"CARD_TOKEN_XXXXXXXXXXXXXXXXXXXX\",\"cardTransactionType\":\"CIT\"}",
"payMethodResponse": "{\"maskCardData\":\"XXXX42****XX4242\"}"
},
"subscriptionInfo": {
"subscriptionId": "SC2026xxxxxxxxxxxxxxxxxxxxxxxxxx",
"subscriptionRequest": "sub_req_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"merchantRequest": "sub_req_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"period": "1"
},
"cancelRedirectUrl": "https://YOUR_DOMAIN/checkout/YOUR_CHECKOUT_ID"
}
}
For x402 stablecoin payments, PAYMENT_NOTIFICATION.result also includes x402Info with x402Request, paymentSignatureHeader, and x402Response.
SUBSCRIPTION_STATUS_NOTIFICATION / SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION
These two notification types share the same payload structure:
{
"eventType": "SUBSCRIPTION_STATUS_NOTIFICATION",
"result": {
"subscriptionId": "SC2026xxxxxxxxxxxxxxxxxxxxxxxxxx",
"subscriptionRequest": "sub_req_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"merchantSubscriptionId": "sub_req_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"subscriptionStatus": "ACTIVE",
"currency": "USD",
"userCurrency": "USD",
"amount": "109.00",
"requestedAt": "2026-02-28T10:05:58.000Z",
"updatedAt": "2026-02-28T10:06:10.000Z",
"userInfo": {
"userId": "user@example.com",
"userEmail": "user@example.com"
},
"merchantInfo": {
"merchantId": "YOUR_MERCHANT_ID"
},
"goodsInfo": {
"goodsId": "goods_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"goodsName": "Sample Product"
},
"productInfo": {
"periodType": "MONTHLY",
"periodInterval": "1",
"currentPeriod": "1",
"startDateTime": "2026-02-28T10:06:10.000Z",
"nextPaymentDateTime": "2026-03-28T10:06:10.000Z",
"description": "Sample Product"
},
"paymentInfo": {
"productName": "SUBSCRIPTION",
"payMethodType": "CREDITCARD",
"payMethodName": "CC_VISA",
"payMethodProperties": "{}"
},
"paymentDetails": [
{
"period": "1",
"acquiringOrderId": "A2026xxxxxxxxxxxxxxxxxxxx",
"orderAmount": "109.00",
"orderCurrency": "USD",
"orderStatus": "PAY_SUCCESS",
"orderUpdatedAt": "2026-02-28T10:06:10.000Z"
}
]
}
}
Payment Method Types
payMethodType Reference
| Type | Description | Example payMethodName |
|---|---|---|
CREDITCARD |
Credit Card | CC_VISA, CC_MASTERCARD, CC_AMEX, CC_JCB, etc. |
DEBITCARD |
Debit Card | DC_VISA, DC_MASTERCARD, DC_ELO, etc. |
EWALLET |
E-Wallet | GCASH, DANA, PROMPTPAY, GRABPAY, etc. |
VA |
Virtual Account | BCA, BNI, BRI, MANDIRI, etc. |
APPLEPAY |
Apple Pay | APPLEPAY |
GOOGLEPAY |
Google Pay | GOOGLEPAY |
Usage Examples
Specify a type and let the user choose on the checkout page:
{"paymentInfo": {"payMethodType": "CREDITCARD"}}
Specify an exact payment method:
{"paymentInfo": {"payMethodType": "CREDITCARD", "payMethodName": "CC_VISA"}}
Combine multiple types:
{"paymentInfo": {"payMethodType": "CREDITCARD,DEBITCARD"}}
Select an e-wallet channel:
{"paymentInfo": {"payMethodType": "EWALLET", "payMethodName": "GCASH"}}
Note: For available
ProductName,PayMethodType,PayMethodNamevalues, merchants can log in to Waffo Portal to view contracted payment methods (Home → Service → Pay-in).
Advanced Configuration
Custom HTTP Transport
SDKs use built-in HTTP clients by default. For connection pooling or advanced features, you can implement custom transport.
<?php use Waffo\Http\HttpRequest; use Waffo\Http\HttpResponse; use Waffo\Http\HttpTransport; final class MerchantTransport implements HttpTransport { public function send(HttpRequest $request): HttpResponse { return new HttpResponse(200, [], '{"code":"0","data":{}}'); } }
Security Note: When implementing custom HTTP transport, ensure TLS 1.2 or higher is enforced. See "TLS Security Configuration" section below.
TLS Security Configuration
SDKs enforce TLS 1.2 or higher by default for all HTTPS communication, complying with PCI DSS security standards.
Default Transport
Default HTTP transport implementations automatically configure TLS 1.2+:
- All HTTPS connections only allow TLS 1.2 and TLS 1.3 protocols
- Insecure legacy protocols (SSLv3, TLS 1.0, TLS 1.1) are automatically disabled
- No additional configuration needed, works out of the box
Custom Transport TLS Configuration
If using custom HTTP transport, ensure TLS 1.2+ is configured:
Debug Logging
Enable debug logging to troubleshoot issues during development.
Timeout Configuration Recommendations
| Operation Type | Connect Timeout | Read Timeout | Notes |
|---|---|---|---|
| Create Order | 5s | 30s | Recommended |
| Create Subscription | 5s | 30s | Recommended |
| Refund Operation | 5s | 30s | Recommended |
| Query Operations | 5s | 15s | Can be shorter |
Connection Pool Recommendations
| Scenario | Max Connections | Max Per Route | Notes |
|---|---|---|---|
| Low Traffic (< 10 QPS) | 20 | 10 | Default config sufficient |
| Medium Traffic (10-100 QPS) | 50 | 20 | Consider using OkHttp |
| High Traffic (> 100 QPS) | 100-200 | 50 | Consider Apache HttpClient |
Instance Reuse
SDK instances are thread-safe. Recommended to use as singleton in your application:
<?php $waffo = Waffo\Waffo::fromEnvironment(); $order = $waffo->order()->inquiry(['paymentRequestId' => 'request-1']); $refund = $waffo->refund()->inquiry(['refundRequestId' => 'refund-1']);
Configuring DNS Cache TTL
We cannot guarantee that the IP address of the Waffo API will be static. To ensure high availability, Waffo employs a multi-gateway architecture with automatic failover — if one gateway provider experiences an outage, DNS records are updated to route traffic to a healthy gateway. Ensure that your DNS cache configuration does not cache DNS entries for too long — otherwise, during a failover event, your application will continue sending requests to the unavailable gateway until the cache expires or your process restarts.
Recommended TTL: 60 seconds or less.
Infrastructure / Gateway Layer
If your requests to Waffo API go through a reverse proxy or load balancer, check its DNS caching behavior as well — this is configured independently from your application code:
-
Nginx: By default, Nginx resolves DNS only at startup and caches the result forever. You must explicitly configure a
resolverwith a TTL:resolver 8.8.8.8 valid=60s; set $backend "api.waffo.com"; proxy_pass https://$backend;
Note: Using a variable in
proxy_passis required to trigger re-resolution; a hardcoded hostname is resolved only once. -
AWS ALB / Cloud Load Balancers: Most cloud load balancers handle DNS re-resolution automatically. Verify your provider's documentation to confirm.
-
CDN / API Gateways: If you route traffic through Cloudflare, AWS API Gateway, or similar services, ensure their DNS TTL settings align with your requirements.
Handling New API Fields (ExtraParams)
When Waffo API adds new fields that are not yet defined in the SDK, you can use the ExtraParams feature to access these fields without waiting for an SDK update.
Reading Unknown Fields from Responses
<?php $response = $waffo->order()->inquiry(['paymentRequestId' => 'request-1']); $futureField = $response->data?->extraFields()['futureField'] ?? null;
Sending Extra Fields in Requests
<?php $response = $waffo->order()->create(['paymentRequestId' => 'request-1', 'merchantOrderId' => 'order-1', 'orderCurrency' => 'USD', 'orderAmount' => '10.00', 'orderDescription' => 'Example', 'notifyUrl' => 'https://example.com/webhooks/waffo', 'userInfo' => ['userId' => 'user-1'], 'paymentInfo' => ['productName' => 'ONE_TIME_PAYMENT'], 'futureField' => 'forward-compatible']); if ($response->isSuccess()) { var_dump($response->data); }
Important Notes
Upgrade SDK Promptly
ExtraParams is designed as a temporary solution for accessing new API fields before SDK updates.
Best Practices:
- Check SDK release notes regularly for new field support
- Once SDK officially supports the field, migrate from
getExtraParam("field")to the official getter (e.g.,getField())- The SDK logs a warning when you use
getExtraParam()on officially supported fieldsWhy migrate?
- Official getters provide type safety
- Better IDE auto-completion and documentation
- Reduced risk of typos in field names
Error Handling
Error Handling Pattern
SDKs use a hybrid error handling approach:
- Business errors: Returned via
ApiResponse, check withresponse.isSuccess() - Unknown status exceptions: Only for write operations (may affect funds or status), network timeout or server returning E0001 error code throws
WaffoUnknownStatusError(or language-specific equivalent)
Methods That Throw Unknown Status Exception
Only these methods that may affect funds or status throw WaffoUnknownStatusError:
| Method | Description |
|---|---|
order().create() |
Create order, may initiate payment |
order().refund() |
Refund, may cause fund changes |
order().cancel() |
Cancel order, affects order status |
subscription().create() |
Create subscription, may cause initial charge |
subscription().cancel() |
Cancel subscription, affects subscription status |
subscription().change() |
Change subscription, may cause charge for upgrade |
Query methods do not throw this exception (e.g., inquiry()), because query operations can be safely retried without affecting funds or status.
WaffoUnknownStatusError Handling
⚠️ IMPORTANT WARNING
When
WaffoUnknownStatusErroris caught, it means the operation result is uncertain.DO NOT directly close the order or assume payment failed! The user may have already completed payment.
Correct handling:
- Call
waffo.order().inquiry()to query actual order status- Or wait for Waffo webhook callback notification
- Use Waffo's returned order status as the final authority
<?php use Waffo\Exception\WaffoUnknownStatusError; try { $response = $waffo->order()->create($params); } catch (WaffoUnknownStatusError $error) { $response = $waffo->order()->inquiry(['paymentRequestId' => $params['paymentRequestId']]); }
WaffoUnknownStatusError Trigger Scenarios
| Scenario | Description |
|---|---|
| Network Timeout | Request timeout, cannot determine if server received request |
| Connection Failed | Network connection failed, cannot determine server status |
| E0001 Error Code | Server returned E0001, indicating processing status unknown |
Error Code Classification
Error codes are classified by first letter:
| Prefix | Category | Description |
|---|---|---|
| S | SDK Internal Error | SDK client internal error such as network timeout, signing failure, etc. |
| A | Merchant Related | Parameter, signature, permission, contract issues on merchant side |
| B | User Related | User status, balance, authorization issues |
| C | System Related | Waffo system or payment channel issues |
| D | Risk Related | Risk control rejection |
| E | Unknown Status | Server returned unknown status |
Complete Error Code Table
SDK Internal Errors (Sxxxx)
| Code | Description | Exception Type | Handling Suggestion |
|---|---|---|---|
S0001 |
Network Error | WaffoUnknownStatusError |
Status unknown, need to query order to confirm |
S0002 |
Invalid Public Key | WaffoError |
Check if public key is valid Base64 encoded X509 format |
S0003 |
RSA Signing Failed | WaffoError |
Check if private key format is correct |
S0004 |
Response Signature Verification Failed | ApiResponse.error() |
Check Waffo public key config, contact Waffo |
S0005 |
Request Serialization Failed | ApiResponse.error() |
Check request parameter format |
S0006 |
SDK Unknown Error | ApiResponse.error() |
Check logs, contact technical support |
S0007 |
Invalid Private Key | WaffoError |
Check if private key is valid Base64 encoded PKCS8 format |
Important:
S0001andE0001(returned by server) indicate unknown status. Do not close order directly! Should call query API or wait for webhook to confirm actual status.
Merchant Related Errors (Axxxxx)
| Code | Description | HTTP Status |
|---|---|---|
0 |
Success | 200 |
A0001 |
Invalid API Key | 401 |
A0002 |
Invalid Signature | 401 |
A0003 |
Parameter Validation Failed | 400 |
A0004 |
Insufficient Permission | 401 |
A0005 |
Merchant Limit Exceeded | 400 |
A0006 |
Merchant Status Abnormal | 400 |
A0007 |
Unsupported Transaction Currency | 400 |
A0008 |
Transaction Amount Exceeded | 400 |
A0009 |
Order Not Found | 400 |
A0010 |
Merchant Contract Does Not Allow This Operation | 400 |
A0011 |
Idempotent Parameter Mismatch | 400 |
A0012 |
Merchant Account Insufficient Balance | 400 |
A0013 |
Order Already Paid, Cannot Cancel | 400 |
A0014 |
Refund Rules Do Not Allow Refund | 400 |
A0015 |
Payment Channel Does Not Support Cancel | 400 |
A0016 |
Payment Channel Rejected Cancel | 400 |
A0017 |
Payment Channel Does Not Support Refund | 400 |
A0018 |
Payment Method Does Not Match Merchant Contract | 400 |
A0019 |
Cannot Refund Due to Chargeback Dispute | 400 |
A0020 |
Payment Amount Exceeds Single Transaction Limit | 400 |
A0021 |
Cumulative Payment Amount Exceeds Daily Limit | 400 |
A0022 |
Multiple Products Exist, Need to Specify Product Name | 400 |
A0023 |
Token Expired, Cannot Create Order | 400 |
A0024 |
Exchange Rate Expired, Cannot Process Order | 400 |
A0026 |
Unsupported Checkout Language | 400 |
A0027 |
Refund Count Reached Limit (50 times) | 400 |
A0029 |
Invalid Card Data Provided by Merchant | 400 |
A0030 |
Card BIN Not Found | 400 |
A0031 |
Unsupported Card Scheme or Card Type | 400 |
A0032 |
Invalid Payment Token Data | 400 |
A0033 |
Multiple Payment Methods with Same Name, Need to Specify Country | 400 |
A0034 |
Order Expiry Time Provided by Merchant Has Passed | 400 |
A0035 |
Current Order Does Not Support Capture Operation | 400 |
A0036 |
Current Order Status Does Not Allow Capture Operation | 400 |
A0037 |
User Payment Token Invalid or Expired | 400 |
A0038 |
MIT Transaction Requires Verified User Payment Token | 400 |
A0039 |
Order Already Refunded by Chargeback Prevention Service | 400 |
A0040 |
Order Cannot Be Created Concurrently | 400 |
A0045 |
MIT Transaction Cannot Process, tokenId Status Unverified | 400 |
User Related Errors (Bxxxxx)
| Code | Description | HTTP Status |
|---|---|---|
B0001 |
User Status Abnormal | 400 |
B0002 |
User Limit Exceeded | 400 |
B0003 |
User Insufficient Balance | 400 |
B0004 |
User Did Not Pay Within Timeout | 400 |
B0005 |
User Authorization Failed | 400 |
B0006 |
Invalid Phone Number | 400 |
B0007 |
Invalid Email Format | 400 |
System Related Errors (Cxxxxx)
| Code | Description | HTTP Status |
|---|---|---|
C0001 |
System Error | 500 |
C0002 |
Merchant Contract Invalid | 500 |
C0003 |
Order Status Invalid, Cannot Continue Processing | 500 |
C0004 |
Order Information Mismatch | 500 |
C0005 |
Payment Channel Rejected | 503 |
C0006 |
Payment Channel Error | 503 |
C0007 |
Payment Channel Under Maintenance | 503 |
Risk Related Errors (Dxxxxx)
| Code | Description | HTTP Status |
|---|---|---|
D0001 |
Risk Control Rejected | 406 |
Unknown Status Errors (Exxxxx)
| Code | Description | HTTP Status |
|---|---|---|
E0001 |
Unknown Status (Need to query or wait for callback) | 500 |
Note: When receiving
E0001error code, it indicates transaction status is unknown. Do not close order directly, should call query API to confirm actual status, or wait for webhook callback notification.
Development
Repository Structure
waffo-sdk/
├── openapi.json # API specification (single source of truth)
├── sdk-spec/ # Shared specifications for all SDKs
│ ├── RSA_SIGNING.md # RSA signing specification
│ ├── HTTP_CLIENT.md # HTTP client specification
│ ├── WEBHOOK_HANDLER.md # Webhook handling specification
│ ├── ERROR_HANDLING.md # Error handling specification
│ ├── NEW_LANGUAGE_GUIDE.md # Guide for adding new language SDK
│ ├── AGENT_INSTRUCTIONS.md # AI agent instructions
│ └── test-vectors/ # Cross-language test vectors
├── packages/
│ ├── waffo-node/ # Node.js SDK
│ ├── waffo-python/ # Python SDK
│ └── ...
├── scripts/
│ ├── generate-types.sh # Type generation script
│ └── run-cross-lang-tests.sh
└── .github/workflows/
Prerequisites
- Node.js 18+
- pnpm (for workspace management)
Setup
# Install dependencies pnpm install # Generate types for all SDKs ./scripts/generate-types.sh # Run all tests ./scripts/run-cross-lang-tests.sh
Generate RSA Key Pair
All SDKs provide utility methods to generate RSA key pairs:
<?php $keys = Waffo\Waffo::generateKeyPair(); echo $keys['privateKey'] . PHP_EOL; echo $keys['publicKey'] . PHP_EOL;
API Specification
All SDKs are generated from openapi.json. When the API changes:
- Update
openapi.json - Run
./scripts/generate-types.sh - Run tests for all SDKs
- Release new versions
SDK Development Guide
This section provides guidelines for developing new language SDKs or maintaining existing ones.
Documentation Synchronization Rules
IMPORTANT: All language-specific SDK READMEs MUST be kept in sync with this monorepo README.
When this README is updated, the changes should be automatically synchronized to all language SDKs:
| Source | Targets |
|---|---|
waffo-sdk/README.md |
packages/waffo-node/README.md, waffo-java/README.md, etc. |
Synchronization Workflow:
-
Single Source of Truth: This monorepo README serves as the master documentation
-
Language Adaptation: Each SDK README adapts the content for its specific language (code examples, installation, framework integration)
-
Required Sections: All SDKs MUST include the following sections from this README:
- Introduction & Core Features
- System Architecture
- Configuration (adapted for language)
- API Usage (all endpoints with language-specific examples)
- Webhook Handling
- Payment Method Types
- Error Handling (complete error code table)
- Support & License
-
Update Process:
# When monorepo README changes, update all SDK READMEs ./scripts/sync-readme.sh -
CI/CD Validation: PR checks should verify README consistency across SDKs
What to Synchronize:
- Error code tables (MUST be identical)
- Payment method types table
- Webhook notification types
- API endpoint descriptions
- Configuration options
- Security requirements (TLS, etc.)
What to Adapt:
- Code examples (language-specific syntax)
- Installation instructions
- Framework integration examples
- Language-specific best practices
README Structure Guidelines
Each language-specific SDK README should include:
-
Header Section
- Badges (version, license, build status, language version)
- Language toggle (if multi-language)
- Brief introduction
-
Core Sections (in order)
- Introduction with core features
- Use cases table
- Table of contents
- System architecture diagram
- Requirements
- Installation
- Quick start (Initialize, Create Order, Query Order)
- Configuration (full options, env vars, framework integration)
- API Usage (all resources with examples)
- Webhook Handling
- Payment Method Types
- Advanced Configuration
- Error Handling (with complete error code table)
- Development & Testing
- Support
- License
-
Code Examples
- Use actual working code, not pseudocode
- Include imports
- Show both success and error handling
- Add comments for important steps
Implementation Requirements
See sdk-spec/ directory for detailed specifications:
RSA_SIGNING.md- RSA signature implementationHTTP_CLIENT.md- HTTP client requirementsWEBHOOK_HANDLER.md- Webhook processingERROR_HANDLING.md- Error handling patternsNEW_LANGUAGE_GUIDE.md- Complete guide for new SDKs
Adding a New Language
See sdk-spec/NEW_LANGUAGE_GUIDE.md for detailed instructions.
Support
- Documentation: Waffo Developer Docs
- Issues: GitHub Issues
- Technical Support: merchant.support@waffo.com
License
MIT License - See LICENSE file for details.