Payment Proxy API Documentation

Version 1.1.0 | Last Updated: November 2025

Payment Proxy is a Laravel-based payment gateway proxy service that enables multiple webstores to use a single Duitku merchant account. The service handles payment creation, status tracking, and automatic callback forwarding with retry mechanisms.

Key Features

Single Account

One Duitku account for multiple webstores

Auto Order ID

Auto-generated merchant order ID with structured format

API Key Auth

Secure API key authentication for each webstore

Auto Retry

Automatic callback forwarding with 3x retry mechanism

Authentication

All protected API endpoints require authentication using an API key.

API Key Format

Format
EP-{16 random uppercase characters}

Example:

EP-BBZZD1PC5OCRBPTE

How to Authenticate

Include the API key in the request header:

Header
X-API-Key: YOUR_API_KEY_HERE

Endpoint URL

Production
https://payment.elfadigital.my.id

Create Payment

POST /api/payment/create

Creates a new payment request through Duitku payment gateway. This endpoint validates the request, generates a unique merchant order ID, and returns a payment URL for the customer.

Request Headers

Content-Type: application/json
X-API-Key: YOUR_API_KEY

Request Body Parameters

Field Type Required Description
webstoreOrderId string Yes Unique order ID from your webstore
paymentAmount integer Yes Payment amount in IDR (minimum: 1)
productDetails string Yes Description of the product/service
email string Yes Customer email address
phoneNumber string Yes Customer phone number
itemDetails array Yes Array of items with name, price, quantity
customerDetail object Yes Customer information object
returnUrl string Yes URL to redirect after payment
expiryPeriod integer No Payment expiry in minutes (5,10,15,30,60). Default: 15

Example Request

cURL
curl -X POST https://payment.elfadigital.my.id/api/payment/create \
  -H "Content-Type: application/json" \
  -H "X-API-Key: EP-BBZZD1PC5OCRBPTE" \
  -d '{
    "webstoreOrderId": "WS-2025-001",
    "paymentAmount": 250000,
    "productDetails": "Premium Package - 1 Month Subscription",
    "email": "customer@example.com",
    "phoneNumber": "081234567890",
    "returnUrl": "https://mywebstore.com/payment/success",
    "expiryPeriod": 30,
    "itemDetails": [
      {
        "name": "Premium Package",
        "price": 250000,
        "quantity": 1
      }
    ],
    "customerDetail": {
      "firstName": "Budi",
      "lastName": "Santoso",
      "email": "customer@example.com",
      "phoneNumber": "081234567890",
      "billingAddress": {
        "firstName": "Budi",
        "lastName": "Santoso",
        "address": "Jl. Sudirman No. 45",
        "city": "Jakarta",
        "postalCode": "12190",
        "phone": "081234567890",
        "countryCode": "ID"
      },
      "shippingAddress": {
        "firstName": "Budi",
        "lastName": "Santoso",
        "address": "Jl. Sudirman No. 45",
        "city": "Jakarta",
        "postalCode": "12190",
        "phone": "081234567890",
        "countryCode": "ID"
      }
    }
  }'

Success Response (201 Created)

JSON
{
  "success": true,
  "message": "Payment created successfully",
  "data": {
    "reference": "DK2025112200001",
    "payment_url": "https://app-sandbox.duitku.com/payment/DK2025112200001",
    "merchant_order_id": "ELFA-22112025-0001",
    "webstore_order_id": "WS-2025-001"
  }
}

Check Payment Status

GET /api/payment/status/{webstoreOrderId}

Retrieves the current payment status for a specific transaction using the webstore order ID.

Example Request

cURL
curl -X GET https://payment.elfadigital.my.id/api/payment/status/WS-2025-001 \
  -H "X-API-Key: EP-BBZZD1PC5OCRBPTE"

Transaction Status Values

Status Description
pending Payment is awaiting customer action
success Payment completed successfully
failed Payment failed
expired Payment link expired

Success Response

JSON
{
  "success": true,
  "data": {
    "webstore_order_id": "WS-2025-001",
    "merchant_order_id": "ELFA-22112025-0001",
    "status": "success",
    "payment_amount": 250000,
    "duitku_status": {
      "merchantCode": "DS12345",
      "amount": "250000",
      "merchantOrderId": "ELFA-22112025-0001",
      "statusCode": "00",
      "statusMessage": "SUCCESS"
    }
  }
}

Duitku Callback

POST /api/callback/duitku
Important

This endpoint is called by Duitku, not by your application. Duitku sends data as application/x-www-form-urlencoded, NOT JSON.

Duitku Result Codes

Result Code Status Description
00 success Payment successful
01 failed Payment failed
02 expired Payment expired

Callback Data Forwarded to Webstore

Field Type Description
webstore_order_id string Your original order ID
merchant_order_id string Proxy-generated order ID (ELFA-xxx)
payment_amount integer Amount in IDR
status string success/failed/expired/pending
result_code string Duitku result code (00/01/02)
duitku_reference string Duitku reference number
payment_method string Payment method code (BCA, BNI, etc)

Payment Flow

1

Webstore → Proxy

Webstore calls POST /api/payment/create with order details

2

Proxy → Duitku

Proxy generates unique merchant order ID and requests invoice from Duitku

3

Proxy → Webstore

Proxy returns payment URL to webstore

4

Customer → Duitku

Customer redirected to payment URL and completes payment

5

Duitku → Proxy → Webstore

Duitku sends callback to proxy, proxy validates and forwards to webstore

Callback Mechanism

Retry Strategy

When forwarding callbacks to webstore endpoints, the proxy implements an automatic retry mechanism:

Attempt Timing Description
1 Immediate First attempt right after receiving callback
2 After 2 seconds If first attempt fails
3 After 5 seconds If second attempt fails
4 After 10 seconds If third attempt fails
Timeout

Each attempt has a 10-second timeout. Your callback endpoint must respond within this time.

Webstore Callback Handler

Requirements
  • Accept application/x-www-form-urlencoded POST data
  • Return HTTP 200 status code for success
  • Respond within 10 seconds
  • Be idempotent (handle duplicate callbacks)
  • Disable CSRF protection for this endpoint
  • Use HTTPS in production

PHP/Laravel Example

PHP
public function handlePaymentCallback(Request $request)
{
    Log::info('Payment callback received', $request->all());
    
    $order = Order::where('id', $request->webstore_order_id)->first();
    
    if (!$order) {
        return response()->json(['success' => false], 404);
    }
    
    // Idempotency check
    if ($order->payment_status === 'success') {
        return response()->json(['success' => true, 'message' => 'Already processed']);
    }
    
    $order->update([
        'payment_status' => $request->status,
        'payment_reference' => $request->duitku_reference,
        'payment_method' => $request->payment_method,
        'paid_at' => $request->status === 'success' ? now() : null,
    ]);
    
    if ($request->status === 'success') {
        SendOrderConfirmationEmail::dispatch($order);
    }
    
    return response()->json(['success' => true], 200);
}

Node.js/Express Example

JavaScript
const express = require('express');
const app = express();

app.use(express.urlencoded({ extended: true }));

app.post('/callback/payment', async (req, res) => {
    const { webstore_order_id, status, duitku_reference } = req.body;
    
    const order = await Order.findOne({ _id: webstore_order_id });
    
    if (!order) {
        return res.status(404).json({ success: false });
    }
    
    if (order.payment_status === 'success') {
        return res.json({ success: true, message: 'Already processed' });
    }
    
    order.payment_status = status;
    order.payment_reference = duitku_reference;
    if (status === 'success') {
        order.paid_at = new Date();
    }
    await order.save();
    
    res.json({ success: true });
});

Python/Django Example

Python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
@require_POST
def payment_callback(request):
    webstore_order_id = request.POST.get('webstore_order_id')
    status = request.POST.get('status')
    
    try:
        order = Order.objects.get(id=webstore_order_id)
    except Order.DoesNotExist:
        return JsonResponse({'success': False}, status=404)
    
    if order.payment_status == 'success':
        return JsonResponse({'success': True, 'message': 'Already processed'})
    
    order.payment_status = status
    if status == 'success':
        order.paid_at = timezone.now()
    order.save()
    
    return JsonResponse({'success': True})

Error Handling

HTTP Status Codes

Status Meaning When It Occurs
200 OK Request successful
201 Created Payment created successfully
401 Unauthorized Missing or invalid API key
404 Not Found Transaction not found
409 Conflict Duplicate webstore order ID
422 Unprocessable Entity Validation error
500 Internal Server Error Server error or Duitku API error

Error Response Format

JSON
{
  "success": false,
  "message": "Error description",
  "errors": {
    "field_name": ["Error message"]
  }
}

Security Considerations

API Key Management

  • Never expose API keys in client-side code
  • Store API keys in environment variables
  • Rotate API keys periodically

HTTPS Requirement

  • Always use HTTPS in production
  • Ensure SSL certificates are valid
  • Configure callback URLs with HTTPS

Signature Validation

The proxy validates Duitku callbacks using MD5 signature:

MD5(merchantCode + amount + merchantOrderId + apiKey)

Data Validation

  • Validate all input data before processing
  • Check amount ranges
  • Sanitize string inputs

Testing

Test Create Payment

cURL
curl -X POST https://payment.elfadigital.my.id/api/payment/create \
  -H "Content-Type: application/json" \
  -H "X-API-Key: EP-BBZZD1PC5OCRBPTE" \
  -d '{
    "webstoreOrderId": "TEST-001",
    "paymentAmount": 50000,
    "productDetails": "Test Product",
    "email": "test@example.com",
    "phoneNumber": "081234567890",
    "returnUrl": "https://webstore.com/return",
    "expiryPeriod": 15,
    "itemDetails": [{"name": "Test Product", "price": 50000, "quantity": 1}],
    "customerDetail": {
      "firstName": "Test",
      "lastName": "User",
      "email": "test@example.com",
      "phoneNumber": "081234567890",
      "billingAddress": {...},
      "shippingAddress": {...}
    }
  }'

Test Check Status

cURL
curl -X GET https://payment.elfadigital.my.id/api/payment/status/TEST-001 \
  -H "X-API-Key: EP-BBZZD1PC5OCRBPTE"

Test Callback Endpoint

cURL
curl -X POST https://yourwebstore.com/callback/payment \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "webstore_order_id=TEST-001" \
  -d "merchant_order_id=ELFA-22112025-0001" \
  -d "payment_amount=100000" \
  -d "status=success" \
  -d "result_code=00" \
  -d "duitku_reference=DK2025110001" \
  -d "payment_method=BCA"