This commit is contained in:
NekoMonci12
2025-04-05 12:39:39 +07:00
parent 70b8e04ebe
commit 39c5044a02
3 changed files with 320 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
<?php
/**
* Tripay_Gateway class
*
* @package Modules\Store
* @version 1.0.0
* @license MIT
*/
class Tripay_Gateway extends GatewayBase {
private $endpoint;
public function __construct() {
$name = 'Tripay';
$author = '<a href="https://tripay.co.id" target="_blank" rel="nofollow noopener">Tripay</a>';
$gateway_version = '1.0.0';
$store_version = '1.0.0';
$settings = ROOT_PATH . '/modules/Store/gateways/Tripay/gateway_settings/settings.php';
parent::__construct($name, $author, $gateway_version, $store_version, $settings);
$this->endpoint = StoreConfig::get('tripay.debug_mode')
? 'https://tripay.co.id/api-sandbox/transaction'
: 'https://tripay.co.id/api/transaction';
}
/**
* Called when the checkout page is loaded.
* Not required for Tripay closed payment.
*/
public function onCheckoutPageLoad(TemplateBase $template, Customer $customer): void {
// Not required for Tripay closed payment.
}
/**
* Processes the order by creating a Tripay closed payment transaction.
*/
public function processOrder(Order $order): void {
$tripay_api_key = StoreConfig::get('tripay.api_key');
$tripay_api_secret = StoreConfig::get('tripay.api_secret');
$tripay_merchant_code = StoreConfig::get('tripay.merchant_code');
$tripay_channel = StoreConfig::get('tripay.channel');
$redirect_url = URL::getSelfURL();
$domain = rtrim(parse_url($redirect_url, PHP_URL_HOST), '/');
if (empty($tripay_api_key) || empty($tripay_api_secret) || empty($tripay_merchant_code)) {
$this->addError('Tripay configuration is missing.');
return;
}
$transaction_endpoint = $this->endpoint . '/create';
$amount = (string)(int)Store::fromCents($order->getAmount()->getTotalCents());
$order_id = $order->data()->id;
$customerData = $order->customer()->data();
$customer_name = $customerData->username;
$customer_email = $customerData->username . '@' . $domain;
$customer_phone = '081234567890';
$merchant_ref = 'MC-' . $customer_name . '-' . time() . '-' . $order_id;
$order_items = [
[
'sku' => 'ITEM001',
'name' => $order->getDescription(),
'price' => $amount,
'quantity' => 1
]
];
$signature = hash_hmac('sha256', $tripay_merchant_code . $merchant_ref . $amount, $tripay_api_secret);
$params = [
'method' => $tripay_channel,
'merchant_ref' => $merchant_ref,
'amount' => $amount,
'customer_name' => $customer_name,
'customer_email' => $customer_email,
'customer_phone' => $customer_phone,
'order_items' => $order_items,
'api_key' => $tripay_api_key,
'merchant_code' => $tripay_merchant_code,
'return_url' => $redirect_url,
'expired_time' => (time() + (15 * 60)),
'signature' => $signature
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $transaction_endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $tripay_api_key,
'Content-Type: application/x-www-form-urlencoded'
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$this->addError('Curl error: ' . curl_error($ch));
curl_close($ch);
return;
}
curl_close($ch);
$result = json_decode($response, true);
if (StoreConfig::get('tripay.debug_mode')) {
if (!isset($result['data']['checkout_url'])) {
echo '<pre>';
echo "Sent Params:\n";
print_r($params);
echo "\nRequest Headers:\n";
print_r([
'Authorization: Bearer ' . $tripay_api_key,
'Content-Type: application/x-www-form-urlencoded'
]);
echo "\n\nRaw Tripay response:\n";
print_r($response);
echo "\n\nParsed JSON:\n";
print_r($result);
echo '</pre>';
$error_message = isset($result['message']) ? $result['message'] : 'Unknown error';
$this->addError('Tripay error: ' . $error_message);
return;
}
} else {
$this->addError('ERROR: Please Contact The Web Administrator');
return;
}
$payment = new Payment($result['data']['reference'], 'transaction');
$payment->create([
'order_id' => $order->data()->id,
'gateway_id' => $this->getId(),
'transaction' => $result['data']['reference'],
'created' => time(),
'last_updated' => time(),
'status_id' => 0,
'amount_cents' => $order->getAmount()->getTotalCents(),
'currency' => $order->getAmount()->getCurrency(),
'fee_cents' => 0
]);
header('Location: ' . $result['data']['checkout_url']);
exit;
}
/**
* Handles the return URL after a successful payment.
* Modify as necessary based on Tripay's redirect/return behavior.
*/
public function handleReturn(): bool {
Redirect::to(URL::build('/'));
die();
return true; // Leave it For Compability Reason
}
/**
* Handles Tripay's listener (webhook/notification) for payment updates.
*/
public function handleListener(): void {
$json = file_get_contents('php://input');
$headers = getallheaders();
$callbackSignature = $headers['X-Callback-Signature'] ?? '';
$callbackEvent = $headers['X-Callback-Event'] ?? '';
$privateKey = StoreConfig::get('tripay.api_secret');
$expectedSignature = hash_hmac('sha256', $json, $privateKey);
if ($callbackSignature !== $expectedSignature) {
http_response_code(403);
$this->logError('[Tripay] Invalid signature');
die(json_encode(['success' => false, 'message' => 'Invalid signature']));
}
$payload = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE || !isset($payload['merchant_ref'])) {
http_response_code(400);
$this->logError('[Tripay] Invalid JSON payload');
die(json_encode(['success' => false, 'message' => 'Invalid JSON']));
}
if ($callbackEvent !== 'payment_status') {
http_response_code(400);
$this->logError('[Tripay] Unexpected event type: ' . $callbackEvent);
die(json_encode(['success' => false, 'message' => 'Unexpected callback event']));
}
$merchant_ref = $payload['merchant_ref'];
$tripay_ref = $payload['reference'];
$status = strtoupper((string) $payload['status']);
$amount_cents = Store::toCents($payload['amount_received']);
$currency = 'IDR';
$order_id = (int) substr(strrchr($merchant_ref, '-'), 1);
$payment = new Payment($tripay_ref, 'transaction');
$data = [
'order_id' => $order_id,
'gateway_id' => $this->getId(),
'transaction' => $tripay_ref,
'amount_cents' => $amount_cents,
'currency' => $currency,
'fee_cents' => 0
];
switch ($status) {
case 'PAID':
$payment->handlePaymentEvent(Payment::COMPLETED, $data);
break;
case 'EXPIRED':
case 'FAILED':
$payment->handlePaymentEvent(Payment::REFUNDED);
break;
default:
$this->logError("[Tripay] Unknown status: $status");
break;
}
echo json_encode(['success' => true]);
}
}
$gateway = new Tripay_Gateway();

View File

@@ -0,0 +1,46 @@
<?php
/**
* Tripay Closed Payment Gateway Settings Page
*
* @package Modules\Store
* @version 1.0.0
* @license MIT
*/
require_once(ROOT_PATH . '/modules/Store/classes/StoreConfig.php');
if (Input::exists()) {
if (Token::check()) {
$enabled = (isset($_POST['enable']) && $_POST['enable'] === 'on') ? 1 : 0;
StoreConfig::set('tripay.api_key', $_POST['tripay_api_key']);
StoreConfig::set('tripay.api_secret', $_POST['tripay_api_secret']);
StoreConfig::set('tripay.merchant_code', $_POST['tripay_merchant_code']);
StoreConfig::set('tripay.channel', $_POST['tripay_channel']);
StoreConfig::set('tripay.debug_mode', isset($_POST['tripay_debug_mode']) && $_POST['tripay_debug_mode'] === 'on' ? 1 : 0);
DB::getInstance()->update('store_gateways', $gateway->getId(), [
'enabled' => $enabled,
]);
Session::flash('gateways_success', $language->get('admin', 'successfully_updated'));
} else {
$errors = [$language->get('general', 'invalid_token')];
}
}
// Generate Tripay Callback URL
$domain = URL::getSelfURL();
$callback_url = $domain . 'store/listener/?gateway=Tripay';
// Assign to Smarty template
$smarty->assign([
'SETTINGS_TEMPLATE' => ROOT_PATH . '/modules/Store/gateways/Tripay/gateway_settings/settings.tpl',
'ENABLE_VALUE' => (isset($enabled)) ? $enabled : $gateway->isEnabled(),
'TRIPAY_API_KEY' => StoreConfig::get('tripay.api_key'),
'TRIPAY_API_SECRET' => StoreConfig::get('tripay.api_secret'),
'TRIPAY_MERCHANT_CODE' => StoreConfig::get('tripay.merchant_code'),
'TRIPAY_CHANNEL' => StoreConfig::get('tripay.channel'),
'TRIPAY_DEBUG_MODE' => StoreConfig::get('tripay.debug_mode'),
'CALLBACK_URL' => $callback_url
]);

View File

@@ -0,0 +1,41 @@
<form action="" method="post">
<div class="form-group">
<label for="inputTripayApiKey">Tripay API Key</label>
<input class="form-control" type="text" id="inputTripayApiKey" name="tripay_api_key" value="{$TRIPAY_API_KEY}" placeholder="Tripay API Key">
</div>
<div class="form-group">
<label for="inputTripayApiSecret">Tripay API Secret</label>
<input class="form-control" type="text" id="inputTripayApiSecret" name="tripay_api_secret" value="{$TRIPAY_API_SECRET}" placeholder="Tripay API Secret">
</div>
<div class="form-group">
<label for="inputTripayMerchantCode">Tripay Merchant Code</label>
<input class="form-control" type="text" id="inputTripayMerchantCode" name="tripay_merchant_code" value="{$TRIPAY_MERCHANT_CODE}" placeholder="Tripay Merchant Code">
</div>
<div class="form-group">
<label for="inputTripayChannel">Tripay Payment Channel</label>
<input class="form-control" type="text" id="inputTripayChannel" name="tripay_channel" value="{$TRIPAY_CHANNEL}" placeholder="QRIS">
</div>
<div class="form-group custom-control custom-switch">
<input id="inputDebugMode" name="tripay_debug_mode" type="checkbox" class="custom-control-input"{if $TRIPAY_DEBUG_MODE eq 1} checked{/if} />
<label class="custom-control-label" for="inputDebugMode">Enable Debug Mode</label>
</div>
<div class="form-group custom-control custom-switch">
<input id="inputEnabled" name="enable" type="checkbox" class="custom-control-input"{if $ENABLE_VALUE eq 1} checked{/if} />
<label class="custom-control-label" for="inputEnabled">Enable Payment Method</label>
</div>
<div class="form-group">
<input type="hidden" name="token" value="{$TOKEN}">
<input type="submit" value="{$SUBMIT}" class="btn btn-primary">
</div>
<div class="form-group">
<label for="callbackURL">Tripay Callback URL</label>
<input class="form-control" type="text" id="callbackURL" value="{$CALLBACK_URL}" readonly>
</div>
</form>