Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { ref, computed, onMounted, nextTick } from 'vue';
import AdminLayout from '@/layouts/admin/CasinoAdminLayout.vue';
import { csrfFetch } from '@/utils/csrfFetch';
const props = defineProps<{
settings: {
mode: 'live'|'sandbox';
api_key?: string;
ipn_secret?: string;
address_mode?: string;
enabled_currencies: string[];
global_min_usd: number;
global_max_usd: number;
btx_per_usd: number;
per_currency_overrides: Record<string, { min_usd?: number|null; max_usd?: number|null; btx_per_usd?: number|null }>;
success_url: string;
cancel_url: string;
};
defaults: {
commonCurrencies: string[];
modes: string[];
addressModes: string[];
}
}>();
const allCurrencies = ref<string[]>(props.defaults.commonCurrencies);
const addCurrency = ref('');
const showApiKey = ref(false);
const showIpnSecret = ref(false);
const testStatus = ref<'idle'|'loading'|'ok'|'fail'>('idle');
const testMessage = ref('');
const form = useForm({
mode: props.settings.mode || 'live',
api_key: props.settings.api_key || '',
ipn_secret: props.settings.ipn_secret || '',
address_mode: props.settings.address_mode || 'per_payment',
enabled_currencies: [...(props.settings.enabled_currencies || [])],
global_min_usd: props.settings.global_min_usd ?? 10,
global_max_usd: props.settings.global_max_usd ?? 10000,
btx_per_usd: props.settings.btx_per_usd ?? 1.0,
per_currency_overrides: { ...(props.settings.per_currency_overrides || {}) },
success_url: props.settings.success_url || '/wallet?deposit=success',
cancel_url: props.settings.cancel_url || '/wallet?deposit=cancel',
});
const pickList = computed(() => {
const set = new Set(form.enabled_currencies);
return allCurrencies.value.filter(c => !set.has(c));
});
function addToWhitelist(cur: string) {
const up = cur.toUpperCase();
if (!form.enabled_currencies.includes(up)) form.enabled_currencies.push(up);
addCurrency.value = '';
}
function removeFromWhitelist(cur: string) {
form.enabled_currencies = form.enabled_currencies.filter(c => c !== cur);
if (form.per_currency_overrides[cur]) {
const rest: any = { ...(form.per_currency_overrides as any) };
delete rest[cur];
form.per_currency_overrides = rest as any;
}
}
function ensureOverride(cur: string) {
if (!form.per_currency_overrides[cur]) {
form.per_currency_overrides[cur] = { min_usd: null, max_usd: null, btx_per_usd: null } as any;
}
}
async function submit() {
await form.post('/admin/payments/settings', { preserveScroll: true });
}
async function testConnection() {
testStatus.value = 'loading';
testMessage.value = '';
try {
const res = await csrfFetch('/admin/payments/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ api_key: form.api_key }),
});
const json = await res.json();
if (res.ok && json.ok) {
testStatus.value = 'ok';
testMessage.value = json.message || 'Verbindung erfolgreich!';
} else {
testStatus.value = 'fail';
testMessage.value = json.message || 'Verbindung fehlgeschlagen.';
}
} catch {
testStatus.value = 'fail';
testMessage.value = 'Netzwerkfehler.';
}
}
onMounted(() => {
nextTick(() => { if ((window as any).lucide) (window as any).lucide.createIcons(); });
});
</script>
<template>
<AdminLayout>
<Head title="Admin Payment Settings" />
<section class="content">
<div class="wrap">
<div class="panel">
<header class="page-head">
<div class="head-flex">
<div class="title-group">
<div class="title">NOWPayments Einstellungen</div>
<p class="subtitle">Konfiguriere LiveModus, CoinsWhitelist, Limits und BTXKurs.</p>
</div>
<div class="actions">
<button class="btn primary" @click="submit" :disabled="form.processing">
<i data-lucide="save"></i>
Speichern
</button>
</div>
</div>
</header>
<!-- API Credentials -->
<div class="section-title">API Zugangsdaten</div>
<div class="form-grid">
<div class="form-item full">
<label class="lbl">NOWPayments API Key</label>
<div class="secret-row">
<input :type="showApiKey ? 'text' : 'password'" v-model="form.api_key" placeholder="Dein API Key..." />
<button type="button" class="btn icon-btn" @click="showApiKey = !showApiKey">
<i :data-lucide="showApiKey ? 'eye-off' : 'eye'"></i>
</button>
<button type="button" class="btn" :class="testStatus === 'ok' ? 'success' : testStatus === 'fail' ? 'danger' : ''" @click="testConnection" :disabled="!form.api_key || testStatus === 'loading'">
<i data-lucide="zap"></i>
{{ testStatus === 'loading' ? 'Teste...' : 'Verbindung testen' }}
</button>
</div>
<small v-if="testMessage" :class="testStatus === 'ok' ? 'text-green' : 'text-red'">{{ testMessage }}</small>
</div>
<div class="form-item full">
<label class="lbl">IPN Secret (Webhook-Signatur)</label>
<div class="secret-row">
<input :type="showIpnSecret ? 'text' : 'password'" v-model="form.ipn_secret" placeholder="Dein IPN Secret..." />
<button type="button" class="btn icon-btn" @click="showIpnSecret = !showIpnSecret">
<i :data-lucide="showIpnSecret ? 'eye-off' : 'eye'"></i>
</button>
</div>
</div>
</div>
<div class="hr"></div>
<!-- General Settings -->
<div class="section-title">Allgemein</div>
<div class="form-grid">
<div class="form-item">
<label class="lbl">Modus</label>
<select v-model="form.mode">
<option v-for="m in props.defaults.modes" :key="m" :value="m">{{ m.toUpperCase() }}</option>
</select>
<small>Für LiveBetrieb auf <b>LIVE</b> stellen.</small>
</div>
<div class="form-item">
<label class="lbl">Adress-Modus</label>
<select v-model="form.address_mode">
<option value="per_payment">Per Zahlung (neu je Transaktion)</option>
<option value="per_user">Per Nutzer (fixe Adresse)</option>
</select>
</div>
<div class="form-item">
<label class="lbl">Globales Minimum (USD/BTX)</label>
<input type="number" step="0.01" v-model.number="form.global_min_usd" />
</div>
<div class="form-item">
<label class="lbl">Globales Maximum (USD/BTX)</label>
<input type="number" step="0.01" v-model.number="form.global_max_usd" />
</div>
<div class="form-item">
<label class="lbl">BTX pro USD (global)</label>
<input type="number" step="0.00000001" v-model.number="form.btx_per_usd" />
<small>Standard 1.0 (1 BTX = 1 USD). Overrides je Währung optional unten.</small>
</div>
<div class="form-item"><!-- spacer --></div>
<div class="form-item">
<label class="lbl">Success URL</label>
<input type="text" v-model="form.success_url" />
</div>
<div class="form-item">
<label class="lbl">Cancel URL</label>
<input type="text" v-model="form.cancel_url" />
</div>
</div>
<div class="hr"></div>
<h3>CoinsWhitelist</h3>
<div class="whitelist">
<div class="tags">
<span v-for="cur in form.enabled_currencies" :key="cur" class="tag">
{{ cur }}
<button type="button" class="x" @click="removeFromWhitelist(cur)">×</button>
</span>
</div>
<div class="add-row">
<select v-model="addCurrency">
<option value="" disabled>+ Währung hinzufügen</option>
<option v-for="cur in pickList" :key="cur" :value="cur">{{ cur }}</option>
</select>
<button class="btn" :disabled="!addCurrency" @click="addToWhitelist(addCurrency)">Hinzufügen</button>
</div>
</div>
<div class="hr"></div>
<h3>PerWährung Overrides</h3>
<table class="table">
<thead>
<tr>
<th>Währung</th>
<th>Min USD</th>
<th>Max USD</th>
<th>BTX pro USD</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="cur in form.enabled_currencies" :key="cur">
<td>{{ cur }}</td>
<td>
<input type="number" step="0.01"
:value="form.per_currency_overrides[cur]?.min_usd ?? ''"
@input="(e:any)=>{ ensureOverride(cur); form.per_currency_overrides[cur].min_usd = e.target.value ? parseFloat(e.target.value) : null }" />
</td>
<td>
<input type="number" step="0.01"
:value="form.per_currency_overrides[cur]?.max_usd ?? ''"
@input="(e:any)=>{ ensureOverride(cur); form.per_currency_overrides[cur].max_usd = e.target.value ? parseFloat(e.target.value) : null }" />
</td>
<td>
<input type="number" step="0.00000001"
:value="form.per_currency_overrides[cur]?.btx_per_usd ?? ''"
@input="(e:any)=>{ ensureOverride(cur); form.per_currency_overrides[cur].btx_per_usd = e.target.value ? parseFloat(e.target.value) : null }" />
</td>
<td>
<button class="btn small" @click="() => { const o=form.per_currency_overrides[cur]; if(o){o.min_usd=null;o.max_usd=null;o.btx_per_usd=null;} }">Zurücksetzen</button>
</td>
</tr>
</tbody>
</table>
<div class="foot">
<button class="btn primary" @click="submit" :disabled="form.processing">
<i data-lucide="save"></i>
Speichern
</button>
</div>
</div>
</div>
</section>
</AdminLayout>
</template>
<style scoped>
.content { padding: 20px; }
.wrap { max-width: 1100px; margin: 0 auto; }
.panel { background: #0f0f10; border: 1px solid #18181b; border-radius: 12px; padding: 16px; }
.page-head .title { font-size: 22px; font-weight: 700; }
.subtitle { color: #a1a1aa; margin-top: 4px; }
.actions { display: flex; gap: 10px; }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-top: 12px; }
.form-item .lbl { display: block; font-weight: 600; margin-bottom: 6px; }
input, select { width: 100%; background: #0b0b0c; border: 1px solid #1f1f22; border-radius: 8px; padding: 10px; color: #e5e7eb; }
.btn { background: #1f2937; border: 1px solid #374151; color: #e5e7eb; padding: 8px 14px; border-radius: 8px; cursor: pointer; }
.btn.primary { background: #ff007a; border-color: #ff2b8f; color: white; }
.btn.small { padding: 6px 10px; font-size: 12px; }
.hr { height: 1px; background: #1f1f22; margin: 16px 0; }
.whitelist .tags { display: flex; gap: 8px; flex-wrap: wrap; }
.tag { background: #18181b; border: 1px solid #27272a; padding: 6px 10px; border-radius: 999px; display: inline-flex; align-items: center; gap: 6px; }
.tag .x { background: transparent; border: 0; color: #aaa; cursor: pointer; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { border-bottom: 1px solid #1f1f22; padding: 10px; text-align: left; }
.foot { display: flex; justify-content: flex-end; margin-top: 16px; }
</style>