Skip to content

MOB-875 Adding Mpesa as a Payment Method#119

Merged
Peter-John-paystack merged 15 commits intomainfrom
feature/MPESA
Apr 22, 2026
Merged

MOB-875 Adding Mpesa as a Payment Method#119
Peter-John-paystack merged 15 commits intomainfrom
feature/MPESA

Conversation

@Peter-John-paystack
Copy link
Copy Markdown
Collaborator

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.

	- 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.
Copy link
Copy Markdown
Collaborator

@zaheer-paystack zaheer-paystack left a comment

Choose a reason for hiding this comment

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

I don't have a ton of context on the actual payment flow so may have missed something there but otherwise LGTM

Comment thread Sources/PaystackSDK/Core/Service/Subscription/PusherSubscriptionListener.swift Outdated
Comment thread Sources/PaystackUI/Charge/MobileMoney/Viewmodels/MPesaChrageViewModel.swift Outdated
  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).
  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.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 22, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Peter-John-paystack Peter-John-paystack marked this pull request as ready for review April 22, 2026 08:48
@Peter-John-paystack Peter-John-paystack merged commit 9d4672f into main Apr 22, 2026
4 of 6 checks passed
@Peter-John-paystack Peter-John-paystack deleted the feature/MPESA branch April 22, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants