-
Notifications
You must be signed in to change notification settings - Fork 130
Fix #556: Add "Mark as pending" button after showing hold invoice #714
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
base: main
Are you sure you want to change the base?
Conversation
…ld invoice - Added 'mark_as_pending' translation in en.yaml and es.yaml - Modified showHoldInvoiceMessage to accept orderId and display buttons - Created markAsPending function to handle marking order as pending - Added markPendingBtn action handler in start.ts - The button appears alongside cancel button after invoice is shown to seller
WalkthroughAdds a "mark as pending" flow: new exported markAsPending command, a Telegram action handler, updates to hold-invoice message to include order-specific inline buttons, and new localization keys in English and Spanish. Changes target order state transitions from WAITING_PAYMENT to PENDING. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
bot/commands.ts(3 hunks)bot/messages.ts(2 hunks)bot/start.ts(2 hunks)locales/en.yaml(1 hunks)locales/es.yaml(1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to models/order*.ts : Enforce Order status lifecycle transitions: PENDING → ACTIVE → FIAT_SENT → COMPLETED; include DISPUTE, CANCELED_BY_ADMIN, EXPIRED, CANCELED
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to models/pendingpayment*.ts : Model PendingPayment as a queue for failed payment retries
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to models/order*.ts : Enforce Order status lifecycle transitions: PENDING → ACTIVE → FIAT_SENT → COMPLETED; include DISPUTE, CANCELED_BY_ADMIN, EXPIRED, CANCELED
Applied to files:
bot/commands.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to jobs/**/*.ts : Implement background jobs for pending payments, order expiration/cleanup, community earnings, node health, and solver availability
Applied to files:
bot/commands.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to models/pendingpayment*.ts : Model PendingPayment as a queue for failed payment retries
Applied to files:
bot/commands.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to bot/start.ts : Implement bot initialization, command registration, and scheduled job setup in bot/start.ts
Applied to files:
bot/start.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to bot/**/*.ts : Use custom Telegraf context types (MainContext, CommunityContext) in handlers and modules
Applied to files:
bot/start.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to bot/modules/*/{commands,actions,messages,scenes,index}.ts : Structure each feature module with commands.ts, actions.ts, messages.ts, scenes.ts, and index.ts
Applied to files:
bot/start.ts
📚 Learning: 2025-08-14T18:53:14.640Z
Learnt from: CR
Repo: lnp2pBot/bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-14T18:53:14.640Z
Learning: Applies to bot/middleware/**/*.ts : Maintain middleware chain order: User validation → Admin checking → Context enhancement → Command routing
Applied to files:
bot/start.ts
🧬 Code graph analysis (3)
bot/commands.ts (6)
bot/modules/community/communityContext.ts (1)
CommunityContext(18-22)models/index.ts (2)
Order(9-9)User(9-9)util/index.ts (1)
getUserI18nContext(615-615)ln/hold_invoice.ts (1)
cancelHoldInvoice(67-67)jobs/cancel_orders.ts (1)
bot(11-124)bot/modules/orders/commands.ts (1)
ctx(113-183)
locales/es.yaml (1)
jobs/check_solvers.ts (1)
community(27-75)
bot/start.ts (4)
bot/middleware/user.ts (1)
userMiddleware(9-19)bot/modules/community/communityContext.ts (1)
CommunityContext(18-22)bot/commands.ts (1)
markAsPending(942-942)bot/modules/dispute/index.ts (1)
bot(7-15)
🔇 Additional comments (8)
locales/en.yaml (1)
672-672: LGTM! Translation key added correctly.The new
mark_as_pendingtranslation key is clear and concise, properly supporting the new "Mark as pending" button functionality.locales/es.yaml (1)
669-669: LGTM! Spanish translation is correct.The translation "Marcar como pendiente" accurately conveys the "Mark as pending" action in Spanish.
bot/commands.ts (2)
461-461: LGTM! Correct parameter addition.Passing
order._idtoshowHoldInvoiceMessageenables the new inline keyboard with "Mark as pending" and "Cancel" buttons.
942-942: Verify authorization before deploying.Ensure the authorization check is added to
markAsPendingbefore this export is used in production, as the current implementation has a critical security vulnerability.bot/start.ts (2)
43-43: Import added correctly.The
markAsPendingimport follows the existing pattern for other command functions.
851-860: Action handler correctly structured, but depends on fixing authorization in markAsPending.The regex pattern
/^markPendingBtn_([0-9a-f]{24})$/properly validates MongoDB ObjectId format, and the handler correctly extracts the orderId. However, this handler will be unsafe until the authorization check is added to themarkAsPendingfunction (see bot/commands.ts review comment).bot/messages.ts (2)
482-482: LGTM! Optional parameter maintains backward compatibility.Making
orderIdoptional ensures existing calls toshowHoldInvoiceMessagecontinue to work without modification.
513-531: Reconsider non-null assertion on ctx.from.Line 515 uses
ctx.from!.idwith a non-null assertion. While telegraf's Context type marksctx.fromas optional and the non-null assertion bypasses TypeScript's safety checks, callback queries typically guaranteectx.fromis defined. If you want defensive programming, add an explicit guard; otherwise, the assertion is acceptable for this handler context.
| const markAsPending = async (ctx: CommunityContext, orderId: string) => { | ||
| try { | ||
| ctx.deleteMessage(); | ||
| ctx.scene.leave(); | ||
|
|
||
| const order = await Order.findOne({ _id: orderId }); | ||
| if (!order) return; | ||
|
|
||
| const user = await User.findOne({ _id: order.seller_id }); | ||
| if (!user) return; | ||
|
|
||
| const i18nCtx = await getUserI18nContext(user); | ||
|
|
||
| // Only allow marking as pending for orders in WAITING_PAYMENT status | ||
| if (order.status !== 'WAITING_PAYMENT') { | ||
| return await messages.genericErrorMessage(ctx, user, i18nCtx); | ||
| } | ||
|
|
||
| // Cancel the hold invoice | ||
| if (order.hash) { | ||
| await cancelHoldInvoice({ hash: order.hash }); | ||
| } | ||
|
|
||
| const buyerUser = await User.findOne({ _id: order.buyer_id }); | ||
| if (!buyerUser) throw new Error('buyerUser was not found'); | ||
|
|
||
| logger.info( | ||
| `Seller Id ${user.id} marked Order Id: ${order._id} as pending, republishing to the channel`, | ||
| ); | ||
|
|
||
| // Reset order to PENDING status | ||
| order.taken_at = null; | ||
| order.status = 'PENDING'; | ||
|
|
||
| if (!!order.min_amount && !!order.max_amount) { | ||
| order.fiat_amount = undefined; | ||
| } | ||
|
|
||
| if (order.price_from_api) { | ||
| order.amount = 0; | ||
| order.fee = 0; | ||
| order.hash = null; | ||
| order.secret = null; | ||
| } | ||
|
|
||
| if (order.type === 'buy') { | ||
| order.seller_id = null; | ||
| await messages.publishBuyOrderMessage(ctx, buyerUser, order, i18nCtx); | ||
| } else { | ||
| order.buyer_id = null; | ||
| await messages.publishSellOrderMessage(ctx, user, order, i18nCtx); | ||
| } | ||
|
|
||
| await order.save(); | ||
| await messages.successCancelOrderMessage(ctx, user, order, i18nCtx); | ||
| } catch (error) { | ||
| logger.error(error); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CRITICAL: Missing authorization check exposes security vulnerability.
The markAsPending function does not verify that the caller (ctx.from.id) is the seller of the order (order.seller_id). This allows any authenticated user to mark any other user's order as pending, potentially disrupting legitimate transactions.
Additionally, when the seller marks an order as pending, the buyer is not notified. This differs from cancelShowHoldInvoice (lines 519-528), which sends notifications to both parties and could leave the buyer confused about the order status change.
🔎 Recommended authorization check and buyer notification
Add authorization validation and buyer notification:
const markAsPending = async (ctx: CommunityContext, orderId: string) => {
try {
ctx.deleteMessage();
ctx.scene.leave();
const order = await Order.findOne({ _id: orderId });
if (!order) return;
const user = await User.findOne({ _id: order.seller_id });
if (!user) return;
const i18nCtx = await getUserI18nContext(user);
+ // Verify that the caller is the seller
+ if (ctx.from && String(ctx.from.id) !== user.tg_id) {
+ return await messages.notAuthorized(ctx, user.tg_id);
+ }
+
// Only allow marking as pending for orders in WAITING_PAYMENT status
if (order.status !== 'WAITING_PAYMENT') {
return await messages.genericErrorMessage(ctx, user, i18nCtx);
}
// Cancel the hold invoice
if (order.hash) {
await cancelHoldInvoice({ hash: order.hash });
}
const buyerUser = await User.findOne({ _id: order.buyer_id });
if (!buyerUser) throw new Error('buyerUser was not found');
logger.info(
`Seller Id ${user.id} marked Order Id: ${order._id} as pending, republishing to the channel`,
);
// Reset order to PENDING status
order.taken_at = null;
order.status = 'PENDING';
if (!!order.min_amount && !!order.max_amount) {
order.fiat_amount = undefined;
}
if (order.price_from_api) {
order.amount = 0;
order.fee = 0;
order.hash = null;
order.secret = null;
}
if (order.type === 'buy') {
order.seller_id = null;
await messages.publishBuyOrderMessage(ctx, buyerUser, order, i18nCtx);
} else {
order.buyer_id = null;
await messages.publishSellOrderMessage(ctx, user, order, i18nCtx);
}
await order.save();
await messages.successCancelOrderMessage(ctx, user, order, i18nCtx);
+ // Notify the buyer that the order has been reset
+ const i18nCtxBuyer = await getUserI18nContext(buyerUser);
+ await messages.counterPartyCancelOrderMessage(ctx, buyerUser, order, i18nCtxBuyer);
} catch (error) {
logger.error(error);
}
};Committable suggestion skipped: line range outside the PR's diff.
Verify that the user clicking the button is the seller of the order before allowing them to mark it as pending. This prevents unauthorized users from manipulating orders that don't belong to them. Fixes security issue identified by CodeRabbit review.
|
Thanks @coderabbitai for catching the security issue! 🙏 I've added the authorization check in commit 67f4850. Now the The check compares Ready for another review! ✅ |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 890 🏁 Script executed: Length of output: 513 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 318 🏁 Script executed: Length of output: 1128 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2443 @ToRyVand Excellent work! 🎉 The authorization check looks perfect! The implementation is solid:
The security issue is now properly addressed. The function will prevent unauthorized users from manipulating orders that don't belong to them. The only remaining suggestion from my earlier review would be adding unit tests for this function, particularly testing:
But that's optional and can be done in a follow-up if you prefer. The core functionality and security are now in place! 👍 🧠 Learnings usedIf you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Catrya
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
Hi @Catrya, you're absolutely right - I apologize for the confusion. This PR does NOT fix issue #556 (the /checkorder creator bug). While exploring the codebase, I found a different UX issue: after showing a hold invoice, the inline keyboard buttons disappear, leaving sellers unable to mark orders as pending when buyers don't pay. This PR fixes that button issue instead. Should I:
|
Yes
No
Sounds good. |
|
Hi @ToRyVand did you test the PR locally and verify that the bot works correctly? |
🐛 Problem
When a seller shows a hold invoice to a buyer, the inline keyboard buttons disappear, leaving the seller without options to mark the order as pending or cancel the operation. This creates a poor UX where sellers are stuck waiting for payment with no way to reset the order back to PENDING status.
Current Flow (Broken):
✅ Solution
Added a "Mark as pending" button alongside the cancel button after the hold invoice is displayed.
New Flow:
[Mark as pending] [Cancel]🔧 Changes Made
Files Modified (5 total):
locales/en.yaml- Added English translationlocales/es.yaml- Added Spanish translationbot/messages.ts- ModifiedshowHoldInvoiceMessage()orderIdparameterbot/commands.ts- CreatedmarkAsPending()functionbot/start.ts- Registered action handlermarkPendingBtn_[orderId]callback/^markPendingBtn_([0-9a-f]{24})$/markAsPending()with extracted orderId📊 Stats
🎯 Implementation Details
Pattern Followed
This implementation follows the same pattern as the existing
cancelShowHoldInvoicefunction:Why
orderIdis OptionalMade
orderIdoptional inshowHoldInvoiceMessage()to maintain backward compatibility with existing code that calls this function without the parameter.Error Handling
The
cancelHoldInvoice()call is wrapped in try-catch because the invoice might already be expired or canceled. This is not critical—the important part is resetting the order state in the database.✅ Testing
cancelShowHoldInvoice)Note: Attempted local testing but encountered dependency issues with
canvas(pixman-1). The code compiles successfully and follows established patterns in the codebase.🤔 Questions for Reviewers
ctx.from.id === order.seller_id?cancelHoldInvoice()acceptable?📚 Related
cancelShowHoldInvoicefunction inbot/commands.ts✨ Checklist
Summary by CodeRabbit
New Features
Localization
✏️ Tip: You can customize this high-level summary in your review settings.