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

Support for Easypay API 2.0 #3

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Support for Easypay API 2.0 #3

wants to merge 9 commits into from

Conversation

joamag
Copy link
Contributor

@joamag joamag commented Dec 7, 2024

Fixes #1

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Easypay API version 2.0 with enhanced payment processing capabilities.
    • Added PaymentAPI class for managing payments, including methods to list and create payments.
    • New MBAppV2 class for web application interface to handle payment requests.
    • Added support for environment configuration with new API version.
  • Documentation

    • Updated README.md to include a new "Resources" section with links to Easypay API documentation and admin portals.
  • Chores

    • Updated .gitignore to exclude environment-related files for better project management.

@joamag joamag self-assigned this Dec 7, 2024
Copy link

coderabbitai bot commented Dec 7, 2024

Walkthrough

The pull request introduces several modifications across multiple files to enhance the Easypay API, particularly focusing on the implementation of version 2.0. Key changes include updates to the .gitignore file to exclude environment files, the addition of a "Resources" section in the README.md, and the introduction of new classes and methods in the base and payment modules. The changes facilitate the transition to Easypay API 2.0 by creating dedicated classes and methods for payment processing.

Changes

File Change Summary
.gitignore Added entries to ignore /.env and /.venv files.
README.md Added a new "Resources" section with links to Easypay API documentation and admin interfaces.
src/easypay/__init__.py Imported APIv2 from base and PaymentAPI from payment.
src/easypay/base.py Introduced APIv2 class, added BASE_URL_V2 and BASE_URL_TEST_V2, modified build method.
src/easypay/payment.py Created PaymentAPI class with list_payments and create_payment methods.
src/examples/__init__.py Replaced MBApp with MBAppV2 and added get_api_v2 import.
src/examples/app_v2.py Added MBAppV2 class with methods for handling payments.
src/examples/base.py Introduced get_api_v2 function for initializing the new API version.

Assessment against linked issues

Objective Addressed Explanation
Create a separate API entity for v2 of the API (#1)
Facilitate the transition to Easypay API 2.0 (#1)

🐰 "In the code we hop and play,
New APIs are here to stay.
With links and classes, oh so bright,
Easypay's future is in sight!
Let's ignore the clutter, keep it neat,
For payments now, we can't be beat!" 🐇


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 36f110c and e009a18.

📒 Files selected for processing (1)
  • src/examples/base.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/examples/base.py

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

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

🧹 Outside diff range and nitpick comments (3)
src/easypay/payment.py (2)

31-31: Remove unused import appier

The appier module is imported but not used in this file, as identified by Ruff (F401). Removing unused imports improves code cleanliness.

Suggested Fix:

-import appier
🧰 Tools
🪛 Ruff (0.8.0)

31-31: appier imported but unused

Remove unused import: appier

(F401)


34-42: Ensure PaymentAPI methods have necessary context and documentation

While the list_payments and create_payment methods are correctly implemented, there are a few considerations:

  1. Dependency on self.base_url:

    The methods use self.base_url, which is not defined within PaymentAPI. It is expected to be provided by the subclass. Ensure that any class inheriting from PaymentAPI defines self.base_url.

  2. Dependency on HTTP methods get and post:

    The methods call self.get and self.post, which should be provided by the superclass (e.g., appier.API). Ensure that PaymentAPI is mixed into a class that provides these methods.

  3. Lack of method docstrings:

    Adding docstrings to these methods would improve code readability and maintainability by explaining their purpose and usage.

Suggested Actions:

  • Add docstrings to the methods to describe their functionality.
  • Document the requirements that self.base_url, self.get, and self.post must be provided by the subclass or superclass.

Example:

class PaymentAPI(object):
    """
    Provides payment-related API methods.

    Classes inheriting from PaymentAPI must define `self.base_url`
    and provide implementations for `self.get` and `self.post` methods.
    """

    def list_payments(self, *args, **kwargs):
        """
        Lists payments using the Easypay API.

        Args:
            *args: Variable length argument list.
            **kwargs: Arbitrary keyword arguments.

        Returns:
            A list of payments retrieved from the API.
        """
        url = self.base_url + "single"
        return self.get(url, *args, **kwargs)

    def create_payment(self, amount, method="mb"):
        """
        Creates a new payment.

        Args:
            amount (float): The amount for the payment.
            method (str, optional): The payment method. Defaults to "mb".

        Returns:
            The created payment object from the API.
        """
        url = self.base_url + "single"
        return self.post(url, data_j=dict(value=amount, method=method))
README.md (1)

5-9: Enhance documentation for API V2 migration

While the added resources are helpful, consider adding:

  1. Migration guide from API V1 to V2
  2. Authentication changes documentation
  3. Code examples for V2 endpoints

Example additions:

 ## Resources
 
 - [Easypay API Docs](https://docs.easypay.pt/)
 - [Easypay Admin](https://id.easypay.pt/)
 - [Easypay Admin Test](https://id.test.easypay.pt/)
+
+## Migration Guide
+
+### Authentication Changes
+API V2 uses client_id/key authentication instead of username/password:
+```python
+# V1 Authentication
+api = get_api()  # uses username, password, cin, entity
+
+# V2 Authentication
+api = get_api_v2()  # uses client_id, key
+```
+
+### Example Usage
+```python
+# Create payment
+payment = api.create_payment(amount=10.0, method="mb")
+
+# List payments
+payments = api.list_payments()
+```
🧰 Tools
🪛 LanguageTool

[grammar] ~8-~8: This phrase is duplicated. You should probably use “Easypay Admin” only once.
Context: ... API Docs](https://docs.easypay.pt/) - Easypay Admin - Easypay Admin Test ## ...

(PHRASE_REPETITION)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7a893c7 and a1f2221.

📒 Files selected for processing (8)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • src/easypay/__init__.py (1 hunks)
  • src/easypay/base.py (3 hunks)
  • src/easypay/payment.py (1 hunks)
  • src/examples/__init__.py (1 hunks)
  • src/examples/app_v2.py (1 hunks)
  • src/examples/base.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.0)
src/easypay/__init__.py

32-32: .base.BASE_URL imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.Scheduler imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.API imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.ShelveAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.APIV2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.APIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.SecurityError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


34-34: .mb.MBAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


35-35: .payment.PaymentAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

src/examples/__init__.py

32-32: .app_v2.MBAppV2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .base.get_api imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .base.get_api_v2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

src/easypay/payment.py

31-31: appier imported but unused

Remove unused import: appier

(F401)

src/examples/app_v2.py

39-39: Star-arg unpacking after a keyword argument is strongly discouraged

(B026)

🪛 LanguageTool
README.md

[grammar] ~8-~8: This phrase is duplicated. You should probably use “Easypay Admin” only once.
Context: ... API Docs](https://docs.easypay.pt/) - Easypay Admin - Easypay Admin Test ## ...

(PHRASE_REPETITION)

🔇 Additional comments (3)
src/easypay/base.py (2)

45-45: Importing the payment module is necessary for PaymentAPI

The addition of from . import payment is appropriate since the APIV2 class inherits from payment.PaymentAPI. This ensures that PaymentAPI is available in the current module.


61-68: Defining BASE_URL_V2 and BASE_URL_TEST_V2 for Easypay API 2.0

The new constants BASE_URL_V2 and BASE_URL_TEST_V2 are correctly defined for the production and test environments of Easypay API 2.0. This is essential for properly routing API requests to the correct endpoints.

.gitignore (1)

6-7: Include environment files and virtual environments in .gitignore

Adding /.env and /.venv to the .gitignore file is appropriate. This prevents sensitive environment variables and local virtual environment directories from being tracked by version control.

Comment on lines 444 to 481
class APIV2(appier.API, payment.PaymentAPI):
"""
Top level entry point for the Easypay API 2.0 services,
should provide the abstract implementations for the
services offered by Easypay using the 2.0 version of the API
"""

def __init__(self, *args, **kwargs):
appier.API.__init__(self, *args, **kwargs)
self.production = appier.conf("EASYPAY_PRODUCTION", False, cast=bool)
self.account_id = appier.conf("EASYPAY_ACCOUNT_ID", None)
self.key = appier.conf("EASYPAY_KEY", None)
self.production = kwargs.get("production", self.production)
self.account_id = kwargs.get("account_id", self.account_id)
self.key = kwargs.get("key", self.key)
self.base_url = BASE_URL_V2 if self.production else BASE_URL_TEST_V2
self.counter = 0
self.references = dict()
self.lock = threading.RLock()
self.scheduler = Scheduler(self)

def build(
self,
method,
url,
data=None,
data_j=None,
data_m=None,
headers=None,
params=None,
mime=None,
kwargs=None,
):
appier.API.build(self, method, url, headers, kwargs)
if self.account_id:
headers["AccountId"] = self.account_id
if self.key:
headers["ApiKey"] = self.key
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle headers correctly in the build method to prevent errors

There are potential issues in the build method of the APIV2 class:

  1. Possible TypeError if headers is None:

    The code attempts to assign values to headers without ensuring it is a dictionary. If headers is None, this will raise a TypeError.

  2. Incorrect parameter passing to the base class build method:

    The call to appier.API.build() may not correctly match the expected parameters, potentially leading to unexpected behavior.

Suggested Fix:

  • Ensure headers is initialized as an empty dictionary if it's None.
  • Pass all necessary parameters to the base class build method.

Apply the following changes:

def build(
    self,
    method,
    url,
    data=None,
    data_j=None,
    data_m=None,
    headers=None,
    params=None,
    mime=None,
    kwargs=None,
):
+   if headers is None:
+       headers = {}
    appier.API.build(
        self,
        method,
        url,
        data=data,
        data_j=data_j,
        data_m=data_m,
        headers=headers,
        params=params,
        mime=mime,
        kwargs=kwargs,
    )
    if self.account_id:
        headers["AccountId"] = self.account_id
    if self.key:
        headers["ApiKey"] = self.key

This ensures that headers is a dictionary and that all parameters are correctly passed to the base class method.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class APIV2(appier.API, payment.PaymentAPI):
"""
Top level entry point for the Easypay API 2.0 services,
should provide the abstract implementations for the
services offered by Easypay using the 2.0 version of the API
"""
def __init__(self, *args, **kwargs):
appier.API.__init__(self, *args, **kwargs)
self.production = appier.conf("EASYPAY_PRODUCTION", False, cast=bool)
self.account_id = appier.conf("EASYPAY_ACCOUNT_ID", None)
self.key = appier.conf("EASYPAY_KEY", None)
self.production = kwargs.get("production", self.production)
self.account_id = kwargs.get("account_id", self.account_id)
self.key = kwargs.get("key", self.key)
self.base_url = BASE_URL_V2 if self.production else BASE_URL_TEST_V2
self.counter = 0
self.references = dict()
self.lock = threading.RLock()
self.scheduler = Scheduler(self)
def build(
self,
method,
url,
data=None,
data_j=None,
data_m=None,
headers=None,
params=None,
mime=None,
kwargs=None,
):
appier.API.build(self, method, url, headers, kwargs)
if self.account_id:
headers["AccountId"] = self.account_id
if self.key:
headers["ApiKey"] = self.key
class APIV2(appier.API, payment.PaymentAPI):
"""
Top level entry point for the Easypay API 2.0 services,
should provide the abstract implementations for the
services offered by Easypay using the 2.0 version of the API
"""
def __init__(self, *args, **kwargs):
appier.API.__init__(self, *args, **kwargs)
self.production = appier.conf("EASYPAY_PRODUCTION", False, cast=bool)
self.account_id = appier.conf("EASYPAY_ACCOUNT_ID", None)
self.key = appier.conf("EASYPAY_KEY", None)
self.production = kwargs.get("production", self.production)
self.account_id = kwargs.get("account_id", self.account_id)
self.key = kwargs.get("key", self.key)
self.base_url = BASE_URL_V2 if self.production else BASE_URL_TEST_V2
self.counter = 0
self.references = dict()
self.lock = threading.RLock()
self.scheduler = Scheduler(self)
def build(
self,
method,
url,
data=None,
data_j=None,
data_m=None,
headers=None,
params=None,
mime=None,
kwargs=None,
):
if headers is None:
headers = {}
appier.API.build(
self,
method,
url,
data=data,
data_j=data_j,
data_m=data_m,
headers=headers,
params=params,
mime=mime,
kwargs=kwargs,
)
if self.account_id:
headers["AccountId"] = self.account_id
if self.key:
headers["ApiKey"] = self.key

Comment on lines +32 to +33
from .app_v2 import MBAppV2
from .base import get_api, get_api_v2
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Add these imports to __all__ to define public interface

The imports MBAppV2, get_api, and get_api_v2 are actively used within the codebase and should be part of the module's public interface. Since __all__ is not currently defined in __init__.py, you should add it to explicitly declare these imports as part of the public API:

__all__ = ["MBAppV2", "get_api", "get_api_v2"]

This will make the imports' purpose clear and follow Python's best practices for package interfaces.

🔗 Analysis chain

Unused imports detected by static analysis tools

The imports MBAppV2, get_api, and get_api_v2 are added but not used within this module. This is flagged by Ruff (F401).

Suggested Actions:

  • If these imports are intended to be part of the module's public interface, consider adding them to __all__ to make them accessible when the package is imported.
  • If they are not needed, remove the unused imports to clean up the code.

Verification Script:

This script searches for occurrences of MBAppV2, get_api, and get_api_v2 within the src/examples/ directory to verify if they are used elsewhere.


These imports are part of the module's public interface

The imports are actively used within the codebase:

  • MBAppV2 is defined in app_v2.py and used for app instantiation
  • get_api is used in app.py for API initialization
  • get_api_v2 is used in app_v2.py for API initialization

Since these imports are used by other modules and appear to be part of the package's functionality, they should be added to __all__ to explicitly define the public interface.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the imported entities are used elsewhere in the codebase.

# Search for usages of MBAppV2
rg 'MBAppV2' src/examples/

# Search for usages of get_api and get_api_v2
rg 'get_api\b' src/examples/
rg 'get_api_v2\b' src/examples/

Length of output: 613


Script:

#!/bin/bash
# Check if __all__ is already defined in __init__.py
rg "__all__" src/examples/__init__.py

Length of output: 37

🧰 Tools
🪛 Ruff (0.8.0)

32-32: .app_v2.MBAppV2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .base.get_api imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .base.get_api_v2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

Comment on lines 32 to 35
from .base import BASE_URL, Scheduler, API, ShelveAPI, APIV2
from .errors import APIError, SecurityError
from .mb import MBAPI
from .payment import PaymentAPI
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

These imports are part of the public API and should be added to __all__

The imports in __init__.py are intentionally re-exported for public use, as evidenced by:

  • They are used in example files (src/examples/base.py uses ShelveAPI and APIV2)
  • They are part of the core API functionality (test files use them)
  • The package is published to PyPI as shown in setup.py
  • These classes represent the main API interfaces for the Easypay service

Add the following to src/easypay/__init__.py:

__all__ = [
    "BASE_URL",
    "Scheduler",
    "API",
    "ShelveAPI",
    "APIV2",
    "APIError",
    "SecurityError",
    "MBAPI",
    "PaymentAPI"
]
🔗 Analysis chain

Unused imports may need to be added to __all__ or removed

The imported entities BASE_URL, Scheduler, API, ShelveAPI, APIV2, APIError, SecurityError, MBAPI, and PaymentAPI are not used within this module, as flagged by Ruff (F401).

Suggested Actions:

  • If these imports are meant to be part of the package's public API, add them to the __all__ list.
  • If they are not needed, remove the unused imports to clean up the code.

Verification Script:

This script helps determine whether the imports should be included in __all__ or removed.


Let me gather more information to understand how these imports are used across the codebase.


Let me check one more thing to see if these imports are used in any test files or examples, as they might be part of the public API.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the imported entities are intended for public API exposure.

# Inspect __all__ in __init__.py
rg '__all__' src/easypay/__init__.py

# List the contents of src/easypay/__init__.py to manually verify
cat src/easypay/__init__.py

Length of output: 1257


Script:

#!/bin/bash
# Check if these imported entities are used in other files that import from easypay
rg -l "from easypay import" --type py
rg "from easypay import.*?(?:BASE_URL|Scheduler|API|ShelveAPI|APIV2|APIError|SecurityError|MBAPI|PaymentAPI)" --type py

# Check if there are any relative imports of these entities
rg "from \. import.*?(?:BASE_URL|Scheduler|API|ShelveAPI|APIV2|APIError|SecurityError|MBAPI|PaymentAPI)" --type py

# Check the content of payment.py since it's imported but not shown in the initial files
cat src/easypay/payment.py

Length of output: 1668


Script:

#!/bin/bash
# Check test files for usage of these imports
fd -e py -e ipynb . test/ examples/ 2>/dev/null | xargs rg "(?:BASE_URL|Scheduler|API|ShelveAPI|APIV2|APIError|SecurityError|MBAPI|PaymentAPI)"

# Check if there's a setup.py that might indicate these are part of the public API
cat setup.py 2>/dev/null || true

Length of output: 11385

🧰 Tools
🪛 Ruff (0.8.0)

32-32: .base.BASE_URL imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.Scheduler imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.API imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.ShelveAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.APIV2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.APIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.SecurityError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


34-34: .mb.MBAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


35-35: .payment.PaymentAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

Comment on lines 45 to 49
def get_api_v2(api_class=easypay.APIV2):
return api_class(
client_id=appier.conf("EASYPAY_CLIENT_ID"),
key=appier.conf("EASYPAY_KEY"),
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for missing configuration

The new get_api_v2 function should validate the presence of required configuration parameters to prevent runtime errors.

Consider adding validation:

 def get_api_v2(api_class=easypay.APIV2):
+    client_id = appier.conf("EASYPAY_CLIENT_ID", mandatory=True)
+    key = appier.conf("EASYPAY_KEY", mandatory=True)
     return api_class(
-        client_id=appier.conf("EASYPAY_CLIENT_ID"),
-        key=appier.conf("EASYPAY_KEY"),
+        client_id=client_id,
+        key=key,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_api_v2(api_class=easypay.APIV2):
return api_class(
client_id=appier.conf("EASYPAY_CLIENT_ID"),
key=appier.conf("EASYPAY_KEY"),
)
def get_api_v2(api_class=easypay.APIV2):
client_id = appier.conf("EASYPAY_CLIENT_ID", mandatory=True)
key = appier.conf("EASYPAY_KEY", mandatory=True)
return api_class(
client_id=client_id,
key=key,
)

Comment on lines 46 to 50
@appier.route("/generate/create", "GET")
def create_payment(self):
amount = self.field("amount", 10)
method = self.field("method", "mb")
return self.api.create_payment(amount=amount, method=method)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Use POST method for payment creation

Creating payments should use POST instead of GET to:

  1. Prevent accidental payment creation through browser prefetching
  2. Follow REST conventions for resource creation
  3. Avoid sensitive data in URL/logs

Apply these changes:

-    @appier.route("/generate/create", "GET")
+    @appier.route("/generate/create", "POST")
     def create_payment(self):
         amount = self.field("amount", 10)
+        if not isinstance(amount, (int, float)) or amount <= 0:
+            raise appier.ValidationError("Invalid amount")
         method = self.field("method", "mb")
+        if method not in ["mb", "cc", "dd"]:  # add valid payment methods
+            raise appier.ValidationError("Invalid payment method")
         return self.api.create_payment(amount=amount, method=method)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@appier.route("/generate/create", "GET")
def create_payment(self):
amount = self.field("amount", 10)
method = self.field("method", "mb")
return self.api.create_payment(amount=amount, method=method)
@appier.route("/generate/create", "POST")
def create_payment(self):
amount = self.field("amount", 10)
if not isinstance(amount, (int, float)) or amount <= 0:
raise appier.ValidationError("Invalid amount")
method = self.field("method", "mb")
if method not in ["mb", "cc", "dd"]: # add valid payment methods
raise appier.ValidationError("Invalid payment method")
return self.api.create_payment(amount=amount, method=method)

Comment on lines +38 to +40
def __init__(self, *args, **kwargs):
appier.APIApp.__init__(self, name="mb_v2", *args, **kwargs)
self.api = base.get_api_v2()
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix constructor parameter ordering

The constructor has star-args unpacking after keyword arguments, which is discouraged.

Apply this fix:

-    def __init__(self, *args, **kwargs):
-        appier.APIApp.__init__(self, name="mb_v2", *args, **kwargs)
+    def __init__(self, **kwargs):
+        appier.APIApp.__init__(self, name="mb_v2", **kwargs)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self, *args, **kwargs):
appier.APIApp.__init__(self, name="mb_v2", *args, **kwargs)
self.api = base.get_api_v2()
def __init__(self, **kwargs):
appier.APIApp.__init__(self, name="mb_v2", **kwargs)
self.api = base.get_api_v2()
🧰 Tools
🪛 Ruff (0.8.0)

39-39: Star-arg unpacking after a keyword argument is strongly discouraged

(B026)

Copy link

@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

🧹 Outside diff range and nitpick comments (1)
src/examples/app_v2.py (1)

36-55: Consider API versioning best practices and documentation

As part of the migration to API v2.0, consider the following architectural improvements:

  1. Add version prefix to routes for better API versioning:
-    @appier.route("/payments", "GET")
+    @appier.route("/v2/payments", "GET")
  1. Add OpenAPI/Swagger documentation using appier's documentation decorators to make the API self-documenting.

  2. Consider implementing API versioning through content negotiation using Accept headers as an alternative to URL versioning.

Would you like me to provide examples of implementing these suggestions?

🧰 Tools
🪛 Ruff (0.8.0)

39-39: Star-arg unpacking after a keyword argument is strongly discouraged

(B026)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a1f2221 and 7b9a843.

📒 Files selected for processing (2)
  • src/easypay/payment.py (1 hunks)
  • src/examples/app_v2.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/easypay/payment.py
🧰 Additional context used
🪛 Ruff (0.8.0)
src/examples/app_v2.py

39-39: Star-arg unpacking after a keyword argument is strongly discouraged

(B026)

🔇 Additional comments (2)
src/examples/app_v2.py (2)

38-40: Fix constructor parameter ordering

The constructor has star-args unpacking after keyword arguments, which is discouraged.

🧰 Tools
🪛 Ruff (0.8.0)

39-39: Star-arg unpacking after a keyword argument is strongly discouraged

(B026)


46-51: ⚠️ Potential issue

Security: Use POST method for payment creation and add input validation

Creating payments should use POST instead of GET for security reasons. Additionally, input validation is required.

-    @appier.route("/payments/create", "GET")
+    @appier.route("/payments/create", "POST")
     def create_payment(self):
         amount = self.field("amount", 10)
+        if not isinstance(amount, (int, float)) or amount <= 0:
+            raise appier.ValidationError("Invalid amount")
         method = self.field("method", "mb")
+        if method not in ["mb", "cc", "dd"]:
+            raise appier.ValidationError("Invalid payment method")
         return self.api.create_payment(amount=amount, method=method)

Likely invalid or redundant comment.

Comment on lines +52 to +55
@appier.route("/payments/notify", "GET")
def notify_payment(self):
print("Payment notified")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance payment notification endpoint security

The notification endpoint needs proper authentication and validation to prevent unauthorized access and ensure data integrity.

-    @appier.route("/payments/notify", "GET")
+    @appier.route("/payments/notify", "POST")
+    @appier.ensure_auth
     def notify_payment(self):
-        print("Payment notified")
+        notification_data = self.request.get_json()
+        if not self._verify_notification_signature(notification_data):
+            raise appier.SecurityError("Invalid notification signature")
+        
+        payment_id = notification_data.get("payment_id")
+        status = notification_data.get("status")
+        
+        # Log the notification for audit purposes
+        self.logger.info(f"Payment {payment_id} status updated to {status}")
+        
+        return dict(success=True)
+
+    def _verify_notification_signature(self, data):
+        # Implement signature verification logic here
+        # using Easypay's webhook signature verification
+        return True  # TODO: Replace with actual implementation

Would you like me to provide the implementation for the signature verification method?

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +42 to +44
@appier.route("/payments", "GET")
def payments(self):
return self.api.list_payments()
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add pagination support for payments listing

For production use, the payments endpoint should implement pagination to handle large datasets efficiently and prevent performance issues.

     @appier.route("/payments", "GET")
     def payments(self):
-        return self.api.list_payments()
+        page = self.field("page", 1, cast=int)
+        size = self.field("size", 20, cast=int)
+        return self.api.list_payments(page=page, size=size)

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (1)
src/easypay/base.py (1)

463-463: Consider v2.0-specific scheduler implementation

The current implementation reuses the v1.0 Scheduler, which is designed for MB (Multibanco) detail polling. Since API v2.0 might have different polling requirements or mechanisms, consider:

  1. Creating a v2.0-specific scheduler
  2. Documenting why v1.0 scheduler is reused if it's intentional
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7b9a843 and ec49b5e.

📒 Files selected for processing (1)
  • src/easypay/base.py (3 hunks)
🔇 Additional comments (3)
src/easypay/base.py (3)

45-45: LGTM! Well-structured constants and imports

The new imports and API endpoint constants are well-documented and follow the existing pattern of separating production and test environments.

Also applies to: 61-68


444-464: LGTM! Well-structured API v2.0 implementation

The APIV2 class is well-designed with:

  • Clear separation from v1.0 implementation
  • Proper configuration management
  • Thread-safe implementation

465-481: ⚠️ Potential issue

Initialize headers dictionary to prevent potential TypeError

The build method attempts to modify headers without ensuring it's initialized as a dictionary. This could raise a TypeError if headers is None.

Apply this fix:

 def build(
     self,
     method,
     url,
     data=None,
     data_j=None,
     data_m=None,
     headers=None,
     params=None,
     mime=None,
     kwargs=None,
 ):
+    if headers is None:
+        headers = {}
     appier.API.build(
         self,
         method,
         url,
         headers,
         kwargs
     )
     if self.account_id:
         headers["AccountId"] = self.account_id
     if self.key:
         headers["ApiKey"] = self.key

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (1)
src/easypay/base.py (1)

61-68: Consider externalizing base URLs to configuration

Defining BASE_URL_V2 and BASE_URL_TEST_V2 within the code makes it less flexible to manage different environments or future URL changes. Consider externalizing these URLs to a configuration file or environment variables to enhance maintainability and adaptability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ec49b5e and 36f110c.

📒 Files selected for processing (3)
  • src/easypay/__init__.py (1 hunks)
  • src/easypay/base.py (3 hunks)
  • src/examples/base.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/examples/base.py
🧰 Additional context used
🪛 Ruff (0.8.0)
src/easypay/__init__.py

32-32: .base.BASE_URL imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.Scheduler imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.API imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.ShelveAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.APIv2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.APIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.SecurityError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


34-34: .mb.MBAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


35-35: .payment.PaymentAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

🔇 Additional comments (2)
src/easypay/base.py (1)

465-481: ⚠️ Potential issue

Handle headers correctly in the build method to prevent errors

There are potential issues in the build method of the APIv2 class:

  1. Possible TypeError if headers is None:

    Assigning values to headers without ensuring it is a dictionary can raise a TypeError if headers is None.

  2. Incorrect parameter passing to the base class build method:

    The call to appier.API.build() may not correctly match the expected parameters, potentially leading to unexpected behavior.

Suggested Fix:

  • Initialize headers as an empty dictionary if it's None.
  • Pass all necessary parameters to the base class build method.

Apply the following changes:

 def build(
     self,
     method,
     url,
     data=None,
     data_j=None,
     data_m=None,
     headers=None,
     params=None,
     mime=None,
     kwargs=None,
 ):
+    if headers is None:
+        headers = {}
     appier.API.build(
         self,
         method,
         url,
         data=data,
         data_j=data_j,
         data_m=data_m,
         headers=headers,
         params=params,
         mime=mime,
         kwargs=kwargs,
     )
     if self.account_id:
         headers["AccountId"] = self.account_id
     if self.key:
         headers["ApiKey"] = self.key
src/easypay/__init__.py (1)

32-35: Add imported entities to __all__ or remove unused imports

The imported entities (BASE_URL, Scheduler, API, ShelveAPI, APIv2, APIError, SecurityError, MBAPI, PaymentAPI) are not used within this module. If they are intended to be part of the public API, consider adding them to the __all__ list to explicitly export them. Otherwise, remove the unused imports to clean up the code.

🧰 Tools
🪛 Ruff (0.8.0)

32-32: .base.BASE_URL imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.Scheduler imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.API imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.ShelveAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


32-32: .base.APIv2 imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.APIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: .errors.SecurityError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


34-34: .mb.MBAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


35-35: .payment.PaymentAPI imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

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.

Easypay API 2.0
1 participant