-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.js
160 lines (131 loc) · 6.19 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const express = require("express");
require("dotenv").config();
const app = express();
app.use(express.json()); // ✅ Ensure JSON body parsing
const PORT = process.env.PORT || 3000;
const API_TOKEN = process.env.NANOPAY_TOKEN;
const cors = require("cors");
app.use(cors()); // ✅ Allow frontend requests
// 🔥 Correct `/create-invoice` route
app.post("/create-invoice", async (req, res) => {
console.log("Received request:", req.body); // ✅ Debugging
const { username, amount, message } = req.body;
const streamchannel = process.env.STREAM_CHANNEL; // ✅ Now loaded from .env - shows up as tip description
if (!username || !amount || parseFloat(amount) <= 0) {
return res.status(400).json({ error: "Invalid input" });
}
const recipientAddress = process.env.NANO_ADDRESS; // ✅ Now loaded from .env
const redirectUrl = process.env.REDIRECT_URL; // ✅ Now loaded from .env
try {
const response = await fetch("https://nanopay.me/api/invoices", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_TOKEN}`
},
body: JSON.stringify({
title: `Tip from ${username}`, // ✅ Required field
description: `Sending tip to ${streamchannel}`,
price: parseFloat(amount), // ✅ Keep this
recipient_address: recipientAddress, // ✅ Required field
metadata: { username, message },
redirect_url: redirectUrl
})
});
const data = await response.json();
console.log("NanoPay API Response:", data); // ✅ Debugging log
console.log("Using API Token:", API_TOKEN); // ✅ Debugging
if (data.pay_url) {
res.json({ payment_url: data.pay_url });
} else {
res.status(400).json({ error: "Failed to create invoice", details: data });
}
} catch (error) {
console.error("Error creating invoice:", error);
res.status(500).json({ error: "Server error" });
}
});
// Webhook Endpoint
app.post("/nanopay-webhook", async (req, res) => {
console.log("🔥 Full NanoPay Webhook Data:", JSON.stringify(req.body, null, 2)); // ✅ Log full request data
const { type, invoice, payment } = req.body;
// ✅ Extract values from correct locations
const status = invoice?.status;
const amount_received = payment?.amount; // ✅ Get amount from 'payment.amount'
const metadata = invoice?.metadata;
if (!status) {
console.warn("⚠️ Webhook received, but 'status' is missing.");
return res.sendStatus(400);
}
if (status !== "paid" && status !== "pending") { // ✅ Accept both
console.warn(`⚠️ Webhook received, but status is '${status}', not 'paid' or 'pending'.`);
return res.sendStatus(200);
}
if (!metadata?.username) {
console.warn("⚠️ Webhook received, but missing metadata.username");
return res.sendStatus(400);
}
const username = metadata.username;
const message = metadata.message || "No message";
const amount = parseFloat(amount_received); // ✅ Convert to number
console.log(`✅ Payment received from ${username}: ${amount} XNO`);
// 🚀 Call the function to send an alert to StreamElements
await sendStreamElementsAlert(username, message, amount);
res.sendStatus(200); // Acknowledge webhook received
});
// Send Alert to StreamElements
async function sendStreamElementsAlert(username, message, amount) {
const SE_JWT_TOKEN = process.env.STREAMELEMENTS_JWT; // StreamElements API Token
const SE_CHANNEL_ID = process.env.STREAMELEMENTS_CHANNEL_ID; // Your StreamElements Channel ID
// 🚀 Get real-time Nano (XNO) to USD conversion rate
const exchangeRate = await getNanoToUsdRate();
const amountInUsd = (amount * exchangeRate).toFixed(2); // Convert XNO to USD
const tipData = {
user: {
userId: "", // Optional: StreamElements user ID (if available)
username: username, // ✅ The tipper's username
email: "no-reply@example.com" // Required, but we use a placeholder
},
provider: "NanoPay", // ✅ Identifying the source of the tip
message: `Ӿ${amount} XNO - ${message}`, // ✅ The message sent with the tip
amount: parseFloat(amountInUsd), // ✅ Convert XNO to USD
currency: "USD", // ✅ StreamElements expects a currency field, Use USD since XNO isn't supported.
imported: true // ✅ This tells StreamElements the tip is external (not via SE Pay)
};
try {
const response = await fetch(`https://api.streamelements.com/kappa/v2/tips/${SE_CHANNEL_ID}`, {
method: "POST",
headers: {
"Accept": "application/json; charset=utf-8, application/json",
"Authorization": `Bearer ${SE_JWT_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(tipData)
});
const responseText = await response.text(); // Get raw response for debugging
console.log(`StreamElements Response: ${responseText}`); // ✅ Log full response
if (!response.ok) {
console.error("❌ Failed to send StreamElements tip:", responseText);
} else {
console.log(`✅ StreamElements tip sent for ${username}: ${amount} XNO (~$${amountInUsd} USD)`);
}
} catch (error) {
console.error("❌ Error sending tip to StreamElements:", error);
}
}
// 🔥 Function to get the current Nano (XNO) to USD exchange rate
async function getNanoToUsdRate() {
try {
const response = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=nano&vs_currencies=usd");
const data = await response.json();
return data.nano.usd; // Return Nano price in USD
} catch (error) {
console.error("❌ Failed to get Nano price:", error);
return 5.00; // Fallback rate (manual estimate)
}
}
// Start the server
app.get("/", (req, res) => {
res.send("🚀 Server is running!");
});
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));