Skip to content

Feat/async b2b service#130

Merged
RafaelJohn9 merged 5 commits intomasterfrom
feat/async-b2b-service
Apr 4, 2026
Merged

Feat/async b2b service#130
RafaelJohn9 merged 5 commits intomasterfrom
feat/async-b2b-service

Conversation

@RafaelJohn9
Copy link
Copy Markdown
Member

@RafaelJohn9 RafaelJohn9 commented Apr 4, 2026

Description

fixes #72

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires documentation update
  • Refactor (code structure improvements, no new functionality)
  • Tests (addition or improvement of tests)
  • Chore (changes to tooling, CI/CD, or metadata)

How Has This Been Tested?

  • I have added Unit Tests to test this functionality.

Checklist

  • My code follows the project's coding style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Screenshots (if applicable)

Additional Context

Summary by CodeRabbit

  • New Features

    • Added asynchronous payment processing methods for B2B transactions, enabling non-blocking operations for express checkout, bill payments, and goods purchases.
  • Chores

    • Updated package status from Beta to Production/Stable.
    • Expanded test coverage for async operations.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 13 seconds.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e10025c-1e41-41eb-93e2-16255f1c58cd

📥 Commits

Reviewing files that changed from the base of the PR and between 664853b and 43daefb.

📒 Files selected for processing (2)
  • .github/workflows/code-quality.yml
  • tests/unit/services/test_b2b_service.py
📝 Walkthrough

Walkthrough

This PR introduces an AsyncB2BService facade class alongside configuration and documentation updates. It adds async methods for B2B transactions (express_checkout, paybill, buygoods) that construct request models and invoke underlying async client methods. The package classifier is updated to Production/Stable, and comprehensive test coverage is added for the new async functionality.

Changes

Cohort / File(s) Summary
Schema Examples
mpesakit/mpesa_express/schemas.py
Updated example password values from base64-like string "bXlwYXNzd29yZA==" to placeholder "B64_ENCODED_PASSWORD" in StkPushSimulateRequest and StkPushQueryRequest JSON schema examples.
Package Configuration
pyproject.toml
Updated PyPI classifier from "Development Status :: 4 - Beta" to "Development Status :: 5 - Production/Stable". Added Hatch environment configuration with test, lint, format, typecheck, and security scripts; defined composite task lists check and ci.
Async Service Implementation
mpesakit/services/b2b.py
Added new AsyncB2BService class with async methods express_checkout, paybill, and buygoods. Each method filters kwargs against the corresponding request model's fields and awaits the underlying async client call. Requires AsyncHttpClient and AsyncTokenManager in constructor.
Service Module Export
mpesakit/services/__init__.py
Imported AsyncB2BService from .b2b module; note that __all__ was not updated to include it, so the class remains unexported from the module's public API.
Async Service Tests
tests/unit/services/test_b2b_service.py
Added async test fixtures for AsyncTokenManager and AsyncHttpClient. Implemented async unit tests (marked with pytest.mark.asyncio) validating response types and field presence for express_checkout, paybill, and buygoods methods, plus tests for kwarg filtering and dependency initialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through async code so fine,
With AsyncB2BService running in time,
Filtering kwargs, awaiting each call,
Production-stable, it handles it all! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Most changes directly support the AsyncB2BService feature (issue #72). However, the example password value updates in schemas.py and the pyproject.toml classifier/tooling changes appear tangential to the core async service implementation objective. Clarify whether the schemas and pyproject.toml changes are necessary dependencies for this PR or separate improvements that should be in a different PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/async b2b service' directly reflects the main change—adding async B2B service functionality. It is concise, clear, and accurately summarizes the primary feature introduced.
Description check ✅ Passed The PR description includes the required sections: a clear problem statement (fixes #72), a marked 'New feature' type, testing confirmation (unit tests added), and completed checklist items covering code style, self-review, documentation, testing, and verification.
Linked Issues check ✅ Passed The PR fully addresses issue #72's objectives: AsyncB2BService is implemented in b2b.py with AsyncHttpClient/AsyncTokenManager dependency injection, exported in init.py (though with a minor gap), and comprehensive unit tests are added in test_b2b_service.py covering the new async methods.
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
  • Commit unit tests in branch feat/async-b2b-service

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.

@RafaelJohn9 RafaelJohn9 changed the base branch from develop to master April 4, 2026 21:20
Signed-off-by: RafaelJohn9 <rafaeljohb@gmail.com>
…ack initial password values.

Signed-off-by: RafaelJohn9 <rafaeljohb@gmail.com>
Copy link
Copy Markdown
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)
mpesakit/services/__init__.py (1)

1-1: Keep __all__ in sync with the new facade.

AsyncB2BService is imported on Line 1, but it is still missing from __all__. If this is meant to be a supported package-level export, wildcard imports and any tooling that relies on the package export list will miss it.

📦 Suggested update
 __all__ = [
+    "AsyncB2BService",
     "B2BService",
     "B2CService",
     "BalanceService",

Also applies to: 13-25

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

In `@mpesakit/services/__init__.py` at line 1, The package exports list __all__ is
missing the newly imported AsyncB2BService, so update the module-level __all__
to include both B2BService and AsyncB2BService; locate the import of
AsyncB2BService in mpesakit/services/__init__.py and add "AsyncB2BService" to
the __all__ sequence (alongside "B2BService") so wildcard imports and tooling
correctly expose the new facade.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mpesakit/mpesa_express/schemas.py`:
- Line 92: Bandit flags the placeholder secret in the schema examples (the
"Password" key with value "B64_ENCODED_PASSWORD" and the other example at the
same pattern near line ~502) as B105; mark these example literal values as false
positives by appending a targeted inline suppression comment (# nosec B105) to
the lines containing the placeholder strings (e.g., the "Password":
"B64_ENCODED_PASSWORD" entry) so Bandit skips them, and apply the same inline #
nosec B105 to the other corresponding example entry mentioned in the review.

In `@tests/unit/services/test_b2b_service.py`:
- Around line 172-177: The fixture mock_async_http_client is defined inside
test_b2b_service_initializes_services_correctly so pytest won't register it;
move the `@pytest.fixture` def mock_async_http_client(...) (the
MagicMock(spec=HttpClient) with client.post = AsyncMock()) out of that test and
dedent it to module scope (or remove it entirely if unnecessary) so pytest can
discover and use it for other tests.
- Around line 309-317: The test's hasattr checks use public names that
AsyncB2BService doesn't set, so the wiring assertions are skipped; update the
test to inspect the actual stored delegate attributes (_express_checkout,
_business_paybill, _business_buygoods) or use getattr(service,
"_express_checkout", None) etc., then assert that each delegate's http_client
equals mock_async_http_client and token_manager equals mock_async_token_manager
(replace the three hasattr(..._service) blocks with checks against these private
attribute names or direct getattr assertions).

---

Nitpick comments:
In `@mpesakit/services/__init__.py`:
- Line 1: The package exports list __all__ is missing the newly imported
AsyncB2BService, so update the module-level __all__ to include both B2BService
and AsyncB2BService; locate the import of AsyncB2BService in
mpesakit/services/__init__.py and add "AsyncB2BService" to the __all__ sequence
(alongside "B2BService") so wildcard imports and tooling correctly expose the
new facade.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 465eb510-f536-4479-a1a2-9cab115048c9

📥 Commits

Reviewing files that changed from the base of the PR and between eecf1ba and 664853b.

📒 Files selected for processing (5)
  • mpesakit/mpesa_express/schemas.py
  • mpesakit/services/__init__.py
  • mpesakit/services/b2b.py
  • pyproject.toml
  • tests/unit/services/test_b2b_service.py

"example": {
"BusinessShortCode": 654321,
"Password": "bXlwYXNzd29yZA==",
"Password": "B64_ENCODED_PASSWORD",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bandit will keep failing on these schema examples.

The security job is already flagging both lines as B105, so this placeholder swap still blocks the PR even though the values are only documentation examples. Add a targeted # nosec B105 here, or move these examples out of Bandit's scan path.

🔧 Minimal fix
-                "Password": "B64_ENCODED_PASSWORD",
+                "Password": "B64_ENCODED_PASSWORD",  # nosec B105 - schema example
...
-                "Password": "B64_ENCODED_PASSWORD",
+                "Password": "B64_ENCODED_PASSWORD",  # nosec B105 - schema example

Also applies to: 502-502

🧰 Tools
🪛 GitHub Actions: Code Quality

[error] 92-92: Bandit (B105: hardcoded_password_string) found a possible hardcoded password string 'B64_ENCODED_PASSWORD' (CWE-259).

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

In `@mpesakit/mpesa_express/schemas.py` at line 92, Bandit flags the placeholder
secret in the schema examples (the "Password" key with value
"B64_ENCODED_PASSWORD" and the other example at the same pattern near line ~502)
as B105; mark these example literal values as false positives by appending a
targeted inline suppression comment (# nosec B105) to the lines containing the
placeholder strings (e.g., the "Password": "B64_ENCODED_PASSWORD" entry) so
Bandit skips them, and apply the same inline # nosec B105 to the other
corresponding example entry mentioned in the review.

@RafaelJohn9 RafaelJohn9 merged commit 7811106 into master Apr 4, 2026
11 checks passed
@RafaelJohn9 RafaelJohn9 deleted the feat/async-b2b-service branch April 4, 2026 21:45
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.

[Feat] AsyncB2BService

1 participant