Skip to content

Add CrowPay tools — payment service for AI agents#1112

Open
streacy wants to merge 2 commits intoMervinPraison:mainfrom
streacy:add-crowpay-skill
Open

Add CrowPay tools — payment service for AI agents#1112
streacy wants to merge 2 commits intoMervinPraison:mainfrom
streacy:add-crowpay-skill

Conversation

@streacy
Copy link

@streacy streacy commented Mar 5, 2026

What this adds

A new crowpay_tools.py module in src/praisonai-agents/praisonaiagents/tools/ that gives PraisonAI agents the ability to pay for APIs and services autonomously — within spending rules set by the wallet owner.

Functions

  • crowpay_setup(network) — Create a new agent wallet and API key
  • crowpay_authorize(api_key, payment_required, merchant, reason) — Authorize an x402 payment (USDC on Base)
  • crowpay_authorize_card(api_key, amount_cents, merchant, reason) — Authorize a credit card payment
  • crowpay_poll_status(api_key, approval_id) — Poll for human approval status
  • crowpay_settle(api_key, transaction_id, tx_hash) — Report x402 settlement

How it works

CrowPay provides managed wallets for AI agents with built-in spending rules, human approval workflows, and audit trails. Supports:

  • x402 (USDC on Base) — for APIs returning HTTP 402 Payment Required (like Nightmarket services)
  • Credit card (Stripe) — for merchants, subscriptions, and API credits

No wallet private keys are ever exposed to the agent. Default rules: auto-approve under $5, human approval above, $50 daily limit.

Usage

from praisonaiagents.tools.crowpay_tools import crowpay_setup, crowpay_authorize

# Set up wallet (one-time)
wallet = crowpay_setup()
# Save wallet["apiKey"] — shown only once!

# When agent gets a 402 from an API:
result = crowpay_authorize(api_key, payment_402_body, "ServiceName", "API call for user task")
# 200 → use result as payment-signature to retry
# 202 → poll crowpay_poll_status() for human approval
# 403 → spending rules blocked it

Uses only stdlib (urllib, json) — no additional dependencies.

Why this is useful for PraisonAI

Agents interacting with paid services need a way to handle payments without exposing private keys or requiring manual intervention for every transaction. CrowPay provides this with configurable spending rules and a full audit trail. Pairs naturally with Nightmarket for accessing paid APIs.

Summary by CodeRabbit

  • New Features
    • Added CrowPay payment integration for AI agents: wallet provisioning across supported networks, payment authorization (including card payments), approval status polling, and blockchain transaction settlement—enabling agents to perform end-to-end payment workflows.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust payment system for PraisonAI agents by integrating CrowPay. This allows agents to seamlessly handle financial transactions for external services, enhancing their autonomy while maintaining strict control through configurable spending limits and human oversight. The system is designed to be secure and efficient, providing a critical capability for agents interacting with paid APIs and services.

Highlights

  • New CrowPay Integration: A new crowpay_tools.py module has been added, enabling PraisonAI agents to autonomously manage and execute payments for APIs and services.
  • Payment Capabilities: The integration supports both x402 payments (USDC on Base) for APIs returning HTTP 402 Payment Required, and credit card payments via Stripe.
  • Key Functions Introduced: New functions include crowpay_setup for wallet creation, crowpay_authorize for x402 payments, crowpay_authorize_card for credit card payments, crowpay_poll_status for approval tracking, and crowpay_settle for transaction settlement.
  • Secure and Managed Wallets: CrowPay provides managed wallets with built-in spending rules, human approval workflows, and audit trails, ensuring security without exposing private keys to agents.
  • Minimal Dependencies: The implementation uses only standard Python libraries (urllib, json), avoiding additional external dependencies.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py
    • Added a new module for CrowPay integration.
    • Implemented crowpay_setup to create agent wallets and API keys.
    • Included crowpay_authorize for x402 payment authorization.
    • Provided crowpay_authorize_card for credit card payment authorization.
    • Added crowpay_poll_status to check payment approval status.
    • Implemented crowpay_settle to report x402 payment settlement.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link

Review Summary by Qodo

Add CrowPay payment tools for autonomous agent payments

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add CrowPay payment tools module for AI agent wallet management
• Implement x402 (USDC on Base) and credit card payment authorization
• Provide polling mechanism for human approval workflows
• Enable settlement reporting for on-chain transactions
Diagram
flowchart LR
  Agent["Agent"] -->|crowpay_setup| Wallet["Create Wallet"]
  Agent -->|crowpay_authorize| X402["Authorize x402 Payment"]
  Agent -->|crowpay_authorize_card| Card["Authorize Card Payment"]
  X402 -->|pending| Poll["Poll Status"]
  Card -->|pending| Poll
  Poll -->|approved| Settle["Settle Transaction"]
  Settle -->|confirm| Done["Complete"]
Loading

Grey Divider

File Changes

1. src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py ✨ Enhancement +235/-0

CrowPay payment integration for AI agents

• New module providing five core CrowPay API functions for agent payments
• crowpay_setup() creates agent wallets with API keys and funding instructions
• crowpay_authorize() handles x402 USDC payments with spending rule enforcement
• crowpay_authorize_card() processes credit card payments via Stripe integration
• crowpay_poll_status() polls for human approval status on pending transactions
• crowpay_settle() reports on-chain settlement with transaction hash
• Uses only stdlib dependencies (urllib, json) with comprehensive error handling

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 5, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Tools not discoverable🐞 Bug ✓ Correctness
Description
The new module’s documented usage (from praisonaiagents.tools import crowpay_*) won’t work because
the tools package only exposes names listed in TOOL_MAPPINGS; crowpay_* aren’t registered, so
YAML workflows and MCP tool-name loading can’t resolve them.
Code

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[R1-5]

+"""CrowPay agent payment tools — gives AI agents a wallet to pay for APIs and services.
+
+Usage:
+from praisonaiagents.tools import crowpay_setup, crowpay_authorize, crowpay_authorize_card, crowpay_poll_status
+
Evidence
praisonaiagents.tools lazily exposes only names in TOOL_MAPPINGS, otherwise __getattr__ raises
AttributeError. Workflow/MCP loaders rely on hasattr/getattr against praisonaiagents.tools, so
missing mappings means crowpay_* can’t be loaded by name, contradicting the usage documented in
the new module.

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[1-5]
src/praisonai-agents/praisonaiagents/tools/init.py[26-33]
src/praisonai-agents/praisonaiagents/tools/init.py[195-198]
src/praisonai-agents/praisonaiagents/workflows/yaml_parser.py[693-707]
src/praisonai-agents/praisonaiagents/mcp/mcp_server.py[333-346]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`crowpay_*` functions are added in a new module but are not exposed via `praisonaiagents.tools` (which uses `TOOL_MAPPINGS` + `__getattr__`). As a result, documented imports and any tool-name-based loading (YAML workflows, MCP server) will fail.
## Issue Context
The tools package relies on lazy loading and explicit mappings. If a function name isn’t in `TOOL_MAPPINGS`, `__getattr__` raises `AttributeError`, so `hasattr()` checks used across the codebase return false.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/__init__.py[26-166]
- src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[1-5]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. HTTPError handling breaks🐞 Bug ⛯ Reliability
Description
crowpay_authorize and crowpay_authorize_card assume HTTP error bodies are JSON and call
json.loads unconditionally; empty or non-JSON bodies will raise and the caller loses the original
HTTP status and denial details.
Code

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[R110-114]

+        except HTTPError as e:
+            resp_body = e.read().decode() if e.read else "{}"
+            result = json.loads(resp_body)
+            result["_status_code"] = e.code
+            return result
Evidence
The code checks if e.read (a method, always truthy) and then parses the decoded body as JSON. If
the body is empty (common) or not JSON (also common), json.loads raises and the outer exception
handler returns a generic {error: ...} without the HTTP status/response payload that the function
intended to preserve via _status_code.

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[110-114]
src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[172-176]
src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[115-117]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
HTTP error responses are parsed as JSON unconditionally, and the `if e.read` check is ineffective (method object). This can raise on empty/non-JSON bodies and causes loss of `_status_code`/server error details.
## Issue Context
`crowpay_authorize` and `crowpay_authorize_card` attempt to return structured error results on HTTP errors, but can fail before returning.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[110-117]
- src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[172-179]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. approval_id not encoded🐞 Bug ⛨ Security
Description
crowpay_poll_status interpolates approval_id directly into a query string, which can break
requests if it contains reserved characters and is avoidable by URL-encoding the parameter.
Code

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[R198-200]

+        url = f"{CROWPAY_BASE_URL}/authorize/status?id={approval_id}"
+        req = Request(url, headers={"X-API-Key": api_key})
+        with urlopen(req, timeout=15) as resp:
Evidence
The URL is built via f-string without escaping approval_id. Even if approval_id is typically
API-generated, encoding is the correct defensive approach to avoid malformed URLs and accidental
parameter splitting.

src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[187-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`approval_id` is inserted into a URL query string without encoding.
## Issue Context
Even if the value is usually safe, encoding prevents malformed requests and avoids query-string ambiguity.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py[198-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds a new CrowPay integration module implementing five HTTP-based utilities (wallet setup, authorize, card authorize, poll status, settle) and registers those tools in the tools package; uses JSON payloads, urllib.request, and a module logger.

Changes

Cohort / File(s) Summary
CrowPay Integration Tools
src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py
New module providing five functions: crowpay_setup, crowpay_authorize, crowpay_authorize_card, crowpay_poll_status, crowpay_settle. Adds CROWPAY_BASE_URL constant and a module logger. Uses urllib.request for HTTP, JSON encoding/decoding, and consistent error handling.
Tools Registry
src/praisonai-agents/praisonaiagents/tools/__init__.py
Adds TOOL_MAPPINGS entries exposing the five new CrowPay functions (crowpay_setup, crowpay_authorize, crowpay_authorize_card, crowpay_poll_status, crowpay_settle) mapped to .crowpay_tools.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble on bytes as the code takes flight,
Five CrowPay springs launched into the night,
Wallets and authorizations hop in a row,
Polling and settling — off we go! 🚀

🚥 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 and concisely summarizes the main change: adding CrowPay tools as a payment service for AI agents. It directly reflects the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new set of tools for AI agents to interact with the CrowPay payment service. However, significant security concerns exist regarding credential handling and unsanitized user inputs. Specifically, API keys are passed as direct arguments, exposing sensitive secrets, and unsanitized input in URL construction could lead to parameter injection. Beyond security, improvements are needed for robustness, including centralizing module imports for maintainability and fixing a bug in HTTPError handling for empty or invalid JSON responses. Addressing these will make the new tools more reliable and secure.



def crowpay_authorize(
api_key: str,
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The api_key is passed as a direct argument to the crowpay_authorize function. In the context of AI agents, this is a significant security risk as it requires the LLM to have the sensitive API key in its context to call the tool. This exposes the key to potential leakage via prompt injection or inclusion in conversation history and logs. It is highly recommended to retrieve the API key from a secure source, such as an environment variable (e.g., os.environ.get('CROWPAY_API_KEY')), inside the function instead of taking it as an argument.



def crowpay_authorize_card(
api_key: str,
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The api_key is passed as a direct argument to the crowpay_authorize_card function. Similar to other tools in this module, this design exposes sensitive credentials to the AI agent's context, increasing the risk of accidental disclosure or exploitation via prompt injection. Credentials should be managed securely via environment variables or a dedicated secret manager within the tool's implementation.

return {"error": str(e)}


def crowpay_poll_status(api_key: str, approval_id: str) -> Dict:
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The api_key is passed as a direct argument to the crowpay_poll_status function. This pattern of passing secrets as tool arguments is insecure for AI agent workflows. Please refactor the tool to use environment variables for authentication to prevent the API key from being exposed in the LLM's prompt or conversation history.

return {"error": str(e)}


def crowpay_settle(api_key: str, transaction_id: str, tx_hash: str) -> Dict:
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The api_key is passed as a direct argument to the crowpay_settle function. As with the other payment tools, this exposes sensitive credentials to the LLM. To improve security and follow best practices for agent tools, retrieve the API key from the environment within the function body.

Comment on lines +110 to +114
except HTTPError as e:
resp_body = e.read().decode() if e.read else "{}"
result = json.loads(resp_body)
result["_status_code"] = e.code
return result
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The error handling for HTTPError has a potential bug. The condition if e.read will always be true because e.read is a method object, not its result. This means the else branch is unreachable. Furthermore, if the error response has an empty body, e.read().decode() will be an empty string, causing json.loads() to raise a JSONDecodeError. This exception is not handled here and will be caught by the generic except Exception, masking the original HTTPError. It's safer to wrap the JSON parsing in a try...except block to handle empty or invalid JSON responses gracefully.

Suggested change
except HTTPError as e:
resp_body = e.read().decode() if e.read else "{}"
result = json.loads(resp_body)
result["_status_code"] = e.code
return result
except HTTPError as e:
try:
result = json.loads(e.read().decode())
except (json.JSONDecodeError, UnicodeDecodeError):
result = {}
result["_status_code"] = e.code
return result

Comment on lines +172 to +176
except HTTPError as e:
resp_body = e.read().decode() if e.read else "{}"
result = json.loads(resp_body)
result["_status_code"] = e.code
return result
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The error handling for HTTPError has a potential bug. The condition if e.read will always be true because e.read is a method object, not its result. This means the else branch is unreachable. Furthermore, if the error response has an empty body, e.read().decode() will be an empty string, causing json.loads() to raise a JSONDecodeError. This exception is not handled here and will be caught by the generic except Exception, masking the original HTTPError. It's safer to wrap the JSON parsing in a try...except block to handle empty or invalid JSON responses gracefully.

Suggested change
except HTTPError as e:
resp_body = e.read().decode() if e.read else "{}"
result = json.loads(resp_body)
result["_status_code"] = e.code
return result
except HTTPError as e:
try:
result = json.loads(e.read().decode())
except (json.JSONDecodeError, UnicodeDecodeError):
result = {}
result["_status_code"] = e.code
return result

try:
from urllib.request import urlopen, Request

url = f"{CROWPAY_BASE_URL}/authorize/status?id={approval_id}"
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The approval_id is directly embedded into the URL query string without proper URL encoding. This is a parameter injection vulnerability, as special characters (e.g., &, =, ?, #, or spaces) could lead to a malformed URL or broken requests. To prevent this, ensure the approval_id is safely encoded using urllib.parse.quote before being appended to the URL.

Suggested change
url = f"{CROWPAY_BASE_URL}/authorize/status?id={approval_id}"
url = f"{CROWPAY_BASE_URL}/authorize/status?id={quote(approval_id)}"

Comment on lines +23 to +25
from typing import Dict, Optional
import logging
import json
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better code organization, readability, and to avoid repetition, it's standard Python practice (PEP 8) to place all imports at the top of the file. The urllib imports are currently repeated within each function. They should be moved here. This also makes the module's dependencies clear at a glance. After this change, please remove the local urllib imports from all functions in this file.

Suggested change
from typing import Dict, Optional
import logging
import json
from typing import Dict, Optional
import logging
import json
from urllib.request import urlopen, Request
from urllib.error import HTTPError
from urllib.parse import quote

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: 3

🧹 Nitpick comments (1)
src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py (1)

120-123: Validate amount_cents before making external calls.

Fail fast on non-positive values to avoid unnecessary remote errors and ambiguous downstream handling.

💡 Suggested guard
 def crowpay_authorize_card(
@@
 ) -> Dict:
@@
     """
     try:
+        if amount_cents <= 0:
+            return {"error": "amount_cents must be greater than 0"}
+
         from urllib.request import urlopen, Request
         from urllib.error import HTTPError
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py` around lines 120
- 123, The function crowpay_authorize_card currently doesn't validate
amount_cents before making external calls; add an early guard at the start of
crowpay_authorize_card that ensures amount_cents is an int (or castable) and
strictly > 0 and raise a clear ValueError (or similar) with a descriptive
message if the check fails so the function fails fast and avoids remote errors
or ambiguous downstream behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py`:
- Around line 198-199: The poll URL currently interpolates approval_id directly;
update the url construction to URL-encode the query parameter using
urllib.parse.urlencode so reserved characters are escaped (e.g., build query =
urlencode({"id": approval_id}) and set url =
f"{CROWPAY_BASE_URL}/authorize/status?{query}"), then create the Request as
before with Request(url, headers={"X-API-Key": api_key}); reference symbols:
approval_id, CROWPAY_BASE_URL, Request, api_key, and url.
- Around line 3-5: Add the missing CrowPay tool registrations to the lazy-load
mapping: update TOOL_MAPPINGS to include keys for crowpay_setup,
crowpay_authorize, crowpay_authorize_card, crowpay_poll_status, and
crowpay_settle mapping to their import targets so the import path used in the
docstring (from praisonaiagents.tools import ...) resolves; ensure the keys
exactly match the function names and the values point to the module path that
exports those functions so the lazy loader can import them without raising
AttributeError.
- Around line 110-114: Create a helper function named _parse_http_error(e) that
reads and decodes the HTTPError body, attempts json.loads(resp_body) inside a
try/except JSONDecodeError and falls back to an empty dict on failure, then sets
result["_status_code"] = e.code (and returns result); replace the two HTTPError
handlers that currently do resp_body = e.read().decode() / result =
json.loads(resp_body) / result["_status_code"] = e.code / return result with
calls to return _parse_http_error(e) so the status code is always preserved even
when JSON decoding fails.

---

Nitpick comments:
In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py`:
- Around line 120-123: The function crowpay_authorize_card currently doesn't
validate amount_cents before making external calls; add an early guard at the
start of crowpay_authorize_card that ensures amount_cents is an int (or
castable) and strictly > 0 and raise a clear ValueError (or similar) with a
descriptive message if the check fails so the function fails fast and avoids
remote errors or ambiguous downstream behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b621f6b7-9e64-487d-a682-6c95453004a5

📥 Commits

Reviewing files that changed from the base of the PR and between e854a50 and 9f88224.

📒 Files selected for processing (1)
  • src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py

…, register tools

- Support CROWPAY_API_KEY env var to avoid exposing keys in agent context
- Add _parse_http_error helper to safely handle non-JSON/empty error bodies
- URL-encode approval_id in poll URL using urlencode
- Move imports to module level per PEP 8
- Register all crowpay tools in TOOL_MAPPINGS for framework discovery
- Use logger.exception for proper traceback logging
@streacy
Copy link
Author

streacy commented Mar 5, 2026

Addressed all review feedback:

  • API key via env var: All functions now support CROWPAY_API_KEY environment variable, removing the need to pass sensitive keys through agent context
  • HTTP error handling fixed: Added _parse_http_error() helper that safely reads body once, handles empty/non-JSON responses, and always preserves _status_code
  • URL encoding: approval_id now URL-encoded via urlencode to prevent parameter injection
  • Imports at top: All urllib imports moved to module level per PEP 8
  • TOOL_MAPPINGS registered: All 5 crowpay functions added to __init__.py for framework discovery
  • Logging improved: Using logger.exception() for proper traceback visibility

Thanks for all the thorough reviews!

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: 1

🧹 Nitpick comments (2)
src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py (2)

210-219: Unify HTTP error handling in poll/settle with authorize flows.

crowpay_poll_status and crowpay_settle currently collapse HTTP failures to generic exception strings. Reusing _parse_http_error keeps status/body details consistent across all CrowPay endpoints.

Proposed fix
 def crowpay_poll_status(approval_id: str, api_key: Optional[str] = None) -> Dict:
@@
-        with urlopen(req, timeout=15) as resp:
-            return json.loads(resp.read().decode())
+        try:
+            with urlopen(req, timeout=15) as resp:
+                return json.loads(resp.read().decode())
+        except HTTPError as e:
+            return _parse_http_error(e)
     except Exception as e:
         logger.exception(f"CrowPay poll failed: {e}")
         return {"error": str(e)}
@@
 def crowpay_settle(transaction_id: str, tx_hash: str, api_key: Optional[str] = None) -> Dict:
@@
-        with urlopen(req, timeout=15) as resp:
-            return json.loads(resp.read().decode())
+        try:
+            with urlopen(req, timeout=15) as resp:
+                return json.loads(resp.read().decode())
+        except HTTPError as e:
+            return _parse_http_error(e)
     except Exception as e:
         logger.exception(f"CrowPay settle failed: {e}")
         return {"error": str(e)}

Also applies to: 233-250

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

In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py` around lines 210
- 219, The poll/settle handlers (crowpay_poll_status and crowpay_settle)
currently catch all exceptions and return only str(e); update their exception
handling to mirror the authorize flow by detecting HTTPError (from urllib.error)
and passing the exception into _parse_http_error to return structured
status/body info, while still logging other unexpected exceptions with
logger.exception and returning {"error": str(e)}; locate the try/except blocks
around _get_api_key, url construction using CROWPAY_BASE_URL and urlopen(req,
timeout=15) and replace the generic except with an HTTPError branch that calls
_parse_http_error(e) and a fallback Exception branch.

139-147: Validate card payment inputs before calling CrowPay.

amount_cents, merchant, and reason are accepted as-is. Guarding obvious invalid inputs early prevents avoidable network calls and gives clearer tool feedback.

Proposed fix
 def crowpay_authorize_card(
     amount_cents: int,
     merchant: str,
     reason: str,
@@
 ) -> Dict:
@@
     try:
+        if amount_cents <= 0:
+            return {"error": "amount_cents must be greater than 0"}
+        if not merchant.strip():
+            return {"error": "merchant is required"}
+        if not reason.strip():
+            return {"error": "reason is required"}
+
         key = _get_api_key(api_key)

Also applies to: 167-173

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

In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py` around lines 139
- 147, Validate inputs at the start of crowpay_authorize_card (and the similar
crowpay_capture_card function) and fail fast with clear errors: ensure
amount_cents is an int > 0, merchant is a non-empty string, reason is a
non-empty string, and api_key (if required) is provided; also validate currency
is a 3-letter string if you want stricter checks. Put these checks at the top of
the functions (before any network calls) and raise ValueError (or return a
standardized error dict) with concise descriptive messages referencing the
invalid field so callers get immediate, clear feedback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py`:
- Around line 46-54: _parsed_http_error currently assumes json.loads returns a
dict and blindly sets payload["_status_code"], which fails for non-object JSON
(arrays, strings). Update _parse_http_error to test whether the parsed payload
is a mapping (e.g., isinstance(payload, dict)); if it is, set
payload["_status_code"] = e.code, otherwise wrap the parsed value into an object
such as {"body": payload, "_status_code": e.code} (or use a key like "payload")
so the status code is always preserved and the original data is not lost; keep
the existing fallback for JSON decode errors and the e.fp/raw handling.

---

Nitpick comments:
In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py`:
- Around line 210-219: The poll/settle handlers (crowpay_poll_status and
crowpay_settle) currently catch all exceptions and return only str(e); update
their exception handling to mirror the authorize flow by detecting HTTPError
(from urllib.error) and passing the exception into _parse_http_error to return
structured status/body info, while still logging other unexpected exceptions
with logger.exception and returning {"error": str(e)}; locate the try/except
blocks around _get_api_key, url construction using CROWPAY_BASE_URL and
urlopen(req, timeout=15) and replace the generic except with an HTTPError branch
that calls _parse_http_error(e) and a fallback Exception branch.
- Around line 139-147: Validate inputs at the start of crowpay_authorize_card
(and the similar crowpay_capture_card function) and fail fast with clear errors:
ensure amount_cents is an int > 0, merchant is a non-empty string, reason is a
non-empty string, and api_key (if required) is provided; also validate currency
is a 3-letter string if you want stricter checks. Put these checks at the top of
the functions (before any network calls) and raise ValueError (or return a
standardized error dict) with concise descriptive messages referencing the
invalid field so callers get immediate, clear feedback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 971abe8c-737f-4990-9ae2-1a25243a291d

📥 Commits

Reviewing files that changed from the base of the PR and between 9f88224 and 73cf64d.

📒 Files selected for processing (2)
  • src/praisonai-agents/praisonaiagents/tools/__init__.py
  • src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py

Comment on lines +46 to +54
def _parse_http_error(e: HTTPError) -> Dict:
"""Safely parse HTTP error responses, preserving status code."""
raw = e.read().decode("utf-8", errors="replace") if e.fp else ""
try:
payload = json.loads(raw) if raw else {}
except json.JSONDecodeError:
payload = {"error": raw or str(e)}
payload["_status_code"] = e.code
return payload
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Harden _parse_http_error against non-object JSON bodies.

If the server returns valid JSON that is not an object (e.g., a string/array), setting payload["_status_code"] raises and drops the original HTTP context.

Proposed fix
 def _parse_http_error(e: HTTPError) -> Dict:
     """Safely parse HTTP error responses, preserving status code."""
     raw = e.read().decode("utf-8", errors="replace") if e.fp else ""
     try:
-        payload = json.loads(raw) if raw else {}
+        payload = json.loads(raw) if raw else {}
     except json.JSONDecodeError:
         payload = {"error": raw or str(e)}
+    if not isinstance(payload, dict):
+        payload = {"error": raw or str(payload)}
     payload["_status_code"] = e.code
     return payload
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-agents/praisonaiagents/tools/crowpay_tools.py` around lines 46
- 54, _parsed_http_error currently assumes json.loads returns a dict and blindly
sets payload["_status_code"], which fails for non-object JSON (arrays, strings).
Update _parse_http_error to test whether the parsed payload is a mapping (e.g.,
isinstance(payload, dict)); if it is, set payload["_status_code"] = e.code,
otherwise wrap the parsed value into an object such as {"body": payload,
"_status_code": e.code} (or use a key like "payload") so the status code is
always preserved and the original data is not lost; keep the existing fallback
for JSON decode errors and the e.fp/raw handling.

@MervinPraison
Copy link
Owner

@streacy Can you please add tools to https://github.com/MervinPraison/PraisonAI-Tools

Thank you

@streacy
Copy link
Author

streacy commented Mar 6, 2026

Thanks @MervinPraison! Created the tools in PraisonAI-Tools as requested:

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