Skip to content

Add Capitec Pay and QR (Scan to Pay) charge flows - #132

Merged
peter-paystack merged 10 commits into
mainfrom
feature/Capitec-and-Scan-to-Pay
Sep 8, 2026
Merged

Add Capitec Pay and QR (Scan to Pay) charge flows#132
peter-paystack merged 10 commits into
mainfrom
feature/Capitec-and-Scan-to-Pay

Conversation

@peter-paystack

Copy link
Copy Markdown
Collaborator

Introduce two new payment methods end to end, following the existing
Views/Viewmodels/Models/Repository layout under PaystackUI/Charge.

Core (PaystackSDK):

  • CapitecPay: authenticate endpoint via CapitecPayService, with CapitecPayAuthenticateRequest/Response models
  • QR: generate endpoint via QRService, with QRGenerateRequest/Response models
  • Add Capitec/QR cases to Channel

UI (PaystackUI):

  • Capitec Pay flow: identifier entry, awaiting-approval, info banner, view model, repository, and South African ID/phone validators
  • QR flow: display view, variant handling, view model, repository, and QRChannelDirectory
  • Wire both into ChargeView/ChargeViewModel, channel selection, and supported-channel/payment-type models
  • Add reusable CopiedToast component

Tests:

  • API, repository, view model, and validator coverage for both flows, plus QRChannelDirectory and updated Charge view model/repository tests

  Introduce two new payment methods end to end, following the existing
  Views/Viewmodels/Models/Repository layout under PaystackUI/Charge.

  Core (PaystackSDK):
  - CapitecPay: authenticate endpoint via CapitecPayService, with
    CapitecPayAuthenticateRequest/Response models
  - QR: generate endpoint via QRService, with QRGenerateRequest/Response
    models
  - Add Capitec/QR cases to Channel

  UI (PaystackUI):
  - Capitec Pay flow: identifier entry, awaiting-approval, info banner,
    view model, repository, and South African ID/phone validators
  - QR flow: display view, variant handling, view model, repository, and
    QRChannelDirectory
  - Wire both into ChargeView/ChargeViewModel, channel selection, and
    supported-channel/payment-type models
  - Add reusable CopiedToast component

  Tests:
  - API, repository, view model, and validator coverage for both flows,
    plus QRChannelDirectory and updated Charge view model/repository tests
@peter-paystack peter-paystack self-assigned this Jul 23, 2026
@peter-paystack peter-paystack added the enhancement New feature or request label Jul 23, 2026
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

  - Add CapitecResponse/CapitecResponseData and ChargeCapitecTransaction so
    requery no longer reuses the card ChargeResponse shape
  - Change postRequery from POST with an empty body to a GET
  - Restore OAEP encryption for Capitec client data and drop the unused
    PKCS#1 helper from Cryptography
  - Use the real device fingerprint instead of the hardcoded test value
  - Set QR generate source to "checkout" and bump bindings version to 2.1.0
  Capitec Pay publishes its own event shape rather than the flat
  Charge3DSResponse used by card 3-D Secure, mobile money, Zap, QR and bank
  transfer: the top-level `status` is a Bool and the transaction status is
  nested at `data.status`. Decoding it as Charge3DSResponse threw, and
  because PusherSubscriptionListener is single-shot, that decode failure
  burned the subscription — the customer then sat on the approval screen
  until the countdown expired.

  - Add CapitecPusherResponse / CapitecPusherResponseData with every field
    below `status` optional, so a partial envelope still decodes instead of
    dropping the subscription. listenForCapitecPayResponse now returns it.
  - Route both resolution paths (Pusher event and requery) through
    ChargeCapitecTransaction so terminal status is interpreted in one place;
    status is trimmed + lowercased, and `status: false` maps to a failure
    regardless of `data`. Carry the server message through as the failure
    reason instead of always using the fallback copy.
  - On a Pusher await failure, start the requery loop immediately rather
    than waiting out the countdown, leaving `state` untouched so the
    approval steps stay on screen. Guard startRequeryLoop so the first of
    its two entry points owns the loop.
  - Add CapitecPusherPending / CapitecRequeryResponse fixtures (registered
    in Package.swift) and ChargeCapitecTransactionTests; extend the API,
    repository and view-model tests to cover the new envelope and fallback.
  06b834e dropped Cryptography.encryptPKCS1 and reverted the protocol to
  encrypt(text:publicKey:), and changed the QR generate source default to
  "checkout", but left the tests calling the removed method — the test
  target no longer compiled.

  - Drop the four PKCS#1 tests and the decryptPKCS1 helper from
    CryptographyTests; each had an exact OAEP twin in the same file.
  - Rename CryptographyEncryptPKCS1Tests to CryptographyEncryptIdentifierTests
    and move it onto encrypt/OAEP-SHA1, keeping its unique coverage: a
    generated 2048-bit key pair, the Capitec clientdata plaintext shapes, and
    non-deterministic ciphertext.
  - Update the QR default-source test to expect "checkout".
  - Bump all four workflows from macos-15 to macos-26, Xcode 26.3 to 26.4,
    and the test destination from iOS 26.2 to 26.4
  - Replace the scanToPayLogo PNG with an SVG
  - Add @zaheer-paystack to CODEOWNERS

  Two things to check before committing:

  - The new asset is named Scan to Pay Logo (2).svg — spaces and the (2) suffix suggest a browser download name.
    Worth renaming to something like scanToPayLogo.svg and updating Contents.json to match.
  - The SVG is registered only as 1x in the images array; if 2x/3x slots still reference the deleted PNG, the
    asset won't resolve at those scales. A vector asset usually wants "preserves-vector-representation": true and
    a single universal entry instead.
  The macos-26 runner has Xcode 26.4 but not the iOS 26.4 simulator
  runtime, so "OS=26.4,name=iPhone 17 Pro" failed to match a device.
  Drop the OS and -sdk pins so xcodebuild uses whatever runtime the
  runner has.
  The Scan to Pay / SnapScan QR channel publishes its own shape rather
  than the flat Charge3DSResponse used by card 3DS, mobile money, Zap and
  bank transfer: top-level `status` is a Bool, `trans` is a JSON number,
  and the redirect key is spelled `redirecturl` (all lowercase, so
  convertFromSnakeCase never maps it). Add QRPusherResponse with explicit
  coding keys and a flexible String/Int decode for `trans`, and return it
  from listenForQRResponse.

  When the single-shot listener errors out, QRViewModel now degrades to
  one checkPending call instead of silently logging: success routes to the
  container, failure surfaces the server message, non-terminal leaves the
  QR on screen with the manual button available. The check is armed once
  per QR so a failed re-subscribe from the manual tap can't loop, and
  retry() re-arms it.

  Fixtures updated to the real wire payloads.
@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@peter-paystack
peter-paystack marked this pull request as ready for review September 8, 2026 12:28
@peter-paystack
peter-paystack merged commit c738733 into main Sep 8, 2026
4 of 6 checks passed
@peter-paystack
peter-paystack deleted the feature/Capitec-and-Scan-to-Pay branch September 8, 2026 12:50
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.

2 participants