feat: BTCPay Server payment integration via pow.dgray.io proxy
This commit is contained in:
@@ -1,30 +1,38 @@
|
||||
# PoW Captcha Server
|
||||
# PoW Captcha & Payment Server
|
||||
|
||||
PHP-basierter Proof-of-Work Captcha Server für dgray.io.
|
||||
PHP-basierter Server für dgray.io mit Proof-of-Work Captcha und BTCPay Payment-Proxy.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Subdomain `pow.dgray.io` auf den Server zeigen
|
||||
2. Dateien in das Web-Root kopieren
|
||||
3. Secret setzen:
|
||||
3. Secrets setzen:
|
||||
```bash
|
||||
# In .env oder Apache/Nginx config:
|
||||
SetEnv POW_SECRET $(openssl rand -hex 32)
|
||||
SetEnv BTCPAY_API_KEY your_btcpay_api_key
|
||||
SetEnv BTCPAY_STORE_ID your_btcpay_store_id
|
||||
```
|
||||
Oder direkt in `config.php` den Wert von `POW_SECRET` ändern.
|
||||
Oder direkt in `config.php` die Werte ändern.
|
||||
|
||||
4. Testen:
|
||||
```bash
|
||||
# PoW Challenge
|
||||
curl https://pow.dgray.io/challenge
|
||||
|
||||
# BTCPay Invoice erstellen
|
||||
curl -X POST https://pow.dgray.io/btcpay/invoice \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"listingId": "test-123", "currency": "EUR"}'
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET /challenge
|
||||
Gibt eine signierte Challenge zurück.
|
||||
Gibt eine signierte PoW-Challenge zurück.
|
||||
|
||||
### POST /verify
|
||||
Prüft die Lösung. Body (JSON):
|
||||
Prüft die PoW-Lösung. Body (JSON):
|
||||
```json
|
||||
{
|
||||
"challenge": "...",
|
||||
@@ -35,9 +43,51 @@ Prüft die Lösung. Body (JSON):
|
||||
}
|
||||
```
|
||||
|
||||
### POST /btcpay/invoice
|
||||
Erstellt eine BTCPay Server Invoice für eine Listing-Gebühr.
|
||||
Body (JSON):
|
||||
```json
|
||||
{
|
||||
"listingId": "uuid-string",
|
||||
"currency": "EUR"
|
||||
}
|
||||
```
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"invoiceId": "...",
|
||||
"checkoutLink": "https://pay.xmr.rocks/i/...",
|
||||
"status": "New",
|
||||
"expirationTime": 1700000000
|
||||
}
|
||||
```
|
||||
|
||||
### GET /btcpay/status?id={invoiceId}
|
||||
Prüft den Zahlungsstatus einer Invoice.
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"invoiceId": "...",
|
||||
"status": "New|Processing|Settled|Expired|Invalid",
|
||||
"additionalStatus": "None|PaidLate|PaidPartial|..."
|
||||
}
|
||||
```
|
||||
|
||||
## Gebühren
|
||||
|
||||
| Währung | Betrag |
|
||||
|---------|--------|
|
||||
| EUR | 1 |
|
||||
| USD | 1 |
|
||||
| CHF | 1 |
|
||||
| GBP | 1 |
|
||||
| JPY | 200 |
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- HMAC-SHA256 signierte Challenges (nicht fälschbar)
|
||||
- TTL: 2 Minuten
|
||||
- CORS: nur `https://dgray.io`
|
||||
- `hash_equals()` gegen Timing-Attacks
|
||||
- BTCPay API-Key bleibt serverseitig (nie im Frontend)
|
||||
- Gebühren serverseitig erzwungen (nicht manipulierbar)
|
||||
|
||||
77
docs/pow-server/btcpay-invoice.php
Normal file
77
docs/pow-server/btcpay-invoice.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
require __DIR__ . '/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$listingId = $input['listingId'] ?? null;
|
||||
$currency = $input['currency'] ?? 'EUR';
|
||||
|
||||
if (!$listingId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing listingId']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$fees = LISTING_FEE;
|
||||
if (!isset($fees[$currency])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Unsupported currency', 'supported' => array_keys($fees)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$amount = $fees[$currency];
|
||||
|
||||
$payload = json_encode([
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'metadata' => [
|
||||
'listingId' => $listingId,
|
||||
'orderId' => 'listing-' . $listingId,
|
||||
],
|
||||
]);
|
||||
|
||||
$url = BTCPAY_BASE_URL . '/api/v1/stores/' . BTCPAY_STORE_ID . '/invoices';
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\nAuthorization: token " . BTCPAY_API_KEY . "\r\n",
|
||||
'content' => $payload,
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$response = file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
http_response_code(502);
|
||||
echo json_encode(['error' => 'Failed to connect to payment server']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Extract HTTP status from response headers
|
||||
$statusCode = 500;
|
||||
if (isset($http_response_header[0]) && preg_match('/\d{3}/', $http_response_header[0], $matches)) {
|
||||
$statusCode = (int)$matches[0];
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
http_response_code($statusCode);
|
||||
echo json_encode(['error' => $data['message'] ?? 'Invoice creation failed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'invoiceId' => $data['id'] ?? null,
|
||||
'checkoutLink' => $data['checkoutLink'] ?? null,
|
||||
'status' => $data['status'] ?? null,
|
||||
'expirationTime' => $data['expirationTime'] ?? null,
|
||||
]);
|
||||
54
docs/pow-server/btcpay-status.php
Normal file
54
docs/pow-server/btcpay-status.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require __DIR__ . '/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$invoiceId = $_GET['id'] ?? null;
|
||||
|
||||
if (!$invoiceId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing id parameter']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = BTCPAY_BASE_URL . '/api/v1/stores/' . BTCPAY_STORE_ID . '/invoices/' . urlencode($invoiceId);
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "Authorization: token " . BTCPAY_API_KEY . "\r\n",
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$response = file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
http_response_code(502);
|
||||
echo json_encode(['error' => 'Failed to connect to payment server']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Extract HTTP status from response headers
|
||||
$statusCode = 500;
|
||||
if (isset($http_response_header[0]) && preg_match('/\d{3}/', $http_response_header[0], $matches)) {
|
||||
$statusCode = (int)$matches[0];
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
http_response_code($statusCode);
|
||||
echo json_encode(['error' => $data['message'] ?? 'Failed to fetch invoice status']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'invoiceId' => $data['id'] ?? null,
|
||||
'status' => $data['status'] ?? null,
|
||||
'additionalStatus' => $data['additionalStatus'] ?? null,
|
||||
]);
|
||||
@@ -2,3 +2,9 @@
|
||||
define('POW_SECRET', getenv('POW_SECRET') ?: 'CHANGE_ME_TO_A_RANDOM_64_CHAR_HEX_STRING');
|
||||
define('POW_DIFFICULTY', 4);
|
||||
define('POW_TTL_SECONDS', 120);
|
||||
|
||||
define('BTCPAY_BASE_URL', getenv('BTCPAY_BASE_URL') ?: 'https://pay.xmr.rocks');
|
||||
define('BTCPAY_API_KEY', getenv('BTCPAY_API_KEY') ?: 'CHANGE_ME');
|
||||
define('BTCPAY_STORE_ID', getenv('BTCPAY_STORE_ID') ?: 'CHANGE_ME');
|
||||
define('BTCPAY_WEBHOOK_SECRET', getenv('BTCPAY_WEBHOOK_SECRET') ?: '');
|
||||
define('LISTING_FEE', ['EUR' => 1, 'USD' => 1, 'CHF' => 1, 'GBP' => 1, 'JPY' => 200]);
|
||||
|
||||
@@ -19,6 +19,12 @@ switch ($uri) {
|
||||
case '/verify':
|
||||
require __DIR__ . '/verify.php';
|
||||
break;
|
||||
case '/btcpay/invoice':
|
||||
require __DIR__ . '/btcpay-invoice.php';
|
||||
break;
|
||||
case '/btcpay/status':
|
||||
require __DIR__ . '/btcpay-status.php';
|
||||
break;
|
||||
default:
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Not found']);
|
||||
|
||||
Reference in New Issue
Block a user