MOB-875 Adding Mpesa as a Payment Method#119
Merged
Peter-John-paystack merged 15 commits intomainfrom Apr 22, 2026
Merged
Conversation
- Added Mobile Money as a supported channel - Added endpoint for Mobile Money charge - Added listener for mobile money events - Added unit tests for endpoint and listener for mobile money charge - Added Mock responses - Updated tests to support mobile money as a channel
- Added Mobile Money as a supported channel - Added endpoint for Mobile Money charge - Added listener for mobile money events - Added unit tests for endpoint and listener for mobile money charge - Added Mock responses - Updated tests to support mobile money as a channel
- Added Mobile Money as a supported channel - Added endpoint for Mobile Money charge - Added listener for mobile money events - Added unit tests for endpoint and listener for mobile money charge - Added Mock responses - Updated tests to support mobile money as a channel
- Added Mobile Money as a supported channel - Added endpoint for Mobile Money charge - Added listener for mobile money events - Added unit tests for endpoint and listener for mobile money charge - Added Mock responses - Updated tests to support mobile money as a channel
- Added Mobile Money as a supported channel - Added endpoint for Mobile Money charge - Added listener for mobile money events - Added unit tests for endpoint and listener for mobile money charge - Added Mock responses - Updated tests to support mobile money as a channel
…ature/MPESA # Conflicts: # .github/workflows/deploy.yml # Example/paystack-sdk-ios/ContentView.swift # PaystackCore.podspec # PaystackUI.podspec # Sources/PaystackSDK/Core/Service/Subscription/PusherSubscriptionListener.swift # Sources/PaystackSDK/Core/Service/URLRequest/Extensions/PaystackUserAgent.swift # Sources/PaystackSDK/Versioning/versions.plist # Tests/PaystackSDKTests/API/Charge/ChargeTests.swift
MPesaProcessingViewModel previously caught network errors and then
discarded them (commented-out display call), and routed non-success
transaction statuses to print(). Errors surfaced to the user only if
they originated in MPesaChrageViewModel.submitPhoneNumber, and even
then both .error and .fatalError auto-dismissed the flow — so the
customer was kicked out of the payment on recoverable failures
instead of being given a retry.
Introduce an MPesaContainer protocol on MPesaChrageViewModel mirroring
ChargeCardContainer. MPesaProcessingViewModel now routes both
listenForMPesa and checkPendingCharge through the container — success
and non-success statuses go to processTransactionResponse, thrown
errors go to displayTransactionError. The container maps .failed to
.error (retry) and .timeout / unexpected statuses to .fatalError
(auto-dismiss), matching ChargeCardViewModel.
MPesaChargeView now splits .error (shows "Try again" retry button
wired to restartMPesaPayment) from .fatalError (auto-dismiss with
ChargeErrorDetails), so recoverable errors no longer terminate the
payment.
TweetNacl/CTweetNacl Podfile fix:
Fix CTweetNacl module resolution in Example Pods build
Xcode 16+ / Swift 6 explicit-module builds fail to build PusherSwift
with "Unable to resolve module dependency: 'CTweetNacl'" because
TweetNacl's C submodule is declared in Pods/TweetNacl/Sources/module.map
but only TweetNacl's own target xcconfig gets that path on
SWIFT_INCLUDE_PATHS — consumers importing TweetNacl cannot find
CTweetNacl.
Extend the Podfile post_install hook to append
"${PODS_ROOT}/TweetNacl/Sources" to SWIFT_INCLUDE_PATHS on every
generated pod target so PusherSwift (and anything transitively
importing TweetNacl) can resolve the C submodule.
zaheer-paystack
previously approved these changes
Apr 21, 2026
Collaborator
zaheer-paystack
left a comment
There was a problem hiding this comment.
I don't have a ton of context on the actual payment flow so may have missed something there but otherwise LGTM
Pin every third-party action to a full commit SHA (with a version
comment) so workflow supply-chain changes are explicit:
- actions/checkout v4 -> v6.0.2
- maxim-lobanov/setup-xcode v1 -> v1.7.0
- maxim-lobanov/setup-cocoapods v1 -> v1.4.0
- peter-evans/create-pull-request v4 -> v8.1.1
- ruby/setup-ruby v1 -> v1.302.0
- danger-swift-with-swiftlint 3.15.0 -> 3.22.1
Replace the archived actions/create-release@v1 with
softprops/action-gh-release@v3.0.0 (release_name -> name).
Bump the build toolchain off the retired macos-12 / macos-14
runners and the Xcode 15.3 / iOS 17.4 simulator:
- runners: macos-12 / macos-14 / macos-latest -> macos-15
- Xcode: 15.3.0 -> 26.3
- simulator: iPhone 15 Pro / iOS 17.4 -> iPhone 17 Pro / iOS 26.2
macos-26 is GA but currently ships without all iOS 26 simulator
runtimes on arm64, so macos-15 is the safer landing point.
Cover the M-Pesa feature at the three layers the rest of the SDK
tests use (repository, ViewModel, charge-API):
* ChargeMobileMoneyRepositoryImplementationTests — chargeMobileMoney
(POST /charge/mobile_money), listenForMPesa (Pusher MOBILE_MONEY_<id>
channel, success and failed payloads), checkPendingCharge.
* MPesaChrageViewModelTests — phone validation, submitPhoneNumber
success/error paths, every processTransactionResponse branch
(success/failed/timeout/pending/unexpected), displayTransactionError,
restart, cancel.
* MPesaProcessingViewModelTests — initializeMPesaAuthorization
success/error, fire-and-forget checkTransactionStatus driven through
a closure hook + fulfillment, cancelTransaction, transactionDetails
proxy.
* MockChargeMobileMoneyRepository and MockMPesaContainer follow the
shape of the existing MockChargeCardRepository / MockChargeContainer.
Also picks up a few small drive-by fixes on the production side:
* MPesaChrageViewModel.cancelTransaction now calls restartMPesaPayment
instead of a TODO.
* ChargeCardViewModel marks its ChargeCardContainer conformance
@mainactor, matching MPesaChrageViewModel.
* Drop leftover print() calls in PusherSubscriptionListener.
* Drop the staging-URL comment in PaystackService.endpoint.
* Reformat ChargeTests.swift (whitespace only).
zaheer-paystack
previously approved these changes
Apr 21, 2026
Adds a `formattedKenyanPhoneNumber` String extension that accepts the three common user-entered formats (leading 0, 254, or +254) and normalizes them to a consistent +254 E.164 form. Applied in `MPesaChrageViewModel.submitPhoneNumber` so the repository always receives a uniformly formatted number. Covered by new `StringExtensionsTests` and additional `MPesaChrageViewModelTests` cases for each accepted prefix.
|
zaheer-paystack
approved these changes
Apr 22, 2026
michael-paystack
approved these changes
Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

MOB-875 Adding MobileMoney Charge endpoint
- Added Mobile Money as a supported channel
- Added endpoint for Mobile Money charge
- Added listener for mobile money events
- Added unit tests for endpoint and listener for mobile money charge
- Added Mock responses
- Updated tests to support mobile money as a channel
- Added Mpesa UI
TweetNacl/CTweetNacl Podfile fix:
Fix CTweetNacl module resolution in Example Pods build
Xcode 16+ / Swift 6 explicit-module builds fail to build PusherSwift
with "Unable to resolve module dependency: 'CTweetNacl'" because
TweetNacl's C submodule is declared in Pods/TweetNacl/Sources/module.map
but only TweetNacl's own target xcconfig gets that path on
SWIFT_INCLUDE_PATHS — consumers importing TweetNacl cannot find
CTweetNacl.
Extend the Podfile post_install hook to append
"${PODS_ROOT}/TweetNacl/Sources" to SWIFT_INCLUDE_PATHS on every
generated pod target so PusherSwift (and anything transitively
importing TweetNacl) can resolve the C submodule.