feat: Stripe-Key-Checker, Krea AI Pattern, Scan-Progress-Tracking
- StripeKeyChecker: 2-Schritt-Check (/v1/balance + /v1/account), Live/Test-Erkennung, Balance, Account-Details - KeyPatterns: Stripe-Patterns einkommentiert (sk_live_, sk_test_), Krea AI Pattern (UUID:Token), Mindest-Suchlänge 4 - KeyScannerService: Stripe-Check integriert, Auto-Check nach Scan entfernt (manuell separat), Progress-Tracking (Phase, Detail, Current/Total, KeysFound) - ApiKeysEndpoint: Stripe in HandleCheck/HandleCheckAll/HandleAdopt, Progress im Status-Endpoint - Frontend: Live-Fortschrittsanzeige mit Spinner, Phase, Query, Counter, Polling 1.5s, Krea AI in typeNames/typeIcons
This commit is contained in:
1 parent
122000487c
commit
3cd35ebbfa
6 files changed
+326
-14
No files matched your search
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gott.Services
|
||||
{
|
||||
public class StripeCheckResult
|
||||
{
|
||||
public string Status { get; set; } // "ok", "invalid", "error"
|
||||
public string Message { get; set; }
|
||||
public bool IsLiveKey { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public long? BalanceAvailable { get; set; }
|
||||
public long? BalancePending { get; set; }
|
||||
public string AccountId { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public string AccountEmail { get; set; }
|
||||
public string AccountCountry { get; set; }
|
||||
public bool? PayoutsEnabled { get; set; }
|
||||
public bool? ChargesEnabled { get; set; }
|
||||
}
|
||||
|
||||
public static class StripeKeyChecker
|
||||
{
|
||||
public static async Task<StripeCheckResult> CheckKeyAsync(IHttpClientFactory httpFactory, string apiKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = httpFactory.CreateClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var isLive = apiKey.StartsWith("sk_live_", StringComparison.Ordinal);
|
||||
|
||||
// Step 1: Check /v1/balance (read-only, no cost)
|
||||
var balanceRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.stripe.com/v1/balance");
|
||||
balanceRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
|
||||
var balanceResp = await client.SendAsync(balanceRequest);
|
||||
var balanceBody = await balanceResp.Content.ReadAsStringAsync();
|
||||
var balanceCode = (int)balanceResp.StatusCode;
|
||||
|
||||
if (balanceCode == 401)
|
||||
{
|
||||
return new StripeCheckResult
|
||||
{
|
||||
Status = "invalid",
|
||||
Message = "Ungültig oder revoked (401)",
|
||||
IsLiveKey = isLive
|
||||
};
|
||||
}
|
||||
|
||||
if (balanceCode >= 400)
|
||||
{
|
||||
// Parse error
|
||||
var errorMsg = "Unbekannter Fehler";
|
||||
try
|
||||
{
|
||||
using var errDoc = JsonDocument.Parse(balanceBody);
|
||||
if (errDoc.RootElement.TryGetProperty("error", out var errObj) &&
|
||||
errObj.TryGetProperty("message", out var msg))
|
||||
errorMsg = msg.GetString();
|
||||
}
|
||||
catch { }
|
||||
|
||||
return new StripeCheckResult
|
||||
{
|
||||
Status = "error",
|
||||
Message = $"API-Fehler ({balanceCode}): {errorMsg}",
|
||||
IsLiveKey = isLive
|
||||
};
|
||||
}
|
||||
|
||||
// Parse balance
|
||||
string currency = null;
|
||||
long balanceAvailable = 0;
|
||||
long balancePending = 0;
|
||||
|
||||
try
|
||||
{
|
||||
using var balDoc = JsonDocument.Parse(balanceBody);
|
||||
var root = balDoc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("available", out var available) && available.GetArrayLength() > 0)
|
||||
{
|
||||
var first = available[0];
|
||||
if (first.TryGetProperty("currency", out var cur))
|
||||
currency = cur.GetString()?.ToUpperInvariant();
|
||||
if (first.TryGetProperty("amount", out var amt))
|
||||
balanceAvailable = amt.GetInt64();
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("pending", out var pending) && pending.GetArrayLength() > 0)
|
||||
{
|
||||
var first = pending[0];
|
||||
if (first.TryGetProperty("amount", out var amt))
|
||||
balancePending = amt.GetInt64();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Step 2: Get account info via /v1/account
|
||||
string accountId = null;
|
||||
string accountName = null;
|
||||
string accountEmail = null;
|
||||
string accountCountry = null;
|
||||
bool? payoutsEnabled = null;
|
||||
bool? chargesEnabled = null;
|
||||
|
||||
try
|
||||
{
|
||||
var accountRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.stripe.com/v1/account");
|
||||
accountRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
|
||||
var accountResp = await client.SendAsync(accountRequest);
|
||||
if ((int)accountResp.StatusCode == 200)
|
||||
{
|
||||
var accountBody = await accountResp.Content.ReadAsStringAsync();
|
||||
using var accDoc = JsonDocument.Parse(accountBody);
|
||||
var acc = accDoc.RootElement;
|
||||
|
||||
if (acc.TryGetProperty("id", out var id))
|
||||
accountId = id.GetString();
|
||||
if (acc.TryGetProperty("business_profile", out var bp) && bp.TryGetProperty("name", out var name))
|
||||
accountName = name.GetString();
|
||||
if (acc.TryGetProperty("email", out var email))
|
||||
accountEmail = email.GetString();
|
||||
if (acc.TryGetProperty("country", out var country))
|
||||
accountCountry = country.GetString();
|
||||
if (acc.TryGetProperty("payouts_enabled", out var pe))
|
||||
payoutsEnabled = pe.GetBoolean();
|
||||
if (acc.TryGetProperty("charges_enabled", out var ce))
|
||||
chargesEnabled = ce.GetBoolean();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Build message
|
||||
var parts = new System.Collections.Generic.List<string>();
|
||||
|
||||
parts.Add(isLive ? "🔴 LIVE Key" : "🟡 Test Key");
|
||||
|
||||
if (accountName != null)
|
||||
parts.Add($"Account: {accountName}");
|
||||
else if (accountId != null)
|
||||
parts.Add($"Account: {accountId}");
|
||||
|
||||
if (accountEmail != null)
|
||||
parts.Add($"Email: {accountEmail}");
|
||||
|
||||
if (accountCountry != null)
|
||||
parts.Add($"Land: {accountCountry}");
|
||||
|
||||
if (currency != null)
|
||||
{
|
||||
var availFormatted = FormatAmount(balanceAvailable, currency);
|
||||
var pendingFormatted = FormatAmount(balancePending, currency);
|
||||
parts.Add($"Balance: {availFormatted} verfügbar, {pendingFormatted} ausstehend");
|
||||
}
|
||||
|
||||
if (chargesEnabled.HasValue)
|
||||
parts.Add(chargesEnabled.Value ? "✅ Charges aktiv" : "❌ Charges deaktiviert");
|
||||
|
||||
if (payoutsEnabled.HasValue)
|
||||
parts.Add(payoutsEnabled.Value ? "✅ Payouts aktiv" : "❌ Payouts deaktiviert");
|
||||
|
||||
return new StripeCheckResult
|
||||
{
|
||||
Status = "ok",
|
||||
Message = string.Join(" · ", parts),
|
||||
IsLiveKey = isLive,
|
||||
Currency = currency,
|
||||
BalanceAvailable = balanceAvailable,
|
||||
BalancePending = balancePending,
|
||||
AccountId = accountId,
|
||||
AccountName = accountName,
|
||||
AccountEmail = accountEmail,
|
||||
AccountCountry = accountCountry,
|
||||
PayoutsEnabled = payoutsEnabled,
|
||||
ChargesEnabled = chargesEnabled
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new StripeCheckResult
|
||||
{
|
||||
Status = "error",
|
||||
Message = ex.Message,
|
||||
IsLiveKey = apiKey.StartsWith("sk_live_", StringComparison.Ordinal)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatAmount(long amountInCents, string currency)
|
||||
{
|
||||
var amount = amountInCents / 100.0;
|
||||
return $"{amount:N2} {currency}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user