polish the api route, response, health, cache, and metrics

This commit is contained in:
dwindown
2025-08-31 20:45:24 +07:00
parent ce001bf9ce
commit 726f5a842f
12 changed files with 627 additions and 97 deletions

View File

@@ -0,0 +1,161 @@
<?php
// app/controllers/License.php
require_once __DIR__ . '/../helpers/cors.php';
require_once __DIR__ . '/../helpers/http.php';
require_once __DIR__ . '/../models/license_store.php';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$path = $_GET['_action'] ?? 'verify'; // e.g., routed as /license/verify
if ($method === 'OPTIONS') { cors_preflight(); exit; }
switch ($path) {
case 'verify':
handle_verify();
break;
// You likely already have activate/deactivate; leaving them out here.
default:
json_error(404, 'not_found');
}
function handle_verify() {
cors_allow();
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$key = trim((string)($input['key'] ?? ''));
$acct = trim((string)($input['account_id'] ?? ''));
if ($key === '') { json_error(400, 'missing_key'); }
$mode = cfg('gateway_mode', 'sandbox');
// Sandbox: accept anything
if ($mode !== 'live') {
$norm = [
'key' => $key,
'source' => preg_match('/^G|^GMR|^GUM/i', $key) ? 'gumroad' : (preg_match('/^M|^MYR/i',$key) ? 'mayar' : 'sandbox'),
'plan' => 'pro', // treat as Pro
'product_id' => null,
'valid' => true,
'expires_at' => null,
'meta' => ['mode'=>'sandbox']
];
$row = license_upsert_from_verification($norm);
json_ok(['source'=>$norm['source'],'plan'=>$norm['plan'],'license'=>$row]);
}
// LIVE mode: try Gumroad first, then Mayar
$gum = verify_gumroad($key);
if ($gum['ok']) {
$row = license_upsert_from_verification($gum['norm']);
json_ok(['source'=>'gumroad','plan'=>$gum['norm']['plan'],'license'=>$row]);
}
$may = verify_mayar($key);
if ($may['ok']) {
$row = license_upsert_from_verification($may['norm']);
json_ok(['source'=>'mayar','plan'=>$may['norm']['plan'],'license'=>$row]);
}
json_error(401, 'invalid_license', ['details'=>['gumroad'=>$gum['err']??null,'mayar'=>$may['err']??null]]);
}
// --- Providers ---
function verify_gumroad($licenseKey) {
$cfg = cfg('gumroad', []);
$url = $cfg['verify_url'] ?? 'https://api.gumroad.com/v2/licenses/verify';
$pids = $cfg['product_ids'] ?? [];
// Gumroad expects POST form with license_key (+ product_id if you want to restrict)
$fields = ['license_key' => $licenseKey];
if (!empty($pids)) {
// If you have multiple products, you can loop; for simplicity, try them in order
foreach ($pids as $pid) {
$try = $fields + ['product_id' => $pid];
[$code,$body,$err] = http_post_form($url, $try);
if ($code >= 200 && $code < 300) {
$json = json_decode($body, true);
if (isset($json['success']) && $json['success'] === true) {
// Normalize
$p = $json['purchase'] ?? [];
$isRecurring = !empty($p['recurrence']);
$plan = $isRecurring ? 'subscription' : 'lifetime';
$expires = null; // Gumroad verify doesnt usually return expiry for subs; optional to compute
return ['ok'=>true,'norm'=>[
'key' => $licenseKey,
'source' => 'gumroad',
'plan' => 'pro', // Your product => Pro features; if you want to store type, add meta
'product_id' => $pid,
'valid' => true,
'expires_at' => $expires,
'meta' => ['raw'=>$json,'plan_type'=>$plan]
]];
}
}
}
return ['ok'=>false,'err'=>'gumroad_no_match'];
} else {
// No product restriction
[$code,$body,$err] = http_post_form($url, $fields);
if ($code >= 200 && $code < 300) {
$json = json_decode($body, true);
if (isset($json['success']) && $json['success'] === true) {
$p = $json['purchase'] ?? [];
$isRecurring = !empty($p['recurrence']);
$plan = $isRecurring ? 'subscription' : 'lifetime';
return ['ok'=>true,'norm'=>[
'key' => $licenseKey,
'source' => 'gumroad',
'plan' => 'pro',
'product_id' => $p['product_id'] ?? null,
'valid' => true,
'expires_at' => null,
'meta' => ['raw'=>$json,'plan_type'=>$plan]
]];
}
}
return ['ok'=>false,'err'=>'gumroad_verify_failed'];
}
}
function verify_mayar($licenseKey) {
$cfg = cfg('mayar', []);
$base = rtrim($cfg['api_base'] ?? '', '/');
$ep = $cfg['endpoint_verify'] ?? '/v1/license/verify';
$url = $base . $ep;
// Most vendors use JSON body; adjust per your doc.
// If Mayar requires Authorization, add it here.
$headers = [];
if (!empty($cfg['secret_key'])) {
$headers[] = 'Authorization: Bearer ' . $cfg['secret_key'];
}
[$code,$body,$err] = http_post_json($url, ['license_key'=>$licenseKey], $headers);
if (!($code >= 200 && $code < 300)) {
return ['ok'=>false,'err'=>'mayar_http_'.$code];
}
$json = json_decode($body, true);
// Normalize based on Mayars response shape
// Expect something like: { success:true, data:{ valid:true, product_id:'...', type:'lifetime|subscription', expires_at: '...' } }
$valid = (bool)($json['success'] ?? false);
if (!$valid) return ['ok'=>false,'err'=>'mayar_invalid'];
$data = $json['data'] ?? [];
$planType = strtolower((string)($data['type'] ?? 'lifetime')); // lifetime or subscription
$expiresAt = $data['expires_at'] ?? null;
return ['ok'=>true,'norm'=>[
'key' => $licenseKey,
'source' => 'mayar',
'plan' => 'pro',
'product_id' => $data['product_id'] ?? null,
'valid' => true,
'expires_at' => $expiresAt,
'meta' => ['raw'=>$json,'plan_type'=>$planType]
]];
}