minder change
This commit is contained in:
@@ -477,6 +477,9 @@
|
|||||||
daily_cost_currency: 'EUR',
|
daily_cost_currency: 'EUR',
|
||||||
report_currency: 'EUR',
|
report_currency: 'EUR',
|
||||||
crypto_currency: 'DOGE',
|
crypto_currency: 'DOGE',
|
||||||
|
crypto_base_price_amount: '',
|
||||||
|
crypto_base_price_currency: 'USD',
|
||||||
|
fiat_base_price_amount: '',
|
||||||
preferred_currencies: ['DOGE', 'USD', 'EUR'],
|
preferred_currencies: ['DOGE', 'USD', 'EUR'],
|
||||||
cost_plans: [],
|
cost_plans: [],
|
||||||
currencies: [],
|
currencies: [],
|
||||||
@@ -882,6 +885,13 @@
|
|||||||
report_currency: 'EUR',
|
report_currency: 'EUR',
|
||||||
crypto_currency: 'DOGE',
|
crypto_currency: 'DOGE',
|
||||||
});
|
});
|
||||||
|
const [offerPreviewForm, setOfferPreviewForm] = useState({
|
||||||
|
crypto_base_price_amount: '',
|
||||||
|
crypto_base_price_currency: 'USD',
|
||||||
|
fiat_base_price_amount: '',
|
||||||
|
});
|
||||||
|
const [previewMinerOffers, setPreviewMinerOffers] = useState([]);
|
||||||
|
const [previewOffersLoading, setPreviewOffersLoading] = useState(false);
|
||||||
const [moduleAuthForm, setModuleAuthForm] = useState({
|
const [moduleAuthForm, setModuleAuthForm] = useState({
|
||||||
required: true,
|
required: true,
|
||||||
users: '',
|
users: '',
|
||||||
@@ -943,16 +953,6 @@
|
|||||||
});
|
});
|
||||||
const [walletWithdrawalModalOpen, setWalletWithdrawalModalOpen] = useState(false);
|
const [walletWithdrawalModalOpen, setWalletWithdrawalModalOpen] = useState(false);
|
||||||
const [walletWithdrawalRows, setWalletWithdrawalRows] = useState([]);
|
const [walletWithdrawalRows, setWalletWithdrawalRows] = useState([]);
|
||||||
const [minerOfferForm, setMinerOfferForm] = useState({
|
|
||||||
label: '',
|
|
||||||
base_price_amount: '',
|
|
||||||
base_price_currency: 'USD',
|
|
||||||
payment_type: 'crypto',
|
|
||||||
auto_renew: false,
|
|
||||||
note: '',
|
|
||||||
is_active: true,
|
|
||||||
});
|
|
||||||
const [minerOfferModalOpen, setMinerOfferModalOpen] = useState(false);
|
|
||||||
const [purchaseMinerModalOpen, setPurchaseMinerModalOpen] = useState(false);
|
const [purchaseMinerModalOpen, setPurchaseMinerModalOpen] = useState(false);
|
||||||
const [purchaseMinerForm, setPurchaseMinerForm] = useState({
|
const [purchaseMinerForm, setPurchaseMinerForm] = useState({
|
||||||
offer_id: '',
|
offer_id: '',
|
||||||
@@ -1003,7 +1003,6 @@
|
|||||||
const currentPayouts = walletTransferRows.length ? walletTransferRows : (Array.isArray(currentSettings.payouts) ? currentSettings.payouts : []);
|
const currentPayouts = walletTransferRows.length ? walletTransferRows : (Array.isArray(currentSettings.payouts) ? currentSettings.payouts : []);
|
||||||
const currentWalletWithdrawals = walletWithdrawalRows.length ? walletWithdrawalRows : (Array.isArray(currentSettings.wallet_withdrawals) ? currentSettings.wallet_withdrawals : []);
|
const currentWalletWithdrawals = walletWithdrawalRows.length ? walletWithdrawalRows : (Array.isArray(currentSettings.wallet_withdrawals) ? currentSettings.wallet_withdrawals : []);
|
||||||
const currentWalletSnapshots = Array.isArray(payload?.wallet_snapshots) ? payload.wallet_snapshots : [];
|
const currentWalletSnapshots = Array.isArray(payload?.wallet_snapshots) ? payload.wallet_snapshots : [];
|
||||||
const currentMinerOffers = Array.isArray(currentSettings.miner_offers) ? currentSettings.miner_offers : [];
|
|
||||||
const currentPurchasedMiners = Array.isArray(currentSettings.purchased_miners) ? currentSettings.purchased_miners : [];
|
const currentPurchasedMiners = Array.isArray(currentSettings.purchased_miners) ? currentSettings.purchased_miners : [];
|
||||||
const currentMiningCurrency = String((latest && latest.coin_currency) || currentSettings.crypto_currency || 'DOGE').toUpperCase();
|
const currentMiningCurrency = String((latest && latest.coin_currency) || currentSettings.crypto_currency || 'DOGE').toUpperCase();
|
||||||
const latestMeasurementDate = latest && latest.measured_at ? parseStoredUtcDate(latest.measured_at) : null;
|
const latestMeasurementDate = latest && latest.measured_at ? parseStoredUtcDate(latest.measured_at) : null;
|
||||||
@@ -1020,12 +1019,6 @@
|
|||||||
: `Verwendet den letzten Upload von ${fmtDate(latest.measured_at)} mit ${fmtNumber(fullTransferCoins, 6)} ${currentMiningCurrency}. Backend erlaubt den Kompletttransfer nur, wenn dieser Upload hoechstens 3 Minuten alt ist.`;
|
: `Verwendet den letzten Upload von ${fmtDate(latest.measured_at)} mit ${fmtNumber(fullTransferCoins, 6)} ${currentMiningCurrency}. Backend erlaubt den Kompletttransfer nur, wenn dieser Upload hoechstens 3 Minuten alt ist.`;
|
||||||
const latestWalletSnapshot = currentWalletSnapshots.length ? currentWalletSnapshots[0] : null;
|
const latestWalletSnapshot = currentWalletSnapshots.length ? currentWalletSnapshots[0] : null;
|
||||||
const currentWalletMiningBalance = walletAssetBalance(latestWalletSnapshot, currentMiningCurrency);
|
const currentWalletMiningBalance = walletAssetBalance(latestWalletSnapshot, currentMiningCurrency);
|
||||||
const renewableOfferIds = new Set(
|
|
||||||
currentMinerOffers
|
|
||||||
.filter((offer) => !!offer.auto_renew)
|
|
||||||
.map((offer) => Number(offer.id))
|
|
||||||
.filter((value) => Number.isFinite(value) && value > 0)
|
|
||||||
);
|
|
||||||
const preferredCurrencyCodes = Array.isArray(currentSettings.preferred_currencies)
|
const preferredCurrencyCodes = Array.isArray(currentSettings.preferred_currencies)
|
||||||
? currentSettings.preferred_currencies.map((code) => String(code || '').toUpperCase()).filter(Boolean)
|
? currentSettings.preferred_currencies.map((code) => String(code || '').toUpperCase()).filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
@@ -1034,8 +1027,10 @@
|
|||||||
? currencies.filter((currency) => preferredCurrencySet.has(String(currency.code || '').toUpperCase()))
|
? currencies.filter((currency) => preferredCurrencySet.has(String(currency.code || '').toUpperCase()))
|
||||||
: currencies;
|
: currencies;
|
||||||
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
||||||
const evaluatedMinerOffers = Array.isArray(payload?.summary?.miner_offers) ? payload.summary.miner_offers : [];
|
const evaluatedMinerOffers = Array.isArray(previewMinerOffers) ? previewMinerOffers : [];
|
||||||
const selectedOfferType = String(minerOfferFilters.offer_type || 'crypto').toLowerCase() === 'fiat' ? 'fiat' : 'crypto';
|
const selectedOfferType = String(minerOfferFilters.offer_type || 'crypto').toLowerCase() === 'fiat' ? 'fiat' : 'crypto';
|
||||||
|
const hasCryptoOfferBasis = Number(offerPreviewForm.crypto_base_price_amount) > 0;
|
||||||
|
const hasFiatOfferBasis = Number(offerPreviewForm.fiat_base_price_amount) > 0;
|
||||||
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
||||||
const paymentType = String(offer.payment_type || '').toLowerCase();
|
const paymentType = String(offer.payment_type || '').toLowerCase();
|
||||||
const speedMin = minerOfferFilters.speed_min === '' ? null : Number(minerOfferFilters.speed_min);
|
const speedMin = minerOfferFilters.speed_min === '' ? null : Number(minerOfferFilters.speed_min);
|
||||||
@@ -1111,7 +1106,7 @@
|
|||||||
? 'crypto'
|
? 'crypto'
|
||||||
: 'fiat',
|
: 'fiat',
|
||||||
is_active: miner.is_active !== false,
|
is_active: miner.is_active !== false,
|
||||||
can_toggle_auto_renew: Number(miner.runtime_months) > 0 && renewableOfferIds.has(Number(miner.miner_offer_id)),
|
can_toggle_auto_renew: Number(miner.runtime_months) > 0,
|
||||||
hashrate_text: formatHashrateWithBonus(miner.mining_speed_value, miner.mining_speed_unit, miner.bonus_speed_value, miner.bonus_speed_unit),
|
hashrate_text: formatHashrateWithBonus(miner.mining_speed_value, miner.mining_speed_unit, miner.bonus_speed_value, miner.bonus_speed_unit),
|
||||||
type_label: 'Aus Angebot gemietet',
|
type_label: 'Aus Angebot gemietet',
|
||||||
})).concat(currentCostPlans.map((plan) => ({
|
})).concat(currentCostPlans.map((plan) => ({
|
||||||
@@ -1149,6 +1144,63 @@
|
|||||||
}
|
}
|
||||||
}, [scenarioMinerOffers, selectedMinerScenarioId]);
|
}, [scenarioMinerOffers, selectedMinerScenarioId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOfferPreviewForm((current) => {
|
||||||
|
const cryptoCurrency = String(currentSettings.crypto_currency || 'DOGE').toUpperCase();
|
||||||
|
const selectedCurrency = String(current.crypto_base_price_currency || 'USD').toUpperCase();
|
||||||
|
if (selectedCurrency === 'USD' || selectedCurrency === cryptoCurrency) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
crypto_base_price_currency: 'USD',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [currentSettings.crypto_currency]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const shouldPreview = selectedOfferType === 'crypto'
|
||||||
|
? Number(offerPreviewForm.crypto_base_price_amount) > 0
|
||||||
|
: Number(offerPreviewForm.fiat_base_price_amount) > 0 && Number(minerOfferFilters.speed_min) > 0;
|
||||||
|
if (!shouldPreview) {
|
||||||
|
setPreviewMinerOffers([]);
|
||||||
|
setPreviewOffersLoading(false);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutId = window.setTimeout(async () => {
|
||||||
|
setPreviewOffersLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/offer-preview`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
crypto_base_price_amount: offerPreviewForm.crypto_base_price_amount || null,
|
||||||
|
crypto_base_price_currency: offerPreviewForm.crypto_base_price_currency || 'USD',
|
||||||
|
fiat_base_price_amount: offerPreviewForm.fiat_base_price_amount || null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
setPreviewMinerOffers(Array.isArray(data?.miner_offers) ? data.miner_offers : []);
|
||||||
|
} catch (err) {
|
||||||
|
setPreviewMinerOffers([]);
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setPreviewOffersLoading(false);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timeoutId);
|
||||||
|
}, [
|
||||||
|
apiBase,
|
||||||
|
offerPreviewForm.crypto_base_price_amount,
|
||||||
|
offerPreviewForm.crypto_base_price_currency,
|
||||||
|
offerPreviewForm.fiat_base_price_amount,
|
||||||
|
minerOfferFilters.speed_min,
|
||||||
|
projectKey,
|
||||||
|
selectedOfferType,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!purchaseMinerModalOpen) {
|
if (!purchaseMinerModalOpen) {
|
||||||
return;
|
return;
|
||||||
@@ -1164,7 +1216,7 @@
|
|||||||
setPurchaseMinerForm((current) => ({
|
setPurchaseMinerForm((current) => ({
|
||||||
...current,
|
...current,
|
||||||
offer_id: current.offer_id || String(selectedOffer.id),
|
offer_id: current.offer_id || String(selectedOffer.id),
|
||||||
base_offer_id: current.base_offer_id || String(selectedOffer.base_offer_id || selectedOffer.id || ''),
|
base_offer_id: current.base_offer_id || String(selectedOffer.id || ''),
|
||||||
label: current.label || String(selectedOffer.label || ''),
|
label: current.label || String(selectedOffer.label || ''),
|
||||||
mining_speed_value: current.mining_speed_value || String(selectedOffer.mining_speed_value || ''),
|
mining_speed_value: current.mining_speed_value || String(selectedOffer.mining_speed_value || ''),
|
||||||
mining_speed_unit: current.mining_speed_unit || String(selectedOffer.mining_speed_unit || ''),
|
mining_speed_unit: current.mining_speed_unit || String(selectedOffer.mining_speed_unit || ''),
|
||||||
@@ -2110,41 +2162,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitMinerOffer(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
setSaving(true);
|
|
||||||
setError('');
|
|
||||||
try {
|
|
||||||
const offerPayload = {
|
|
||||||
...minerOfferForm,
|
|
||||||
base_price_currency: minerOfferForm.payment_type === 'crypto'
|
|
||||||
? (minerOfferForm.base_price_currency || 'USD')
|
|
||||||
: 'EUR',
|
|
||||||
};
|
|
||||||
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/miner-offers`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(offerPayload),
|
|
||||||
});
|
|
||||||
setMessage('Miner-Angebot gespeichert.');
|
|
||||||
setMinerOfferForm({
|
|
||||||
label: '',
|
|
||||||
base_price_amount: '',
|
|
||||||
base_price_currency: 'USD',
|
|
||||||
payment_type: 'crypto',
|
|
||||||
auto_renew: false,
|
|
||||||
note: '',
|
|
||||||
is_active: true,
|
|
||||||
});
|
|
||||||
setMinerOfferModalOpen(false);
|
|
||||||
await loadBootstrap(projectKey);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function purchaseMinerOffer(offerId, overrides) {
|
async function purchaseMinerOffer(offerId, overrides) {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
setError('');
|
setError('');
|
||||||
@@ -2192,7 +2209,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await purchaseMinerOffer(String(selectedOffer.base_offer_id || selectedOffer.id), {
|
await purchaseMinerOffer(String(selectedOffer.id), {
|
||||||
|
computed_offer: selectedOffer,
|
||||||
label: purchaseMinerForm.label || null,
|
label: purchaseMinerForm.label || null,
|
||||||
mining_speed_value: purchaseMinerForm.mining_speed_value || null,
|
mining_speed_value: purchaseMinerForm.mining_speed_value || null,
|
||||||
mining_speed_unit: purchaseMinerForm.mining_speed_unit || null,
|
mining_speed_unit: purchaseMinerForm.mining_speed_unit || null,
|
||||||
@@ -3055,15 +3073,13 @@
|
|||||||
: 'Crypto-Angebote sind aktiv. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.')
|
: 'Crypto-Angebote sind aktiv. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.')
|
||||||
: 'Fiat-Angebote sind aktiv. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.',
|
: 'Fiat-Angebote sind aktiv. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.',
|
||||||
[
|
[
|
||||||
h('div', { key: 'actions', className: 'mc-inline-row' }, [
|
|
||||||
h('button', {
|
|
||||||
key: 'add-offer',
|
|
||||||
type: 'button',
|
|
||||||
className: 'mc-button mc-button--secondary',
|
|
||||||
onClick: () => setMinerOfferModalOpen(true),
|
|
||||||
}, 'Basis-Angebot anlegen'),
|
|
||||||
]),
|
|
||||||
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
||||||
|
selectedOfferType === 'crypto'
|
||||||
|
? inputField('Basispreis fuer 50 kH/s auf 3 Monate', 'number', offerPreviewForm.crypto_base_price_amount, (value) => setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_amount: value }), '0.000001')
|
||||||
|
: inputField('Basispreis fuer 100 kH/s auf 3 Monate in EUR', 'number', offerPreviewForm.fiat_base_price_amount, (value) => setOfferPreviewForm({ ...offerPreviewForm, fiat_base_price_amount: value }), '0.000001'),
|
||||||
|
selectedOfferType === 'crypto'
|
||||||
|
? selectField('Basiswaehrung', offerPreviewForm.crypto_base_price_currency || 'USD', Array.from(new Set(['USD', String(currentSettings.crypto_currency || 'DOGE').toUpperCase()])), (value) => setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_currency: value || 'USD' }))
|
||||||
|
: displayField('Basiswaehrung', 'EUR'),
|
||||||
selectField('Angebotsart', minerOfferFilters.offer_type, [
|
selectField('Angebotsart', minerOfferFilters.offer_type, [
|
||||||
{ value: 'crypto', label: 'Crypto' },
|
{ value: 'crypto', label: 'Crypto' },
|
||||||
{ value: 'fiat', label: 'Fiat' },
|
{ value: 'fiat', label: 'Fiat' },
|
||||||
@@ -3140,7 +3156,7 @@
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
setPurchaseMinerForm({
|
setPurchaseMinerForm({
|
||||||
offer_id: String(offer.id),
|
offer_id: String(offer.id),
|
||||||
base_offer_id: String(offer.base_offer_id || offer.id || ''),
|
base_offer_id: String(offer.id || ''),
|
||||||
purchased_at: nowDateTimeLocalValue(),
|
purchased_at: nowDateTimeLocalValue(),
|
||||||
label: String(offer.label || ''),
|
label: String(offer.label || ''),
|
||||||
mining_speed_value: String(offer.mining_speed_value || ''),
|
mining_speed_value: String(offer.mining_speed_value || ''),
|
||||||
@@ -3159,7 +3175,15 @@
|
|||||||
}, 'Mieten'),
|
}, 'Mieten'),
|
||||||
]),
|
]),
|
||||||
]))
|
]))
|
||||||
: [h('tr', { key: 'empty' }, h('td', { colSpan: 7 }, selectedOfferType === 'fiat' && minerOfferFilters.speed_min === '' ? 'Bitte zuerst eine Geschwindigkeit eingeben, damit Fiat-Angebote berechnet werden.' : 'Keine Angebote passen auf die gesetzten Filter.'))]
|
: [h('tr', { key: 'empty' }, h('td', { colSpan: 7 }, previewOffersLoading
|
||||||
|
? 'Berechne Angebote …'
|
||||||
|
: !hasCryptoOfferBasis && selectedOfferType === 'crypto'
|
||||||
|
? 'Bitte zuerst direkt hier den Crypto-Basispreis fuer 50 kH/s auf 3 Monate eingeben.'
|
||||||
|
: !hasFiatOfferBasis && selectedOfferType === 'fiat'
|
||||||
|
? 'Bitte zuerst direkt hier den Fiat-Basispreis fuer 100 kH/s auf 3 Monate eingeben.'
|
||||||
|
: selectedOfferType === 'fiat' && minerOfferFilters.speed_min === ''
|
||||||
|
? 'Bitte zuerst eine Geschwindigkeit eingeben, damit Fiat-Angebote berechnet werden.'
|
||||||
|
: 'Keine Angebote passen auf die gesetzten Filter.'))]
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@@ -3317,38 +3341,6 @@
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
], () => setWalletWithdrawalModalOpen(false)) : null,
|
], () => setWalletWithdrawalModalOpen(false)) : null,
|
||||||
minerOfferModalOpen ? renderModal('Basis-Miner-Angebot anlegen', [
|
|
||||||
h('form', { key: 'form', className: 'mc-form', onSubmit: submitMinerOffer }, [
|
|
||||||
inputField('Label', 'text', minerOfferForm.label, (value) => setMinerOfferForm({ ...minerOfferForm, label: value })),
|
|
||||||
displayField('Basis-Geschwindigkeit', baseOfferSpeedLabel(minerOfferForm.payment_type)),
|
|
||||||
displayField('Basis-Laufzeit', '3 Monate'),
|
|
||||||
displayField('Ableitung', baseOfferDerivationLabel(minerOfferForm.payment_type)),
|
|
||||||
inputField(baseOfferPriceLabel(minerOfferForm.payment_type, currentSettings.crypto_currency), 'number', minerOfferForm.base_price_amount, (value) => setMinerOfferForm({ ...minerOfferForm, base_price_amount: value }), '0.000001'),
|
|
||||||
selectField('Zahlungsart', minerOfferForm.payment_type, [{ value: 'fiat', label: 'FIAT' }, { value: 'crypto', label: 'Krypto' }], (value) => setMinerOfferForm({
|
|
||||||
...minerOfferForm,
|
|
||||||
payment_type: value,
|
|
||||||
base_price_currency: value === 'crypto'
|
|
||||||
? 'USD'
|
|
||||||
: 'EUR',
|
|
||||||
})),
|
|
||||||
minerOfferForm.payment_type === 'crypto'
|
|
||||||
? selectField('Basiswaehrung', minerOfferForm.base_price_currency, Array.from(new Set(['USD', String(currentSettings.crypto_currency || 'DOGE').toUpperCase()])), (value) => setMinerOfferForm({ ...minerOfferForm, base_price_currency: value || 'USD' }))
|
|
||||||
: displayField('Basiswaehrung', 'EUR'),
|
|
||||||
h('label', { className: 'mc-checkbox' }, [
|
|
||||||
h('input', { type: 'checkbox', checked: !!minerOfferForm.auto_renew, onChange: (event) => setMinerOfferForm({ ...minerOfferForm, auto_renew: event.target.checked }) }),
|
|
||||||
'Automatische Verlängerung',
|
|
||||||
]),
|
|
||||||
textareaField('Notiz', minerOfferForm.note, (value) => setMinerOfferForm({ ...minerOfferForm, note: value })),
|
|
||||||
h('label', { className: 'mc-checkbox' }, [
|
|
||||||
h('input', { type: 'checkbox', checked: !!minerOfferForm.is_active, onChange: (event) => setMinerOfferForm({ ...minerOfferForm, is_active: event.target.checked }) }),
|
|
||||||
'Als verfuegbar markieren',
|
|
||||||
]),
|
|
||||||
h('div', { className: 'mc-inline-row' }, [
|
|
||||||
h('button', { type: 'button', className: 'mc-button mc-button--ghost', onClick: () => setMinerOfferModalOpen(false) }, 'Abbrechen'),
|
|
||||||
h('button', { type: 'submit', className: 'mc-button mc-button--secondary', disabled: saving }, saving ? 'Speichert …' : 'Angebot speichern'),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
], () => setMinerOfferModalOpen(false)) : null,
|
|
||||||
purchaseMinerModalOpen ? renderModal('Neuen Miner mieten', [
|
purchaseMinerModalOpen ? renderModal('Neuen Miner mieten', [
|
||||||
h('form', { key: 'form', className: 'mc-form', onSubmit: submitPurchaseMiner }, [
|
h('form', { key: 'form', className: 'mc-form', onSubmit: submitPurchaseMiner }, [
|
||||||
selectField('Angebot', purchaseMinerForm.offer_id, [{ value: '', label: 'Bitte waehlen' }].concat(scenarioMinerOffers.map((offer) => ({
|
selectField('Angebot', purchaseMinerForm.offer_id, [{ value: '', label: 'Bitte waehlen' }].concat(scenarioMinerOffers.map((offer) => ({
|
||||||
@@ -3358,7 +3350,7 @@
|
|||||||
const offer = scenarioMinerOffers.find((item) => String(item.id) === String(value));
|
const offer = scenarioMinerOffers.find((item) => String(item.id) === String(value));
|
||||||
setPurchaseMinerForm({
|
setPurchaseMinerForm({
|
||||||
offer_id: value,
|
offer_id: value,
|
||||||
base_offer_id: offer ? String(offer.base_offer_id || offer.id || '') : '',
|
base_offer_id: offer ? String(offer.id || '') : '',
|
||||||
purchased_at: purchaseMinerForm.purchased_at || nowDateTimeLocalValue(),
|
purchased_at: purchaseMinerForm.purchased_at || nowDateTimeLocalValue(),
|
||||||
label: offer ? String(offer.label || '') : '',
|
label: offer ? String(offer.label || '') : '',
|
||||||
mining_speed_value: offer && offer.mining_speed_value !== null && offer.mining_speed_value !== undefined ? String(offer.mining_speed_value) : '',
|
mining_speed_value: offer && offer.mining_speed_value !== null && offer.mining_speed_value !== undefined ? String(offer.mining_speed_value) : '',
|
||||||
@@ -3395,19 +3387,7 @@
|
|||||||
targetModalOpen ? renderModal('Ziel anlegen', [
|
targetModalOpen ? renderModal('Ziel anlegen', [
|
||||||
h('form', { key: 'form', className: 'mc-form', onSubmit: submitTarget }, [
|
h('form', { key: 'form', className: 'mc-form', onSubmit: submitTarget }, [
|
||||||
inputField('Label', 'text', targetForm.label, (value) => setTargetForm({ ...targetForm, label: value })),
|
inputField('Label', 'text', targetForm.label, (value) => setTargetForm({ ...targetForm, label: value })),
|
||||||
selectField('Angebots-Verknuepfung', targetForm.miner_offer_id || '', [{ value: '', label: 'Kein verknuepftes Angebot' }].concat(currentMinerOffers.map((offer) => ({
|
displayField('Angebots-Verknuepfung', 'Direkte Angebots-Links sind bei Live-Berechnung deaktiviert.'),
|
||||||
value: String(offer.id),
|
|
||||||
label: `${offer.label} · ${fmtNumber(offer.base_price_amount, 6)} ${offer.base_price_currency}`,
|
|
||||||
}))), (value) => {
|
|
||||||
const offer = currentMinerOffers.find((item) => String(item.id) === String(value));
|
|
||||||
setTargetForm({
|
|
||||||
...targetForm,
|
|
||||||
miner_offer_id: value,
|
|
||||||
label: targetForm.label || offer?.label || '',
|
|
||||||
target_amount_fiat: offer ? String(offer.base_price_amount ?? offer.effective_price_amount ?? '') : targetForm.target_amount_fiat,
|
|
||||||
currency: offer?.base_price_currency || offer?.effective_price_currency || targetForm.currency,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
inputField('Betrag', 'number', targetForm.target_amount_fiat, (value) => setTargetForm({ ...targetForm, target_amount_fiat: value }), '0.01'),
|
inputField('Betrag', 'number', targetForm.target_amount_fiat, (value) => setTargetForm({ ...targetForm, target_amount_fiat: value }), '0.01'),
|
||||||
selectField('Waehrung', targetForm.currency, selectableCurrencies.map((currency) => currency.code), (value) => setTargetForm({ ...targetForm, currency: value })),
|
selectField('Waehrung', targetForm.currency, selectableCurrencies.map((currency) => currency.code), (value) => setTargetForm({ ...targetForm, currency: value })),
|
||||||
inputField('Sortierung', 'number', String(targetForm.sort_order), (value) => setTargetForm({ ...targetForm, sort_order: Number(value) || 0 })),
|
inputField('Sortierung', 'number', String(targetForm.sort_order), (value) => setTargetForm({ ...targetForm, sort_order: Number(value) || 0 })),
|
||||||
@@ -3684,22 +3664,6 @@
|
|||||||
return `${label ? label + ' ' : ''}${fmtNumber(value, 4)} ${unit}`;
|
return `${label ? label + ' ' : ''}${fmtNumber(value, 4)} ${unit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function baseOfferSpeedLabel(paymentType) {
|
|
||||||
return String(paymentType || 'fiat') === 'crypto' ? '50 kH/s' : '100 kH/s';
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseOfferPriceLabel(paymentType, cryptoCurrency) {
|
|
||||||
return String(paymentType || 'fiat') === 'crypto'
|
|
||||||
? `Basispreis fuer 50 kH/s auf 3 Monate in USD oder ${String(cryptoCurrency || 'DOGE').toUpperCase()}`
|
|
||||||
: 'Basispreis fuer 100 kH/s auf 3 Monate in EUR';
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseOfferDerivationLabel(paymentType) {
|
|
||||||
return String(paymentType || 'fiat') === 'crypto'
|
|
||||||
? 'Varianten fuer 3, 6, 12, 18, 24 und 36 Monate mit festen Rabatten und Bonuswerten'
|
|
||||||
: 'Varianten fuer 3, 6 und 12 Monate mit festen Rabatten und Bonuswerten';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatHashrateWithBonus(speedValue, speedUnit, bonusValue, bonusUnit) {
|
function formatHashrateWithBonus(speedValue, speedUnit, bonusValue, bonusUnit) {
|
||||||
const parts = [
|
const parts = [
|
||||||
formatSpeed(speedValue, speedUnit, 'Basis'),
|
formatSpeed(speedValue, speedUnit, 'Basis'),
|
||||||
|
|||||||
@@ -302,6 +302,10 @@ final class Router
|
|||||||
Http::json(['data' => $this->saveMinerOffer($projectKey, Http::input())], 201);
|
Http::json(['data' => $this->saveMinerOffer($projectKey, Http::input())], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($resource === 'offer-preview' && $method === 'POST') {
|
||||||
|
Http::json(['data' => $this->offerPreview($projectKey, Http::input())]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($resource === 'purchased-miners' && $method === 'GET') {
|
if ($resource === 'purchased-miners' && $method === 'GET') {
|
||||||
Http::json(['data' => $this->purchasedMiners($projectKey)]);
|
Http::json(['data' => $this->purchasedMiners($projectKey)]);
|
||||||
}
|
}
|
||||||
@@ -310,9 +314,9 @@ final class Router
|
|||||||
Http::json(['data' => $this->updatePurchasedMiner($projectKey, (int) $matches[1], Http::input())]);
|
Http::json(['data' => $this->updatePurchasedMiner($projectKey, (int) $matches[1], Http::input())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('~^miner-offers/(\d+)/purchase$~', $resource, $matches) && $method === 'POST') {
|
if (preg_match('~^miner-offers/([^/]+)/purchase$~', $resource, $matches) && $method === 'POST') {
|
||||||
$this->repository()->ensureProject($projectKey);
|
$this->repository()->ensureProject($projectKey);
|
||||||
Http::json(['data' => $this->purchaseMiner($projectKey, (int) $matches[1], Http::input())], 201);
|
Http::json(['data' => $this->purchaseMiner($projectKey, rawurldecode((string) $matches[1]), Http::input())], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ApiException('Ressource nicht gefunden.', 404, ['resource' => $resource, 'method' => $method]);
|
throw new ApiException('Ressource nicht gefunden.', 404, ['resource' => $resource, 'method' => $method]);
|
||||||
@@ -1645,7 +1649,59 @@ final class Router
|
|||||||
|
|
||||||
private function minerOffers(string $projectKey): array
|
private function minerOffers(string $projectKey): array
|
||||||
{
|
{
|
||||||
return $this->repository()->listMinerOffers($projectKey);
|
$settings = $this->settings($projectKey, [
|
||||||
|
'cost_plans' => true,
|
||||||
|
'currencies' => false,
|
||||||
|
'payouts' => true,
|
||||||
|
'wallet_withdrawals' => true,
|
||||||
|
'miner_offers' => false,
|
||||||
|
'purchased_miners' => true,
|
||||||
|
]);
|
||||||
|
$measurements = $this->measurements($projectKey, $settings);
|
||||||
|
$summary = $this->analytics()->buildSummary($measurements, $settings, [], [
|
||||||
|
'include_offer_scenarios' => true,
|
||||||
|
'include_long_term_projection' => true,
|
||||||
|
]);
|
||||||
|
return is_array($summary['miner_offers'] ?? null) ? $summary['miner_offers'] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function offerPreview(string $projectKey, array $input): array
|
||||||
|
{
|
||||||
|
$settings = $this->settings($projectKey, [
|
||||||
|
'cost_plans' => true,
|
||||||
|
'currencies' => false,
|
||||||
|
'payouts' => true,
|
||||||
|
'wallet_withdrawals' => true,
|
||||||
|
'miner_offers' => false,
|
||||||
|
'purchased_miners' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings['crypto_base_price_amount'] = $this->optionalDecimal($input['crypto_base_price_amount'] ?? null);
|
||||||
|
$settings['crypto_base_price_currency'] = $this->requiredCurrency($input['crypto_base_price_currency'] ?? 'USD', 'crypto_base_price_currency');
|
||||||
|
$settings['fiat_base_price_amount'] = $this->optionalDecimal($input['fiat_base_price_amount'] ?? null);
|
||||||
|
|
||||||
|
if ($settings['crypto_base_price_amount'] !== null) {
|
||||||
|
if ($settings['crypto_base_price_amount'] <= 0) {
|
||||||
|
throw new ApiException('crypto_base_price_amount muss groesser als 0 sein.', 422, ['field' => 'crypto_base_price_amount']);
|
||||||
|
}
|
||||||
|
if (!in_array($settings['crypto_base_price_currency'], ['USD', $settings['crypto_currency']], true)) {
|
||||||
|
throw new ApiException('crypto_base_price_currency muss USD oder die Projekt-Kryptowaehrung sein.', 422, ['field' => 'crypto_base_price_currency']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['fiat_base_price_amount'] !== null && $settings['fiat_base_price_amount'] <= 0) {
|
||||||
|
throw new ApiException('fiat_base_price_amount muss groesser als 0 sein.', 422, ['field' => 'fiat_base_price_amount']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$measurements = $this->measurements($projectKey, $settings);
|
||||||
|
$summary = $this->analytics()->buildSummary($measurements, $settings, [], [
|
||||||
|
'include_offer_scenarios' => true,
|
||||||
|
'include_long_term_projection' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'miner_offers' => is_array($summary['miner_offers'] ?? null) ? $summary['miner_offers'] : [],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function purchasedMiners(string $projectKey): array
|
private function purchasedMiners(string $projectKey): array
|
||||||
@@ -1905,56 +1961,17 @@ final class Router
|
|||||||
|
|
||||||
private function saveMinerOffer(string $projectKey, array $input): array
|
private function saveMinerOffer(string $projectKey, array $input): array
|
||||||
{
|
{
|
||||||
$paymentType = $this->enumValue($input['payment_type'] ?? 'fiat', ['fiat', 'crypto'], 'payment_type');
|
throw new ApiException(
|
||||||
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
'Basis-Angebote werden nicht mehr in der Datenbank gespeichert. Bitte die Basispreise direkt im Bereich Miner-Angebote eingeben.',
|
||||||
$projectCryptoCurrency = $this->requiredCurrency($this->settings($projectKey, [
|
410
|
||||||
'cost_plans' => false,
|
);
|
||||||
'currencies' => false,
|
|
||||||
'payouts' => false,
|
|
||||||
'wallet_withdrawals' => false,
|
|
||||||
'miner_offers' => false,
|
|
||||||
'purchased_miners' => false,
|
|
||||||
])['crypto_currency'] ?? 'DOGE', 'crypto_currency');
|
|
||||||
$basePriceCurrency = $this->requiredCurrency($input['base_price_currency'] ?? ($input['reference_price_currency'] ?? $input['price_currency'] ?? null), 'base_price_currency');
|
|
||||||
$payload = [
|
|
||||||
'label' => $this->requiredString($input['label'] ?? null, 'label', 120),
|
|
||||||
'runtime_months' => 3,
|
|
||||||
'mining_speed_value' => $baseSpeed['value'],
|
|
||||||
'mining_speed_unit' => $baseSpeed['unit'],
|
|
||||||
'bonus_speed_value' => null,
|
|
||||||
'bonus_speed_unit' => null,
|
|
||||||
'bonus_percent' => null,
|
|
||||||
'base_price_amount' => $this->requiredDecimal($input['base_price_amount'] ?? ($input['reference_price_amount'] ?? $input['price_amount'] ?? null), 'base_price_amount'),
|
|
||||||
'base_price_currency' => $basePriceCurrency,
|
|
||||||
'payment_type' => $paymentType,
|
|
||||||
'auto_renew' => !empty($input['auto_renew']) ? 1 : 0,
|
|
||||||
'note' => $this->optionalString($input['note'] ?? null, 1000),
|
|
||||||
'is_active' => !empty($input['is_active']) ? 1 : 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($paymentType === 'crypto') {
|
|
||||||
$allowedCryptoCurrencies = array_values(array_unique(['USD', $projectCryptoCurrency]));
|
|
||||||
if (!in_array($payload['base_price_currency'], $allowedCryptoCurrencies, true)) {
|
|
||||||
throw new ApiException('Bei Krypto-Angeboten sind nur USD oder die Projekt-Kryptowaehrung erlaubt.', 422, [
|
|
||||||
'field' => 'base_price_currency',
|
|
||||||
'allowed' => $allowedCryptoCurrencies,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($payload['base_price_currency'] !== 'EUR') {
|
|
||||||
throw new ApiException('Bei FIAT-Angeboten ist aktuell nur EUR als Basiswaehrung erlaubt.', 422, [
|
|
||||||
'field' => 'base_price_currency',
|
|
||||||
'allowed' => ['EUR'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->repository()->saveMinerOffer($projectKey, $payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function purchaseMiner(string $projectKey, int $offerId, array $input): array
|
private function purchaseMiner(string $projectKey, string $offerId, array $input): array
|
||||||
{
|
{
|
||||||
$offer = $this->repository()->getMinerOffer($projectKey, $offerId);
|
$offer = is_array($input['computed_offer'] ?? null)
|
||||||
|
? $this->normalizeComputedMinerOffer((array) $input['computed_offer'], $offerId)
|
||||||
|
: $this->findComputedMinerOffer($projectKey, $offerId);
|
||||||
if (!is_array($offer)) {
|
if (!is_array($offer)) {
|
||||||
throw new ApiException('Miner-Angebot nicht gefunden.', 404);
|
throw new ApiException('Miner-Angebot nicht gefunden.', 404);
|
||||||
}
|
}
|
||||||
@@ -2001,7 +2018,7 @@ final class Router
|
|||||||
? $this->requiredDateTime($input['purchased_at'], 'purchased_at', $this->projectTimezone($projectKey))
|
? $this->requiredDateTime($input['purchased_at'], 'purchased_at', $this->projectTimezone($projectKey))
|
||||||
: $this->currentTimestamp();
|
: $this->currentTimestamp();
|
||||||
|
|
||||||
return $this->repository()->purchaseMiner($projectKey, $offerId, [
|
return $this->repository()->purchaseMiner($projectKey, null, [
|
||||||
'purchased_at' => $purchasedAt,
|
'purchased_at' => $purchasedAt,
|
||||||
'label' => $this->optionalString($input['label'] ?? ($offer['label'] ?? null), 120) ?? $offer['label'],
|
'label' => $this->optionalString($input['label'] ?? ($offer['label'] ?? null), 120) ?? $offer['label'],
|
||||||
'runtime_months' => $offer['runtime_months'] ?? null,
|
'runtime_months' => $offer['runtime_months'] ?? null,
|
||||||
@@ -2031,16 +2048,6 @@ final class Router
|
|||||||
throw new ApiException('Es kann aktuell nur auto_renew geaendert werden.', 422, ['field' => 'auto_renew']);
|
throw new ApiException('Es kann aktuell nur auto_renew geaendert werden.', 422, ['field' => 'auto_renew']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$offerId = is_numeric($miner['miner_offer_id'] ?? null) ? (int) $miner['miner_offer_id'] : null;
|
|
||||||
if ($offerId === null) {
|
|
||||||
throw new ApiException('Dieser Miner kann nicht ueber ein Angebot aktualisiert werden.', 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
$offer = $this->repository()->getMinerOffer($projectKey, $offerId);
|
|
||||||
if (!is_array($offer) || empty($offer['auto_renew'])) {
|
|
||||||
throw new ApiException('Dieser Miner unterstuetzt keine automatische Verlaengerung.', 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->repository()->updatePurchasedMinerAutoRenew(
|
return $this->repository()->updatePurchasedMinerAutoRenew(
|
||||||
$projectKey,
|
$projectKey,
|
||||||
$minerId,
|
$minerId,
|
||||||
@@ -2048,6 +2055,91 @@ final class Router
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function findComputedMinerOffer(string $projectKey, string $offerId): ?array
|
||||||
|
{
|
||||||
|
foreach ($this->minerOffers($projectKey) as $offer) {
|
||||||
|
if ((string) ($offer['id'] ?? '') === $offerId) {
|
||||||
|
return $offer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeComputedMinerOffer(array $offer, string $offerId): array
|
||||||
|
{
|
||||||
|
$resolvedOfferId = trim((string) ($offer['id'] ?? ''));
|
||||||
|
if ($resolvedOfferId !== '' && $resolvedOfferId !== $offerId) {
|
||||||
|
throw new ApiException('computed_offer passt nicht zur angeforderten Offer-ID.', 422, [
|
||||||
|
'field' => 'computed_offer.id',
|
||||||
|
'expected' => $offerId,
|
||||||
|
'actual' => $resolvedOfferId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentType = $this->enumValue($offer['payment_type'] ?? 'fiat', ['fiat', 'crypto'], 'computed_offer.payment_type');
|
||||||
|
$runtimeMonths = $this->requiredPositiveInt($offer['runtime_months'] ?? null, 'computed_offer.runtime_months');
|
||||||
|
$miningSpeedValue = $this->optionalDecimal($offer['mining_speed_value'] ?? null);
|
||||||
|
$miningSpeedUnit = $this->optionalSpeedUnit($offer['mining_speed_unit'] ?? null);
|
||||||
|
if (($miningSpeedValue === null) xor ($miningSpeedUnit === null)) {
|
||||||
|
throw new ApiException('computed_offer muss Mining-Geschwindigkeit und Einheit gemeinsam enthalten.', 422, [
|
||||||
|
'field' => 'computed_offer.mining_speed_value',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bonusPercent = $this->optionalDecimal($offer['bonus_percent'] ?? null);
|
||||||
|
if ($bonusPercent !== null && $bonusPercent < 0) {
|
||||||
|
throw new ApiException('computed_offer.bonus_percent darf nicht negativ sein.', 422, [
|
||||||
|
'field' => 'computed_offer.bonus_percent',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bonusSpeedValue = $this->optionalDecimal($offer['bonus_speed_value'] ?? null);
|
||||||
|
$bonusSpeedUnit = $this->optionalSpeedUnit($offer['bonus_speed_unit'] ?? null);
|
||||||
|
if (($bonusSpeedValue === null) xor ($bonusSpeedUnit === null)) {
|
||||||
|
throw new ApiException('computed_offer muss Bonus-Hashrate und Einheit gemeinsam enthalten.', 422, [
|
||||||
|
'field' => 'computed_offer.bonus_speed_value',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePriceAmount = $this->optionalDecimal($offer['base_price_amount'] ?? null);
|
||||||
|
$basePriceCurrency = $this->optionalCurrency($offer['base_price_currency'] ?? null);
|
||||||
|
$effectivePriceAmount = $this->optionalDecimal($offer['effective_price_amount'] ?? null);
|
||||||
|
$effectivePriceCurrency = $this->optionalCurrency($offer['effective_price_currency'] ?? ($offer['price_currency'] ?? null));
|
||||||
|
$referencePriceAmount = $this->optionalDecimal($offer['reference_price_amount'] ?? $basePriceAmount);
|
||||||
|
$referencePriceCurrency = $this->optionalCurrency($offer['reference_price_currency'] ?? $basePriceCurrency);
|
||||||
|
$usdReferenceAmount = $this->optionalDecimal($offer['usd_reference_amount'] ?? null);
|
||||||
|
|
||||||
|
if ($basePriceAmount === null && $effectivePriceAmount === null) {
|
||||||
|
throw new ApiException('computed_offer muss mindestens einen Preis enthalten.', 422, [
|
||||||
|
'field' => 'computed_offer.effective_price_amount',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge($offer, [
|
||||||
|
'id' => $resolvedOfferId !== '' ? $resolvedOfferId : $offerId,
|
||||||
|
'label' => $this->optionalString($offer['label'] ?? null, 120),
|
||||||
|
'payment_type' => $paymentType,
|
||||||
|
'runtime_months' => $runtimeMonths,
|
||||||
|
'mining_speed_value' => $miningSpeedValue,
|
||||||
|
'mining_speed_unit' => $miningSpeedUnit,
|
||||||
|
'bonus_percent' => $bonusPercent,
|
||||||
|
'bonus_speed_value' => $bonusSpeedValue,
|
||||||
|
'bonus_speed_unit' => $bonusSpeedUnit,
|
||||||
|
'base_price_amount' => $basePriceAmount,
|
||||||
|
'base_price_currency' => $basePriceCurrency,
|
||||||
|
'effective_price_amount' => $effectivePriceAmount,
|
||||||
|
'effective_price_currency' => $effectivePriceCurrency,
|
||||||
|
'price_currency' => $effectivePriceCurrency,
|
||||||
|
'reference_price_amount' => $referencePriceAmount,
|
||||||
|
'reference_price_currency' => $referencePriceCurrency,
|
||||||
|
'usd_reference_amount' => $usdReferenceAmount,
|
||||||
|
'auto_renew' => !empty($offer['auto_renew']),
|
||||||
|
'is_active' => !array_key_exists('is_active', $offer) || !empty($offer['is_active']),
|
||||||
|
'note' => $this->optionalString($offer['note'] ?? null, 1000),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private function saveDashboard(string $projectKey, array $input): array
|
private function saveDashboard(string $projectKey, array $input): array
|
||||||
{
|
{
|
||||||
$payload = [
|
$payload = [
|
||||||
|
|||||||
@@ -1003,7 +1003,7 @@ final class AnalyticsService
|
|||||||
private function expandOfferVariants(array $offers, array $settings): array
|
private function expandOfferVariants(array $offers, array $settings): array
|
||||||
{
|
{
|
||||||
$expanded = [];
|
$expanded = [];
|
||||||
foreach ($offers as $offer) {
|
foreach ($this->configuredOfferBases($settings) as $offer) {
|
||||||
$paymentType = $this->normalizeOfferPaymentType($offer);
|
$paymentType = $this->normalizeOfferPaymentType($offer);
|
||||||
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
||||||
$baseSpeedValue = (float) $baseSpeed['value'];
|
$baseSpeedValue = (float) $baseSpeed['value'];
|
||||||
@@ -1089,6 +1089,39 @@ final class AnalyticsService
|
|||||||
return $expanded;
|
return $expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function configuredOfferBases(array $settings): array
|
||||||
|
{
|
||||||
|
$bases = [];
|
||||||
|
$cryptoAmount = is_numeric($settings['crypto_base_price_amount'] ?? null) ? (float) $settings['crypto_base_price_amount'] : null;
|
||||||
|
$cryptoCurrency = strtoupper(trim((string) ($settings['crypto_base_price_currency'] ?? '')));
|
||||||
|
if ($cryptoAmount !== null && $cryptoAmount > 0 && $cryptoCurrency !== '') {
|
||||||
|
$bases[] = [
|
||||||
|
'id' => 'computed-crypto-base',
|
||||||
|
'label' => 'Crypto Server',
|
||||||
|
'payment_type' => 'crypto',
|
||||||
|
'base_price_amount' => $cryptoAmount,
|
||||||
|
'base_price_currency' => $cryptoCurrency,
|
||||||
|
'auto_renew' => 0,
|
||||||
|
'is_active' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fiatAmount = is_numeric($settings['fiat_base_price_amount'] ?? null) ? (float) $settings['fiat_base_price_amount'] : null;
|
||||||
|
if ($fiatAmount !== null && $fiatAmount > 0) {
|
||||||
|
$bases[] = [
|
||||||
|
'id' => 'computed-fiat-base',
|
||||||
|
'label' => 'Fiat Server',
|
||||||
|
'payment_type' => 'fiat',
|
||||||
|
'base_price_amount' => $fiatAmount,
|
||||||
|
'base_price_currency' => 'EUR',
|
||||||
|
'auto_renew' => 1,
|
||||||
|
'is_active' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bases;
|
||||||
|
}
|
||||||
|
|
||||||
private function normalizeOfferPaymentType(array $offer): string
|
private function normalizeOfferPaymentType(array $offer): string
|
||||||
{
|
{
|
||||||
$paymentType = strtolower(trim((string) ($offer['payment_type'] ?? '')));
|
$paymentType = strtolower(trim((string) ($offer['payment_type'] ?? '')));
|
||||||
|
|||||||
@@ -832,7 +832,7 @@ final class MiningRepository
|
|||||||
return is_array($row) ? $this->normalizeRow($row) : null;
|
return is_array($row) ? $this->normalizeRow($row) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function purchaseMiner(string $projectKey, int $offerId, array $payload): array
|
public function purchaseMiner(string $projectKey, ?int $offerId, array $payload): array
|
||||||
{
|
{
|
||||||
if ($this->driver === 'pgsql') {
|
if ($this->driver === 'pgsql') {
|
||||||
$stmt = $this->pdo->prepare(
|
$stmt = $this->pdo->prepare(
|
||||||
@@ -1430,7 +1430,7 @@ final class MiningRepository
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizePurchasedPayload(string $projectKey, int $offerId, array $payload): array
|
private function normalizePurchasedPayload(string $projectKey, ?int $offerId, array $payload): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'project_key' => $projectKey,
|
'project_key' => $projectKey,
|
||||||
|
|||||||
Reference in New Issue
Block a user