$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 doesn’t 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 Mayar’s 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] ]]; }