Apa itu Tripay Payment Gateway?
Tripay adalah solusi pembayaran digital Indonesia yang memungkinkan bisnis online menerima pembayaran melalui berbagai metode. Keunggulan Tripay adalah single API untuk semua channel pembayaran, dokumentasi lengkap, dan biaya transaksi yang kompetitif.
Channel pembayaran yang tersedia: Virtual Account (BCA, BRI, BNI, Mandiri, Permata), QRIS (support semua e-wallet), Indomaret, Alfamart, dan masih banyak lagi.
Persiapan Sebelum Integrasi
Sebelum memulai integrasi, pastikan Anda sudah memiliki:
- Akun merchant Tripay (daftar di tripay.co.id)
- API Key dan Private Key dari dashboard merchant Tripay
- Merchant Code
- Laravel versi 8 atau lebih baru
- Pemahaman dasar tentang HTTP Client Laravel (Guzzle)
Step 1: Konfigurasi Environment Variables
Tambahkan kredensial Tripay Anda ke file .env. Buka file .env dan tambahkan baris berikut:
TRIPAY_API_KEY=your-api-key-here
TRIPAY_PRIVATE_KEY=your-private-key-here
TRIPAY_MERCHANT_CODE=T0001
TRIPAY_MODE=sandbox
Catatan: Ganti sandbox menjadi production ketika sudah siap go-live. Untuk development, gunakan mode sandbox terlebih dahulu.
Step 2: Membuat Service Class untuk Tripay
Untuk menjaga kode tetap terorganisir, kita akan membuat service class khusus untuk handling Tripay yang langsung berkomunikasi dengan API endpoint. Buat file baru di app/Services/TripayService.php:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class TripayService
{
private $apiKey;
private $privateKey;
private $merchantCode;
private $baseUrl;
public function __construct()
{
$this->apiKey = config('services.tripay.api_key');
$this->privateKey = config('services.tripay.private_key');
$this->merchantCode = config('services.tripay.merchant_code');
// Set base URL based on mode
$mode = config('services.tripay.mode', 'production');
$this->baseUrl = $mode === 'sandbox'
? 'https://tripay.co.id/api-sandbox'
: 'https://tripay.co.id/api';
}
/**
* Get available payment channels
*/
public function getPaymentChannels()
{
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/merchant/payment-channel');
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Get Channels Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Get payment instructions for specific channel
*/
public function getPaymentInstructions($code, $amount = null, $allowHtml = false)
{
try {
$params = [
'code' => $code,
'amount' => $amount,
'allow_html' => $allowHtml ? 1 : 0,
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/payment/instruction', array_filter($params));
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Get Instructions Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Calculate fee for specific amount and payment method
*/
public function calculateFee($code, $amount)
{
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/merchant/fee-calculator', [
'code' => $code,
'amount' => $amount,
]);
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Calculate Fee Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Create closed payment transaction
*/
public function createTransaction($data)
{
try {
$signature = $this->generateSignature($data['merchant_ref'], $data['amount']);
$payload = [
'method' => $data['method'],
'merchant_ref' => $data['merchant_ref'],
'amount' => $data['amount'],
'customer_name' => $data['customer_name'],
'customer_email' => $data['customer_email'],
'customer_phone' => $data['customer_phone'],
'order_items' => $data['order_items'],
'return_url' => $data['return_url'] ?? url('/payment/return'),
'expired_time' => $data['expired_time'] ?? (time() + (24 * 60 * 60)),
'signature' => $signature
];
// Add optional callback_url if provided
if (isset($data['callback_url'])) {
$payload['callback_url'] = $data['callback_url'];
}
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->post($this->baseUrl . '/transaction/create', $payload);
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Create Transaction Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Get transaction detail
*/
public function getTransactionDetail($reference)
{
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/transaction/detail', [
'reference' => $reference
]);
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Get Transaction Detail Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Get list of transactions
*/
public function getTransactionList($page = 1, $perPage = 10)
{
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/merchant/transactions', [
'page' => $page,
'per_page' => $perPage,
]);
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Get Transaction List Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Create open payment (static payment)
*/
public function createOpenPayment($method, $merchantRef, $customerName)
{
try {
$signature = hash_hmac('sha256', $this->merchantCode . $method . $merchantRef, $this->privateKey);
$payload = [
'method' => $method,
'merchant_ref' => $merchantRef,
'customer_name' => $customerName,
'signature' => $signature
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->post($this->baseUrl . '/open-payment/create', $payload);
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Create Open Payment Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Get open payment detail
*/
public function getOpenPaymentDetail($uuid)
{
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->apiKey,
])->get($this->baseUrl . '/open-payment/' . $uuid . '/detail');
return $response->json();
} catch (\Exception $e) {
Log::error('Tripay Get Open Payment Detail Error: ' . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Generate signature for closed transaction
*/
private function generateSignature($merchantRef, $amount)
{
return hash_hmac('sha256', $this->merchantCode . $merchantRef . $amount, $this->privateKey);
}
/**
* Validate callback signature
*/
public function validateCallbackSignature($callbackSignature, $jsonBody)
{
$signature = hash_hmac('sha256', $jsonBody, $this->privateKey);
return $signature === $callbackSignature;
}
}
Service class ini menggunakan HTTP Client bawaan Laravel untuk berkomunikasi langsung dengan API Tripay, tanpa dependency package pihak ketiga. Semua method sudah dilengkapi dengan error handling dan logging.
Step 3: Konfigurasi Service Config
Buat konfigurasi untuk Tripay di file config/services.php. Tambahkan kode berikut di array services:
'tripay' => [
'api_key' => env('TRIPAY_API_KEY'),
'private_key' => env('TRIPAY_PRIVATE_KEY'),
'merchant_code' => env('TRIPAY_MERCHANT_CODE'),
'mode' => env('TRIPAY_MODE', 'production'),
],
Step 4: Membuat Controller untuk Payment
Sekarang kita akan membuat controller untuk menangani proses pembayaran. Jalankan command artisan berikut:
php artisan make:controller PaymentController
Kemudian buka file app/Http/Controllers/PaymentController.php dan tambahkan kode berikut:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\TripayService;
use App\Models\Order;
class PaymentController extends Controller
{
protected $tripayService;
public function __construct(TripayService $tripayService)
{
$this->tripayService = $tripayService;
}
/**
* Show available payment channels
*/
public function index()
{
$response = $this->tripayService->getPaymentChannels();
if (!$response['success']) {
return back()->with('error', 'Failed to load payment channels');
}
return view('payment.index', [
'channels' => $response['data']
]);
}
/**
* Process payment creation
*/
public function createPayment(Request $request)
{
$request->validate([
'payment_method' => 'required',
'amount' => 'required|numeric|min:10000',
'customer_name' => 'required',
'customer_email' => 'required|email',
'customer_phone' => 'required',
]);
// Generate unique merchant reference
$merchantRef = 'INV-' . time() . '-' . rand(1000, 9999);
// Prepare order items
$orderItems = [
[
'sku' => 'PROD-001',
'name' => $request->product_name ?? 'Product Name',
'price' => $request->amount,
'quantity' => 1,
]
];
// Create transaction data
$transactionData = [
'method' => $request->payment_method,
'merchant_ref' => $merchantRef,
'amount' => $request->amount,
'customer_name' => $request->customer_name,
'customer_email' => $request->customer_email,
'customer_phone' => $request->customer_phone,
'order_items' => $orderItems,
];
try {
$response = $this->tripayService->createTransaction($transactionData);
if ($response['success']) {
// Save to database
Order::create([
'merchant_ref' => $merchantRef,
'reference' => $response['data']['reference'],
'payment_method' => $request->payment_method,
'payment_name' => $response['data']['payment_name'] ?? '',
'amount' => $response['data']['amount'],
'fee' => $response['data']['total_fee'] ?? 0,
'amount_received' => $response['data']['amount_received'] ?? 0,
'customer_name' => $request->customer_name,
'customer_email' => $request->customer_email,
'customer_phone' => $request->customer_phone,
'status' => 'UNPAID',
'checkout_url' => $response['data']['checkout_url'] ?? null,
'expired_time' => date('Y-m-d H:i:s', $response['data']['expired_time']),
]);
return redirect()->route('payment.invoice', $response['data']['reference'])
->with('success', 'Payment created successfully');
}
return back()->with('error', $response['message'] ?? 'Failed to create payment');
} catch (\Exception $e) {
return back()->with('error', 'Error: ' . $e->getMessage());
}
}
/**
* Show invoice/payment instruction
*/
public function invoice($reference)
{
$order = Order::where('reference', $reference)->first();
if (!$order) {
return redirect()->route('payment.index')
->with('error', 'Order not found');
}
$transaction = $this->tripayService->getTransactionDetail($reference);
if (!$transaction['success']) {
return view('payment.invoice', [
'order' => $order,
'transaction' => null
]);
}
return view('payment.invoice', [
'order' => $order,
'transaction' => $transaction['data']
]);
}
/**
* Check payment status
*/
public function checkStatus($reference)
{
$transaction = $this->tripayService->getTransactionDetail($reference);
if ($transaction['success']) {
$order = Order::where('reference', $reference)->first();
if ($order && $order->status !== $transaction['data']['status']) {
$order->update([
'status' => $transaction['data']['status'],
'paid_at' => $transaction['data']['status'] === 'PAID' ? now() : null,
]);
}
}
return response()->json($transaction);
}
}
Step 5: Handling Callback dari Tripay
Callback adalah fitur penting yang digunakan Tripay untuk mengirim notifikasi status pembayaran ke server kita. Buat controller baru untuk handling callback:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Order;
use App\Services\TripayService;
use Illuminate\Support\Facades\Log;
class TripayCallbackController extends Controller
{
protected $tripayService;
public function __construct(TripayService $tripayService)
{
$this->tripayService = $tripayService;
}
/**
* Handle callback from Tripay
*/
public function handle(Request $request)
{
// Get callback signature from header
$callbackSignature = $request->server('HTTP_X_CALLBACK_SIGNATURE');
// Get JSON data
$json = $request->getContent();
// Log callback for debugging
Log::info('Tripay Callback Received', [
'signature' => $callbackSignature,
'body' => $json
]);
// Validate signature
if (!$this->tripayService->validateCallbackSignature($callbackSignature, $json)) {
Log::warning('Invalid callback signature');
return response()->json([
'success' => false,
'message' => 'Invalid signature',
], 401);
}
// Decode JSON
$data = json_decode($json);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('Invalid JSON data from callback');
return response()->json([
'success' => false,
'message' => 'Invalid data sent by payment gateway',
], 400);
}
// Validate callback event
$callbackEvent = $request->server('HTTP_X_CALLBACK_EVENT');
if ($callbackEvent !== 'payment_status') {
Log::warning('Invalid callback event: ' . $callbackEvent);
return response()->json([
'success' => false,
'message' => 'Invalid callback event',
], 400);
}
// Find order
$order = Order::where('reference', $data->reference)->first();
if (!$order) {
Log::error('Order not found: ' . $data->reference);
return response()->json([
'success' => false,
'message' => 'Order not found',
], 404);
}
// Update order status based on payment status
if ($data->status === 'PAID') {
$order->update([
'status' => 'PAID',
'paid_at' => now(),
]);
Log::info('Order paid successfully: ' . $order->reference);
// Send notification to customer
// Mail::to($order->customer_email)->send(new PaymentSuccessMail($order));
} elseif ($data->status === 'EXPIRED') {
$order->update([
'status' => 'EXPIRED',
]);
Log::info('Order expired: ' . $order->reference);
} elseif ($data->status === 'FAILED') {
$order->update([
'status' => 'FAILED',
]);
Log::info('Order failed: ' . $order->reference);
}
return response()->json([
'success' => true,
]);
}
}
Step 6: Membuat Model dan Migration untuk Order
Kita perlu membuat model dan migration untuk menyimpan data order. Jalankan command berikut:
php artisan make:model Order -m
Buka file migration yang baru dibuat di folder database/migrations dan tambahkan kolom-kolom berikut:
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('merchant_ref')->unique();
$table->string('reference')->unique();
$table->string('payment_method');
$table->string('payment_name')->nullable();
$table->decimal('amount', 15, 2);
$table->decimal('fee', 15, 2)->default(0);
$table->decimal('amount_received', 15, 2)->default(0);
$table->string('customer_name');
$table->string('customer_email');
$table->string('customer_phone')->nullable();
$table->text('checkout_url')->nullable();
$table->enum('status', ['UNPAID', 'PAID', 'EXPIRED', 'FAILED'])->default('UNPAID');
$table->timestamp('expired_time')->nullable();
$table->timestamp('paid_at')->nullable();
$table->timestamps();
});
}
Kemudian update model Order:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
protected $fillable = [
'merchant_ref',
'reference',
'payment_method',
'payment_name',
'amount',
'fee',
'amount_received',
'customer_name',
'customer_email',
'customer_phone',
'checkout_url',
'status',
'expired_time',
'paid_at',
];
protected $casts = [
'expired_time' => 'datetime',
'paid_at' => 'datetime',
];
}
protected $casts = [
'paid_at' => 'datetime',
];
}
Jalankan migration:
php artisan migrate
Step 7: Membuat Routes
Tambahkan routes untuk payment di file routes/web.php:
use App\Http\Controllers\PaymentController;
use App\Http\Controllers\TripayCallbackController;
// Payment routes
Route::get('/payment', [PaymentController::class, 'index'])->name('payment.index');
Route::post('/payment/create', [PaymentController::class, 'createPayment'])->name('payment.create');
Route::get('/payment/invoice/{reference}', [PaymentController::class, 'invoice'])->name('payment.invoice');
Route::get('/payment/check-status/{reference}', [PaymentController::class, 'checkStatus'])->name('payment.check-status');
// Callback route (exclude from CSRF verification)
Route::post('/payment/callback', [TripayCallbackController::class, 'handle'])->name('payment.callback');
Penting: Untuk callback route, Anda perlu mengecualikannya dari CSRF verification. Buka file app/Http/Middleware/VerifyCsrfToken.php dan tambahkan:
protected $except = [
'payment/callback',
];
Step 8: Membuat View untuk Payment
Buat view sederhana untuk form pembayaran. Buat file resources/views/payment/index.blade.php:
@extends('layouts.app')
@section('content')
<div class="container">
<h2>Pilih Metode Pembayaran</h2>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if(session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<form action="{{ route('payment.create') }}" method="POST">
@csrf
<div class="form-group">
<label>Pilih Metode Pembayaran</label>
<select name="payment_method" class="form-control" required>
@foreach($channels as $channel)
<option value="{{ $channel['code'] }}">
{{ $channel['name'] }} - Fee: Rp {{ number_format($channel['total_fee']['flat']) }}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label>Jumlah Pembayaran</label>
<input type="number" name="amount" class="form-control" required min="10000">
</div>
<div class="form-group">
<label>Nama</label>
<input type="text" name="customer_name" class="form-control" required>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="customer_email" class="form-control" required>
</div>
<div class="form-group">
<label>No. Handphone</label>
<input type="text" name="customer_phone" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Bayar Sekarang</button>
</form>
</div>
@endsection
Step 9: Setting Callback URL di Dashboard Tripay
Langkah terakhir adalah mengatur callback URL di dashboard merchant Tripay Anda:
- Login ke dashboard Tripay
- Masuk ke menu Merchant Settings
- Temukan field Callback URL
- Masukkan URL callback Anda, contoh:
https://yourdomain.com/payment/callback - Simpan pengaturan
Testing Integrasi
Untuk testing, Tripay menyediakan mode sandbox. Berikut langkah-langkahnya:
- Pastikan
TRIPAY_MODE=sandboxdi file .env - Gunakan API Key dan Private Key dari sandbox environment
- Akses halaman payment di aplikasi Anda
- Pilih metode pembayaran dan isi form
- Setelah transaksi dibuat, Anda akan mendapatkan nomor virtual account atau instruksi pembayaran
- Untuk testing callback, Anda bisa menggunakan simulator di dashboard Tripay
Tips Testing: Gunakan tools seperti ngrok untuk membuat URL publik dari localhost Anda saat development, sehingga callback dari Tripay bisa masuk ke aplikasi lokal Anda.
Tips dan Best Practices
- Validasi Signature: Selalu validasi signature pada callback untuk memastikan request benar-benar dari Tripay
- Logging: Implementasikan logging untuk setiap transaksi dan callback yang masuk, ini akan membantu debugging
- Error Handling: Berikan error handling yang baik untuk setiap API call ke Tripay
- Status Order: Gunakan state machine atau enum untuk mengelola status order agar konsisten
- Notification: Kirim email atau notifikasi ke customer setiap ada perubahan status pembayaran
- Security: Jangan pernah commit file .env yang berisi API Key dan Private Key ke repository
Troubleshooting Common Issues
1. Callback tidak masuk:
- Pastikan callback URL sudah diset dengan benar di dashboard Tripay
- Cek apakah URL callback bisa diakses dari luar (bukan localhost)
- Pastikan route callback sudah dikecualikan dari CSRF protection
2. Signature validation failed:
- Periksa kembali Private Key yang digunakan
- Pastikan tidak ada space atau karakter tersembunyi di Private Key
- Verifikasi bahwa Anda menggunakan raw JSON body untuk generate signature
3. Transaction creation failed:
- Cek minimum amount untuk metode pembayaran yang dipilih
- Pastikan semua required fields sudah terisi dengan benar
- Verifikasi format data sesuai dengan dokumentasi Tripay
Kesimpulan
Integrasi Tripay payment gateway dengan Laravel sebenarnya cukup straightforward jika mengikuti langkah-langkah di atas dengan teliti. Yang paling penting adalah memahami alur kerja dari payment gateway, mulai dari pembuatan transaksi, handling callback, hingga update status order.
Dengan menggunakan service class yang terpisah, kode Anda akan lebih mudah dimaintain dan bisa di-reuse di berbagai bagian aplikasi. Jangan lupa untuk selalu melakukan testing menyeluruh sebelum go-live ke production environment.
Semoga tutorial ini membantu Anda dalam mengintegrasikan Tripay payment gateway ke aplikasi Laravel. Selamat coding!
Referensi: Dokumentasi resmi Tripay tersedia di tripay.co.id/developer
...