Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates more content and the Log method #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.DS_Store
node_modules
.idea
*.log
Empty file added bot-interactions.log
Empty file.
174 changes: 119 additions & 55 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -1,98 +1,162 @@

import { Bot } from "grammy";
import logger from "./logger.js";

const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);

// Map is used to keep track of users who have paid. In a production scenario, replace with a robust database solution.
// Map to track users who have paid
const paidUsers = new Map();

/*
Handles the /start command.
Sends a welcome message to the user and explains the available commands for interacting with the bot.
*/
bot.command("start", (ctx) =>
ctx.reply(
`Welcome! I am a simple bot that can accept payments via Telegram Stars. The following commands are available:
logger.info("Bot is starting...");

/pay - to pay
/status - to check payment status
/refund - to refund payment`,
),
);

/*
Handles the /pay command.
Generates an invoice that users can click to make a payment. The invoice includes the product name, description, and payment options.
Note: Replace "Test Product", "Test description", and other placeholders with actual values in production.
*/
// Log all interactions
bot.use(async (ctx, next) => {
const user = ctx.from ? `${ctx.from.username || ctx.from.id}` : "unknown";
const text = ctx.message?.text || "non-text interaction";
logger.info(`User: ${user}, Interaction: ${text}`);
await next();
});


// Log specific commands
bot.command("start", (ctx) => {
const user = ctx.from.username || ctx.from.id;
logger.info(`User ${user} used /start command.`);
ctx.reply(`Welcome! I am a simple bot that can accept payments via Telegram Stars. The following commands are available:

/pay - to pay
/status - to check payment status
/refund - to refund payment`);
});


// Log when a user initiates payment
bot.command("pay", (ctx) => {
const user = ctx.from.username || ctx.from.id;
logger.info(`User ${user} initiated /pay command.`);
return ctx.replyWithInvoice(
"Test Product", // Product name
"Test description", // Product description
"{}", // Payload (replace with meaningful data)
"XTR", // Currency
[{ amount: 1, label: "Test Product" }], // Price breakdown
"Test Productttttttt", // Product name
"Test description", // Product description
JSON.stringify({ userId: ctx.from.id }), // Payload with user information
"XTR", // Currency
[{ amount: 2, label: "Test Product" }] // Price breakdown
);
});

/*
Handles the pre_checkout_query event.
Telegram sends this event to the bot when a user clicks the payment button.
The bot must respond with answerPreCheckoutQuery within 10 seconds to confirm or cancel the transaction.
*/

// Log pre-checkout queries
bot.on("pre_checkout_query", (ctx) => {
const payload = JSON.parse(ctx.preCheckoutQuery.invoice_payload);
logger.info(`Pre-checkout query received for User ID: ${payload.userId}`);
return ctx.answerPreCheckoutQuery(true).catch(() => {
console.error("answerPreCheckoutQuery failed");
logger.error("Failed to answer pre-checkout query");
});
});

/*
Handles the message:successful_payment event.
This event is triggered when a payment is successfully processed.
Updates the paidUsers map to record the payment details and logs the successful payment.
*/

// Log successful payments
bot.on("message:successful_payment", (ctx) => {
if (!ctx.message || !ctx.message.successful_payment || !ctx.from) {
return;
}

const payment = ctx.message.successful_payment;
logger.info(`Invoice Payload: ${payment.invoice_payload}`);

const userId = ctx.from.id;
const paymentId = payment.telegram_payment_charge_id;
const totalAmount = payment.total_amount;

// Initialize user payments array if not present
if (!paidUsers.has(userId)) {
paidUsers.set(userId, []);
}

// Add the new payment record
paidUsers.get(userId).push({
paymentId,
amount: totalAmount,
timestamp: new Date().toISOString(),
});

paidUsers.set(
ctx.from.id, // User ID
ctx.message.successful_payment.telegram_payment_charge_id, // Payment ID
logger.info(
`Payment successful. User ID: ${userId}, Payment ID: ${paymentId}, Amount: ${totalAmount} Stars`
);

console.log(ctx.message.successful_payment); // Logs payment details
ctx.reply(`Thank you for your payment of ${totalAmount} Stars!`);
});


/*
Handles the /status command.
Checks if the user has made a payment and responds with their payment status.
*/
bot.command("status", (ctx) => {
const message = paidUsers.has(ctx.from.id)
? "You have paid" // User has paid
: "You have not paid yet"; // User has not paid
return ctx.reply(message);
const user = ctx.from.username || ctx.from.id;

if (paidUsers.has(ctx.from.id)) {
const payments = paidUsers.get(ctx.from.id);

// Ensure payments is an array before performing reduce
if (Array.isArray(payments)) {
const totalAmount = payments.reduce((sum, payment) => sum + payment.amount, 0);
logger.info(
`User ${user} checked payment status: User has paid a total of ⭐ ${totalAmount} Stars.`
);
ctx.reply(`You have made ${payments.length} payment(s) totaling ⭐ ${totalAmount} Stars.`);
} else {
logger.error(`Payments for user ${user} is not an array.`);
ctx.reply("There was an error retrieving your payment status. Please try again.");
}
} else {
logger.info(`User ${user} checked payment status: User has not paid.`);
ctx.reply("You have not paid yet.");
}
});


/*
Handles the /refund command.
Refunds the payment made by the user if applicable. If the user hasn't paid, informs them that no refund is possible.
Handles the /refund command.
Refunds the payment made by the user if applicable. If the user hasn't paid, informs them that no refund is possible.
*/
bot.command("refund", (ctx) => {
bot.command("refund", async (ctx) => {
const userId = ctx.from.id;

if (!paidUsers.has(userId)) {
return ctx.reply("You have not paid yet, there is nothing to refund");
logger.info(`User ${userId} requested a refund but has not paid.`);
return ctx.reply("You have not paid yet, there is nothing to refund.");
}

ctx.api
.refundStarPayment(userId, paidUsers.get(userId)) // Initiates the refund
.then(() => {
paidUsers.delete(userId); // Removes the user from the paidUsers map
return ctx.reply("Refund successful");
})
.catch(() => ctx.reply("Refund failed")); // Handles refund errors
const payments = paidUsers.get(userId);
if (!payments || payments.length === 0) {
logger.info(`No valid payments found for refund. User ID: ${userId}`);
return ctx.reply("There are no valid payments to refund.");
}

// Get the most recent payment
const lastPayment = payments[payments.length - 1]; // Peek at the last payment without removing it
logger.info(
`Refund requested for User ID: ${userId}, Payment ID: ${lastPayment.paymentId}, Amount: ${lastPayment.amount}`
);

// Process the refund logic
try {
await ctx.api.refundStarPayment(userId, lastPayment.paymentId); // If supported in your setup
payments.pop(); // Remove the refunded payment from the array
if (payments.length === 0) {
paidUsers.delete(userId); // Remove user if no payments remain
} else {
paidUsers.set(userId, payments); // Update payments map
}

logger.info(`Refund processed successfully for Payment ID: ${lastPayment.paymentId}`);
return ctx.reply(`Refund of ⭐ ${lastPayment.amount} Stars has been successfully processed.`);
} catch (error) {
logger.error(`Refund failed for Payment ID: ${lastPayment.paymentId}, Error: ${error.message}`);
return ctx.reply("Refund failed. Please contact support.");
}
});

// Starts the bot and makes it ready to receive updates and process commands.

// Start the bot
bot.start();
15 changes: 15 additions & 0 deletions logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createLogger, format, transports } from "winston";

const logger = createLogger({
level: "info",
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ level, message, timestamp }) => `${timestamp} [${level}]: ${message}`)
),
transports: [
new transports.Console(),
new transports.File({ filename: "bot-interactions.log" }),
],
});

export default logger; // Use default export
Loading