@@ -19,37 +19,22 @@ class ChargeViewModel: ObservableObject {
1919 }
2020
2121 @MainActor
22- func verifyAccessCodeAndProceedWithCard( ) async {
23- var supportedChannels : [ SupportedChannels ] = [ ]
22+ func verifyAccessCodeAndProceed( ) async {
2423 do {
2524 transactionState = . loading( )
26- let accessCodeResponse = try await repository. verifyAccessCode ( accessCode)
27- guard accessCodeResponse. paymentChannels. contains ( where: { $0 == . card || $0 == . mobileMoney } ) else {
28- let message = " Card/MPesa payments are not supported. " +
25+ let response = try await repository. verifyAccessCode ( accessCode)
26+ let supported = resolveSupportedChannels ( from: response)
27+
28+ guard !supported. isEmpty else {
29+ let message = " No supported payment methods. " +
2930 " Please reach out to your merchant for further information "
30- let cause = " There are currently no payment channels on " +
31- " your integration that are supported by the SDK"
31+ let cause = " No payment channels on this integration " +
32+ " are supported by the SDK "
3233 throw ChargeError ( displayMessage: message, causeMessage: cause)
3334 }
3435
35- let mobileMoneyChannel = accessCodeResponse. channelOptions? . mobileMoney? . contains ( where: { $0. key == SupportedChannels . MPESA. rawValue } ) ?? false
36-
37- accessCodeResponse. paymentChannels. forEach {
38- if $0 == . card {
39- supportedChannels. append ( . CARD)
40- }
41- if $0 == . mobileMoney && accessCodeResponse. channelOptions? . mobileMoney? . contains ( where: { $0. key == SupportedChannels . MPESA. rawValue } ) ?? false {
42- supportedChannels. append ( . MPESA)
43- }
44- }
45-
46- if mobileMoneyChannel {
47- transactionState = . channelSelection(
48- transactionInformation: accessCodeResponse, supportedChannels: supportedChannels)
49- } else {
50- self . transactionDetails = accessCodeResponse
51- transactionState = . payment( type: . card( transactionInformation: accessCodeResponse) )
52- }
36+ transactionDetails = response
37+ transactionState = nextState ( for: supported, response: response)
5338 } catch {
5439 let error = ChargeError ( error: error)
5540 Logger . error ( " Verify access code failed with error: %@ " ,
@@ -59,24 +44,62 @@ class ChargeViewModel: ObservableObject {
5944 }
6045 }
6146
62- }
47+ private func resolveSupportedChannels( from response: VerifyAccessCode ) -> [ SupportedChannel ] {
48+ var result : [ SupportedChannel ] = [ ]
49+
50+ if response. paymentChannels. contains ( . card) {
51+ result. append ( . card)
52+ }
53+
54+ if response. paymentChannels. contains ( . mobileMoney) ,
55+ let providers = response. channelOptions? . mobileMoney, !providers. isEmpty {
56+ let allowed = filtered ( providers)
57+ result. append ( contentsOf: allowed. map { . mobileMoney( $0) } )
58+ }
59+
60+ return result
61+ }
6362
64- enum SupportedChannels : String , CaseIterable {
65- case CARD = " CARD "
66- case MPESA = " MPESA "
67- case unsupportedChannel
68-
69- var image : Image {
70- switch self {
71- case . CARD:
72- return Image ( " cardLogo " , bundle: . current)
73- case . MPESA:
74- return Image ( " kenyaSHLogo " , bundle: . current)
75- case . unsupportedChannel:
76- return Image ( systemName: " exclamationmark.triangle.fill " )
63+ private func filtered( _ providers: [ MobileMoneyChannel ] ) -> [ MobileMoneyChannel ] {
64+ guard let allowlist = Self . supportedMobileMoneyProviders else {
65+ return providers
7766 }
67+ return providers. filter { allowlist. contains ( $0. key. uppercased ( ) ) }
7868 }
7969
70+ private func nextState( for channels: [ SupportedChannel ] ,
71+ response: VerifyAccessCode ) -> ChargeState {
72+ if channels. count == 1 , case . card = channels [ 0 ] {
73+ return . payment( type: . card( transactionInformation: response) )
74+ }
75+
76+ if !channels. contains ( . card) ,
77+ channels. count == 1 ,
78+ case . mobileMoney( let provider) = channels [ 0 ] {
79+ return . payment( type: . mobileMoney( transactionInformation: response,
80+ provider: provider) )
81+ }
82+
83+ return . channelSelection( transactionInformation: response,
84+ supportedChannels: channels)
85+ }
86+
87+ }
88+
89+ // MARK: - Mobile money provider allowlist
90+ extension ChargeViewModel {
91+
92+ /// Mobile money provider keys (`MobileMoneyChannel.key`, uppercased) that
93+ /// the SDK is allowed to route to. The API may return providers we don't
94+ /// yet have logos, copy, or phone formatters for — listing them here is
95+ /// what opts them into the UI.
96+ ///
97+ /// Set to `nil` to accept every provider the API returns (no filtering).
98+ /// Add a new key here when you've added its logo to `SupportedChannel.image`
99+ /// and its country code / phone formatter to the relevant helpers.
100+ static var supportedMobileMoneyProviders : Set < String > ? = [
101+ " MPESA " , " ATL_KE " , " MTN " , " ATL " , " VOD "
102+ ]
80103}
81104
82105// MARK: - Charge Container
0 commit comments