Skip to content

Add settled_by_admin field in order.ts model and improve getDetailedOrder output.#747

Open
Luquitasjeffrey wants to merge 9 commits intomainfrom
add-settled_by_admin-field
Open

Add settled_by_admin field in order.ts model and improve getDetailedOrder output.#747
Luquitasjeffrey wants to merge 9 commits intomainfrom
add-settled_by_admin-field

Conversation

@Luquitasjeffrey
Copy link
Collaborator

@Luquitasjeffrey Luquitasjeffrey commented Feb 16, 2026

This PR removes the 'COMPLETED_BY_ADMIN' status in an Order and adds a new boolean field 'settled_by_admin' that, as the name suggests, denotes if an order was indeed completed or not by an admin.
Also, now when an admin uses the checkorder command, it will display if the order was completed by an admin

Summary by CodeRabbit

  • New Features

    • Order details now show whether an order was settled by an admin, in all supported languages.
  • Updates

    • Settlement and order-status handling adjusted: admin-settled orders are tracked explicitly and finalization behavior was refined, affecting when payments are released and when blocks are prevented.
  • Chores

    • Existing orders were migrated to align with the new admin-settled tracking.

…d_by_admin' field denoting that, and modified the 'getDetailedOrder' function output to show if an order was completed by an admin or not
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@Luquitasjeffrey has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 13bced62-5573-492d-be01-9779f92fcf7c

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9a117 and d790c63.

📒 Files selected for processing (4)
  • tests/bot/bot.spec.ts
  • tests/bot/modules/block.spec.ts
  • tests/scripts/migrate_completed_by_admin_orders.spec.ts
  • tests/util/index.spec.ts

Walkthrough

Replaces the COMPLETED_BY_ADMIN status with a boolean settled_by_admin, tightens status checks to PAID_HOLD_INVOICE, sets settled_by_admin when admin settles orders, migrates existing orders, and surfaces settledByAdmin in order detail translations across locales.

Changes

Cohort / File(s) Summary
Command & flow logic
bot/commands.ts, bot/modules/block/commands.ts, bot/start.ts
Removed COMPLETED_BY_ADMIN from status checks/queries; narrowed filters to PAID_HOLD_INVOICE; set order.settled_by_admin = true when admin settles and removed setting status to COMPLETED_BY_ADMIN.
Data model
models/order.ts
Added settled_by_admin: { type: Boolean, default: false } and settled_by_admin?: boolean to IOrder; removed 'COMPLETED_BY_ADMIN' from the status enum.
Migration script
scripts/migrate_completed_by_admin_orders.ts
New migration that converts orders with COMPLETED_BY_ADMIN to PAID_HOLD_INVOICE and sets settled_by_admin = true.
Order detail rendering
util/index.ts
Added settledByAdmin derived from order.settled_by_admin, translated it, and included it in the order_detail payload.
Localization
locales/{de,en,es,fa,fr,it,ko,pt,ru,uk}.yaml
Added a new order_detail line showing Settled by admin: ${settledByAdmin} (localized) in each language.
Project config / misc
tsconfig.json, package.json
Formatting and include/exclude updates; added scripts/**/* to TS include.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • mostronatorcoder

Poem

🐰 I hopped through code where statuses changed,
A tiny flag now marks the end;
The admin's touch in fields arranged,
Translations sing — the rabbit's friend. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary changes: adding a settled_by_admin field to the order model and enhancing getDetailedOrder output to display admin settlement status.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-settled_by_admin-field
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@bot/start.ts`:
- Around line 547-551: The code currently sets order.settled_by_admin = true and
calls await order.save() before calling settleHoldInvoice, which can leave the
flag persisted if settleHoldInvoice throws; change the flow so you first call
await settleHoldInvoice({ secret: order.secret }) and only after it succeeds set
order.settled_by_admin = true and call await order.save(), or wrap both calls in
a try/catch and roll back (reset) order.settled_by_admin and avoid saving if
settleHoldInvoice fails; refer to the order object, its save() method, and the
settleHoldInvoice function when updating the logic.

In `@locales/fa.yaml`:
- Around line 183-184: Replace the English label "Settled by admin:
${settledByAdmin}" in the Persian locale with a Persian translation to keep
localization consistent; update the entry in locales/fa.yaml (the string
"Settled by admin: ${settledByAdmin}") to a Persian equivalent such as "تسویه
شده توسط ادمین: ${settledByAdmin}" ensuring the placeholder ${settledByAdmin}
remains unchanged.

@mostronatorcoder
Copy link
Contributor

🚨 Request Changes - Critical Issues Found

I need to request changes before this PR can be merged. There are critical security and data integrity issues that must be addressed:

1. BREAKING CHANGE without Data Migration (CRITICAL)

Removing "COMPLETED_BY_ADMIN" from the status enum is a breaking change that will break existing orders in production.

Problem: Orders currently in "COMPLETED_BY_ADMIN" status will be in an invalid state after deployment.

Required: Add a database migration script that:

  • Finds all orders with status: "COMPLETED_BY_ADMIN"
  • Updates them to status: "PAID_HOLD_INVOICE"
  • Sets settled_by_admin: true

Without this migration, existing orders will be unreachable and potentially cause runtime errors.

2. Data Integrity Issue in bot/start.ts (SECURITY)

Current (WRONG):

order.settled_by_admin = true;
await order.save();
await settleHoldInvoice({ secret: order.secret });

Problem: If settleHoldInvoice() fails, the order is already marked as settled by admin but the hold invoice was NOT settled. This creates an inconsistent state where the database says "settled by admin" but the Lightning payment did not complete.

Required (CORRECT):

try {
  await settleHoldInvoice({ secret: order.secret });
  order.settled_by_admin = true;
  await order.save();
} catch (error) {
  // Handle error - do NOT mark as settled_by_admin
  throw error;
}

The settlement must succeed BEFORE marking the order as settled by admin.

3. Schema Issue - Optional Field

In models/order.ts:

settled_by_admin?: boolean;  // Optional - BAD

Problem: Optional fields can be undefined, leading to inconsistent data.

Required:

settled_by_admin: { type: Boolean, default: false, required: true }

4. Missing CodeRabbit Review

This PR has been pending CodeRabbit review. I should have waited for that automated analysis before reviewing. Please ensure all automated checks pass.


Summary of Required Changes

  1. Add database migration for existing "COMPLETED_BY_ADMIN" orders
  2. Fix order of operations in bot/start.ts - settle first, then mark
  3. Make field required with default false in schema
  4. Verify all existing orders with "COMPLETED_BY_ADMIN" are handled

This is financial software handling real Bitcoin. We cannot merge breaking changes without proper migration and data integrity guarantees.

Status: 🔴 Request Changes - DO NOT MERGE until issues are resolved.

@mostronatorcoder
Copy link
Contributor

Hey @Luquitasjeffrey, friendly reminder — the issues from my previous review are still unresolved. The last two commits are upstream merges, no fixes.

Here's a quick recap:

1. 🔴 Missing database migration (CRITICAL)

You're removing COMPLETED_BY_ADMIN from the status enum, but existing orders in production with that status will become invalid. You need a migration script that:

// Find orders with the old status and migrate them
db.orders.updateMany(
  { status: "COMPLETED_BY_ADMIN" },
  { $set: { status: "PAID_HOLD_INVOICE", settled_by_admin: true } }
)

2. 🔴 Order of operations in bot/start.ts (DATA INTEGRITY)

Still persisting settled_by_admin = true BEFORE calling settleHoldInvoice(). If the LN call fails, the DB says "settled by admin" but the invoice was never settled.

Current (wrong):

order.settled_by_admin = true;
await order.save();                              // persisted
await settleHoldInvoice({ secret: order.secret }); // can fail

I understand your concern about race conditions (from your earlier reply to CodeRabbit), but the current approach trades one race condition for a worse one — a false positive where the DB says the invoice was settled when it wasn't. If you're worried about async event handlers, you could set the flag in memory first and save after settlement succeeds:

await settleHoldInvoice({ secret: order.secret });
order.settled_by_admin = true;
await order.save();

3. 🟡 TypeScript interface vs Mongoose schema mismatch

The Mongoose schema has { type: Boolean, default: false } (good), but the TypeScript interface still declares settled_by_admin?: boolean (optional). For consistency, consider adding required: true to the schema or at minimum document why it's optional.


This PR handles real Bitcoin orders — we need these data integrity issues resolved before merging. Let me know if you have questions or need help with any of these.

…correcting order of operations in bot/start.ts due to a potential failure while settling the hold invoice
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
bot/start.ts (1)

547-551: ⚠️ Potential issue | 🔴 Critical

Set settled_by_admin before settleHoldInvoice to avoid a race.

At Line 548, settlement can trigger async order-update handlers before Line 549–550 runs, which can leave inconsistent state for admin-settled orders.

Suggested fix
       if (order.secret) {
-        await settleHoldInvoice({ secret: order.secret });
         order.settled_by_admin = true;
         await order.save();
+        await settleHoldInvoice({ secret: order.secret });
       }

Based on learnings: In bot/start.ts /settleorder command, order.settled_by_admin = true must be set and saved before calling settleHoldInvoice({ secret: order.secret }) to prevent a race condition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/start.ts` around lines 547 - 551, The order settlement code can race
because settleHoldInvoice(...) may trigger async handlers that read order state
before we mark it as admin-settled; update the sequence so you set
order.settled_by_admin = true and await order.save() before calling
settleHoldInvoice({ secret: order.secret }) (refer to the settleorder command,
order.settled_by_admin, order.save, and settleHoldInvoice symbols) so the
persisted flag exists for any downstream handlers.
🧹 Nitpick comments (1)
scripts/migrate_completed_by_admin_orders.ts (1)

30-35: Prefer finally for deterministic connection cleanup.

Line 33–35 exits on error without guaranteed close. Consider using finally to always close the Mongo connection and set process.exitCode instead of immediate process.exit(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/migrate_completed_by_admin_orders.ts` around lines 30 - 35, The
migration currently calls mongoose.connection.close() only on success and uses
process.exit(...) inside the catch which can leave the DB open; refactor the
try/catch to a try/catch/finally where you always await
mongoose.connection.close() in the finally block (handling a potentially
undefined connection) and replace direct calls to
process.exit(0)/process.exit(1) with setting process.exitCode = 0 or 1 so
shutdown can occur deterministically; update references in this file to remove
immediate process.exit usage and ensure logger.error(`Migration failed:
${error}`) remains in the catch before setting process.exitCode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@bot/start.ts`:
- Around line 547-551: The order settlement code can race because
settleHoldInvoice(...) may trigger async handlers that read order state before
we mark it as admin-settled; update the sequence so you set
order.settled_by_admin = true and await order.save() before calling
settleHoldInvoice({ secret: order.secret }) (refer to the settleorder command,
order.settled_by_admin, order.save, and settleHoldInvoice symbols) so the
persisted flag exists for any downstream handlers.

---

Nitpick comments:
In `@scripts/migrate_completed_by_admin_orders.ts`:
- Around line 30-35: The migration currently calls mongoose.connection.close()
only on success and uses process.exit(...) inside the catch which can leave the
DB open; refactor the try/catch to a try/catch/finally where you always await
mongoose.connection.close() in the finally block (handling a potentially
undefined connection) and replace direct calls to
process.exit(0)/process.exit(1) with setting process.exitCode = 0 or 1 so
shutdown can occur deterministically; update references in this file to remove
immediate process.exit usage and ensure logger.error(`Migration failed:
${error}`) remains in the catch before setting process.exitCode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aae5b76a-95fe-43af-8241-9fac1e0f31ae

📥 Commits

Reviewing files that changed from the base of the PR and between a07edbe and 7e9a117.

📒 Files selected for processing (3)
  • bot/start.ts
  • scripts/migrate_completed_by_admin_orders.ts
  • tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • tsconfig.json

@Luquitasjeffrey
Copy link
Collaborator Author

Hey @Luquitasjeffrey, friendly reminder — the issues from my previous review are still unresolved. The last two commits are upstream merges, no fixes.

Here's a quick recap:

1. 🔴 Missing database migration (CRITICAL)

You're removing COMPLETED_BY_ADMIN from the status enum, but existing orders in production with that status will become invalid. You need a migration script that:

// Find orders with the old status and migrate them
db.orders.updateMany(
  { status: "COMPLETED_BY_ADMIN" },
  { $set: { status: "PAID_HOLD_INVOICE", settled_by_admin: true } }
)

2. 🔴 Order of operations in bot/start.ts (DATA INTEGRITY)

Still persisting settled_by_admin = true BEFORE calling settleHoldInvoice(). If the LN call fails, the DB says "settled by admin" but the invoice was never settled.

Current (wrong):

order.settled_by_admin = true;
await order.save();                              // persisted
await settleHoldInvoice({ secret: order.secret }); // can fail

I understand your concern about race conditions (from your earlier reply to CodeRabbit), but the current approach trades one race condition for a worse one — a false positive where the DB says the invoice was settled when it wasn't. If you're worried about async event handlers, you could set the flag in memory first and save after settlement succeeds:

await settleHoldInvoice({ secret: order.secret });
order.settled_by_admin = true;
await order.save();

3. 🟡 TypeScript interface vs Mongoose schema mismatch

The Mongoose schema has { type: Boolean, default: false } (good), but the TypeScript interface still declares settled_by_admin?: boolean (optional). For consistency, consider adding required: true to the schema or at minimum document why it's optional.

This PR handles real Bitcoin orders — we need these data integrity issues resolved before merging. Let me know if you have questions or need help with any of these.

I addressed your review.

  1. I added the script scripts/migrate_completed_by_admin_orders.ts, to migrate those orders it use the following commands:
    npx tsc node dist/scripts/migrate_completed_by_admin_orders.js

  2. Fixed the order of operations, you were correct, settleHoldInvoice now is called before setting the settled_by_admin field to true

  3. I dismissed your third comment about the "inconsistency" between the Order interface and the model because in mongoose all fields can be nullable unless explicitly required, so your 3rd comment is dismissed.

Copy link
Contributor

@mostronatorcoder mostronatorcoder bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR removes the COMPLETED_BY_ADMIN status and replaces it with a settled_by_admin boolean field. The design is architecturally correct and includes a critical fix for operation ordering.


✅ Strengths

1. Correct architectural design

  • Removing COMPLETED_BY_ADMIN status and replacing it with a settled_by_admin boolean is the right approach
  • Status should reflect workflow state, not who completed it
  • Boolean flag captures metadata without polluting the state machine

2. Migration script included

  • scripts/migrate_completed_by_admin_orders.ts migrates existing orders
  • Transforms status: COMPLETED_BY_ADMINstatus: PAID_HOLD_INVOICE + settled_by_admin: true
  • Safe logic using updateMany

3. Complete status cleanup

  • All COMPLETED_BY_ADMIN references removed except:
    • Migration script (correct, necessary)
    • Locale files (message translations, OK)

4. Critical fix: Operation ordering in bot/start.ts

Before:

if (order.secret) await settleHoldInvoice({ secret: order.secret });

order.status = 'COMPLETED_BY_ADMIN';  // ❌ Status changes even if settle fails
await order.save();

Problem: If settleHoldInvoice() throws, order ends up in COMPLETED_BY_ADMIN status but the invoice is NOT settled in LND → critical inconsistency.

Now:

if (order.secret) {
  await settleHoldInvoice({ secret: order.secret });  // ✅ If fails, exception exits here
  order.settled_by_admin = true;                      // ✅ Only executes if settle OK
  await order.save();
}

Analysis:CORRECT

  • If settleHoldInvoice() fails → exception exits, nothing modified
  • If settle OK → marks settled_by_admin = true and saves
  • Status remains PAID_HOLD_INVOICE (correct state after settle)

This is a real bug fix.

5. Display in checkorder

  • Now shows "Settled by admin: Yes/No" in /checkorder output
  • Useful for audit and debugging

⚠️ Issues Found

🔴 CRITICAL: Missing tests

The PR modifies:

  • Critical business logic (settle flow in bot/start.ts)
  • Data model (models/order.ts)
  • Order queries (bot/commands.ts, bot/modules/block/commands.ts)

But:

git diff cb1838e..7e9a117 -- tests/
(no output)

No new or updated tests.

Test cases that SHOULD exist:

1. Migration script

it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => {
  // Given: order with status COMPLETED_BY_ADMIN
  // When: run migration
  // Then: status = PAID_HOLD_INVOICE && settled_by_admin = true
});

2. Admin settle flow (bot/start.ts)

it('should set settled_by_admin when admin settles with secret', async () => {
  // Given: order with secret
  // When: admin executes settle
  // Then: settled_by_admin = true
});

it('should not modify order if settleHoldInvoice fails', async () => {
  // Given: order with invalid secret
  // When: admin executes settle (LND fails)
  // Then: order status/flags unchanged
});

3. Display in checkorder

it('should show "Settled by admin: Yes" when settled_by_admin = true', async () => {
  // Given: order with settled_by_admin = true
  // When: /checkorder
  // Then: output contains "Settled by admin: Yes"
});

4. Block queries

it('should exclude settled orders from pending count', async () => {
  // Given: orders with settled_by_admin = true
  // When: user tries /block
  // Then: those orders do NOT count as pending
});

Impact: Without tests, any regression in this logic will go unnoticed until production.


🟡 Minor: Unnecessary tsconfig.json change

+    "scripts/**/*"

Adding scripts/ to tsconfig include is fine for compiling the migration script, BUT:

  • Migration script runs once manually
  • Doesn't need to be in production build
  • Better approach: Run migration with ts-node directly, without modifying tsconfig

Not critical, but unnecessary noise in the PR.


🟡 Minor: Unrelated formatting changes

-    "lib":["ES2021", "DOM"],
+    "lib": [
+      "ES2021",
+      "DOM"
+    ],

This is just formatting. Better to do it in a separate "chore: prettier" commit to avoid contaminating the functional diff.


🔍 Logic Validation

Query in /setinvoice

Before:

const where = {
  $and: [
    { buyer_id: user._id },
    {
      $or: [
        { status: 'PAID_HOLD_INVOICE' },
        { status: 'COMPLETED_BY_ADMIN' },  // ❌ No longer exists
      ],
    },
  ],
};

Now:

const where = {
  buyer_id: user._id,
  status: 'PAID_HOLD_INVOICE',  // ✅ Simplified
};

Analysis:CORRECT

  • Previously searched for orders in PAID_HOLD_INVOICE OR COMPLETED_BY_ADMIN
  • Now only searches PAID_HOLD_INVOICE (the only valid status after migration)
  • Simpler and correct query

📊 Findings Summary

Severity Issue Impact
🔴 Critical No tests Risk of regression in settle logic and queries
🟡 Minor tsconfig include scripts/ Noise in build config
🟡 Minor Formatting changes Contaminated diff

✅ Recommendations

BEFORE MERGE:

  • Add tests for migration script
  • Add tests for settle flow with settled_by_admin
  • Add tests for checkorder/block queries
  • Verify in staging that migration script works correctly

NICE TO HAVE:

  • Separate formatting changes in separate commit
  • Evaluate if tsconfig change is necessary (alternative: ts-node for migration)

POST-MERGE:

  • Add grep in CI to prevent accidental use of COMPLETED_BY_ADMIN in the future
  • Document in README or CHANGELOG the model change (breaking change for external integrations reading the DB)

🎯 Verdict

Status: ⚠️ REQUEST CHANGES

Reasons:

  1. Lack of tests is critical for settle logic and migration
  2. The code itself is correct and improves the system (fixes operation ordering bug)
  3. The architectural design is solid

Required action:

  • Add minimum tests for migration and settle flow
  • Validate migration in staging before merge

Once tests are added:APPROVE


Additional Notes

I verified all COMPLETED_BY_ADMIN references have been removed from queries:

git grep -n "COMPLETED_BY_ADMIN" | grep -v "locales/"
# Only found in migration script (correct)

I also checked for potential broken queries:

git grep -n "status.*:" bot/ util/ models/ | grep -i "success\|paid_hold\|canceled\|expired"
# All queries updated correctly

The PR is well-structured and the logic is sound. The only blocker is the missing test coverage for critical business logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants