Skip to content

Commit

Permalink
Merge pull request omise#283 from omise/feature/MIT-1904
Browse files Browse the repository at this point in the history
 Implement white-label installment for KTC
  • Loading branch information
Andrei Solovev authored Apr 23, 2024
2 parents ea90145 + e43588a commit 5f6b083
Show file tree
Hide file tree
Showing 23 changed files with 145 additions and 61 deletions.
2 changes: 1 addition & 1 deletion ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/omise/omise-ios.git";
requirement = {
branch = feature/v5_refactoring;
branch = "feature/MIT-1904";
kind = branch;
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/omise/omise-ios.git",
"state" : {
"branch" : "feature/v5_refactoring",
"revision" : "a59269f3d734ebc17d8e717b5c03b17d5d67a679"
"branch" : "feature/MIT-1904",
"revision" : "0ed8ce39701b08433a2d5c0a678e95edd8b2b8ef"
}
}
],
Expand Down
15 changes: 15 additions & 0 deletions ExampleApp/Views/ProductDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@ extension ProductDetailViewController: ChoosePaymentMethodDelegate {
func choosePaymentMethodDidCancel() {
omiseSDK.dismiss()
}

func choosePaymentMethodDidComplete(with whiteLabelInstallmentSource: Source, token: Token) {
print("White-label installment payment Token is created with id '\(token.id)', Source id: '\(whiteLabelInstallmentSource.id)'")
omiseSDK.dismiss {
let alertController = UIAlertController(
title: "Token & Source Created",
message: "A token with id of \(token.id) and source with id of \(whiteLabelInstallmentSource.id) was successfully created. Please send this id to server to create a charge.",
preferredStyle: .alert
)
let okAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}

}
}

// MARK: - Custom Credit Card Form View Controller Delegate
Expand Down
1 change: 1 addition & 0 deletions OmiseSDK/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"SourceType.installment_scb" = "Siam Commercial Bank";
"SourceType.installment_ttb" = "TMBThanachart Bank";
"SourceType.installment_uob" = "United Overseas Bank";
"SourceType.installment_wlb_ktc" = "KTC";
"SourceType.internet_banking_bay" = "Bank of Ayudhya";
"SourceType.internet_banking_bbl" = "Bangkok Bank";
"SourceType.paynow" = "PayNow";
Expand Down
1 change: 1 addition & 0 deletions OmiseSDK/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"SourceType.installment_scb" = "サイアム・コマーシャル銀行";
"SourceType.installment_ttb" = "TMBタナチャート銀行";
"SourceType.installment_uob" = "ユナイテッド・オーバーシーズ銀行";
"SourceType.installment_wlb_ktc" = "クルンタイカード";
"SourceType.internet_banking_bay" = "アユタヤ銀行";
"SourceType.internet_banking_bbl" = "バンコク銀行";
"SourceType.paynow" = "PayNow";
Expand Down
1 change: 1 addition & 0 deletions OmiseSDK/Resources/th.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"SourceType.installment_scb" = "ธนาคารไทยพาณิชย์";
"SourceType.installment_ttb" = "ธนาคารทีเอ็มบีธนชาต";
"SourceType.installment_uob" = "ธนาคารยูโอบี";
"SourceType.installment_wlb_ktc" = "เคทีซี";
"SourceType.internet_banking_bay" = "ธนาคารกรุงศรีอยุธยา";
"SourceType.internet_banking_bbl" = "ธนาคารกรุงเทพ";
"SourceType.paynow" = "เพย์นาว";
Expand Down
10 changes: 10 additions & 0 deletions OmiseSDK/Sources/ChoosePaymentMethodDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// Omise Payment delegate protocol is required to process payment results
public protocol ChoosePaymentMethodDelegate: AnyObject {
func choosePaymentMethodDidComplete(with source: Source)
func choosePaymentMethodDidComplete(with token: Token)
func choosePaymentMethodDidComplete(with whiteLabelInstallmentSource: Source, token: Token)
func choosePaymentMethodDidComplete(with error: Error)
func choosePaymentMethodDidCancel()
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,6 @@ extension Source.Payment.Installment: Codable {
}

extension Source.Payment.Installment {
/// List of sourceTypes belongs to Installment payment type
public static var sourceTypes: [SourceType] {
[
.installmentBAY,
.installmentBBL,
.installmentFirstChoice,
.installmentKBank,
.installmentKTC,
.installmentMBB,
.installmentSCB,
.installmentTTB,
.installmentUOB
]
}

/// Available Installments terms (months) for given sourceType
public static func availableTerms(for sourceType: SourceType) -> [Int] {
switch sourceType {
Expand All @@ -50,7 +35,7 @@ extension Source.Payment.Installment {
return [ 4, 6, 8, 9, 10 ]
case .installmentMBB:
return [ 6, 12, 18, 24 ]
case .installmentKTC:
case .installmentKTC, .installmentWhiteLabelKTC:
return [ 3, 4, 5, 6, 7, 8, 9, 10 ]
case .installmentKBank:
return [ 3, 4, 6, 10 ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ extension Source.Payment: Codable {
self = try .trueMoneyWallet(TrueMoneyWallet(from: decoder))
case FPX.sourceType:
self = try .fpx(FPX(from: decoder))
case _ where Installment.sourceTypes.contains(sourceType):
case _ where sourceType.isInstallment:
self = try .installment(Installment(from: decoder))
default:
self = .sourceType(sourceType)
Expand All @@ -104,7 +104,7 @@ extension Source.Payment {
EContext.sourceType,
TrueMoneyWallet.sourceType,
FPX.sourceType
] + Installment.sourceTypes
] + SourceType.installments

return requiresDetails.contains(sourceType)
}
Expand Down
10 changes: 10 additions & 0 deletions OmiseSDK/Sources/OmiseAPI/SourceType/SourceType+Installments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ extension SourceType {
.installmentSCB,
.installmentTTB,
.installmentUOB
] + whiteLabelInstallments
}

public static var whiteLabelInstallments: [SourceType] {
[
.installmentWhiteLabelKTC
]
}

var isInstallment: Bool {
Self.installments.contains(self)
}

var isWhiteLabelInstallment: Bool {
Self.whiteLabelInstallments.contains(self)
}
}
2 changes: 2 additions & 0 deletions OmiseSDK/Sources/OmiseAPI/SourceType/SourceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public enum SourceType: String, Codable, CaseIterable {
case installmentTTB = "installment_ttb"
/// United Overseas Bank (UOB) https://docs.opn.ooo/installment-payments
case installmentUOB = "installment_uob"
/// Krungthai Card (KTC) https://docs.opn.ooo/installment-white-label-payments
case installmentWhiteLabelKTC = "installment_wlb_ktc"
/// Bank of Ayudhya (Krungsri) https://docs.opn.ooo/internet-banking
case internetBankingBAY = "internet_banking_bay"
/// Bangkok Bank https://docs.opn.ooo/internet-banking
Expand Down
20 changes: 0 additions & 20 deletions OmiseSDK/Sources/OmiseSDK.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
import Foundation
import UIKit

/// Omise Payment delegate protocol is required to process payment results
public protocol ChoosePaymentMethodDelegate: AnyObject {
func choosePaymentMethodDidComplete(with source: Source)
func choosePaymentMethodDidComplete(with token: Token)
func choosePaymentMethodDidComplete(with error: Error)
func choosePaymentMethodDidCancel()
}

/// Payment Results
public enum PaymentResult {
/// Payment completed with Card payment and contains Token object
case token(Token)
/// Payment completed with Source payment and contains Source object
case source(Source)
/// Payment completed with Error
case error(Error)
/// Payment was cancelled by user
case cancel
}

public class OmiseSDK {
/// Static container that allows to assign a shared instance of OmiseSDK to be used as a Singleton object
public static var shared = OmiseSDK(publicKey: "pkey_")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ extension ChoosePaymentCoordinator {
completion()
}
}

func processWhiteLabelInstallmentPayment(_ payment: Source.Payment, card: CreateTokenPayload.Card, completion: @escaping () -> Void) {

guard let delegate = choosePaymentMethodDelegate else { return }
let sourcePayload = CreateSourcePayload(
amount: amount,
currency: currency,
details: payment
)

let tokenPayload = CreateTokenPayload(card: card)

client.createSource(payload: sourcePayload) { [weak self, weak delegate, tokenPayload] result in
guard let self = self else { return }
switch result {
case .failure(let error):
self.processError(error)
case .success(let source):
self.client.createToken(payload: tokenPayload) { [weak self, weak delegate] result in
switch result {
case .success(let token):
delegate?.choosePaymentMethodDidComplete(with: source, token: token)
case .failure(let error):
self?.processError(error)
}
completion()
}
}
}
}
}

extension ChoosePaymentCoordinator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ class ChoosePaymentCoordinator: NSObject, ViewAttachable {
}

/// Creates Credit Carc Payment screen and attach current flow object inside created controller to be deallocated together
func createCreditCardPaymentController() -> CreditCardPaymentController {
let viewModel = CreditCardPaymentViewModel(currentCountry: currentCountry, delegate: self)
func createCreditCardPaymentController(paymentType: CreditCardPaymentOption = .card) -> CreditCardPaymentController {
let viewModel = CreditCardPaymentViewModel(currentCountry: currentCountry, paymentType: paymentType, delegate: self)
let viewController = CreditCardPaymentController(nibName: nil, bundle: .omiseSDK)
viewController.viewModel = viewModel
viewController.title = PaymentMethod.creditCard.localizedTitle
Expand Down Expand Up @@ -105,10 +105,16 @@ class ChoosePaymentCoordinator: NSObject, ViewAttachable {

/// Creates Installement screen and attach current flow object inside created controller to be deallocated together
func createInstallmentController() -> SelectPaymentController {
let sourceTypes = client.latestLoadedCapability?.availableSourceTypes(SourceType.installments)
var sourceTypes = client.latestLoadedCapability?.availableSourceTypes(SourceType.installments) ?? []

// Remove .installmentKTC if .installmentWhiteLabelKTC is present
if sourceTypes.contains(.installmentWhiteLabelKTC) {
sourceTypes.removeAll { $0 == .installmentKTC }
}

let viewModel = SelectSourceTypePaymentViewModel(
title: PaymentMethod.installment.localizedTitle,
sourceTypes: sourceTypes ?? [],
sourceTypes: sourceTypes,
delegate: self
)

Expand Down Expand Up @@ -191,10 +197,19 @@ extension ChoosePaymentCoordinator: FPXPaymentFormControllerDelegate {
}

extension ChoosePaymentCoordinator: CreditCardPaymentDelegate {
func didSelectCardPayment(_ card: CreateTokenPayload.Card, completion: @escaping () -> Void) {
processPayment(card, completion: completion)
func didSelectCardPayment(
paymentType: CreditCardPaymentOption,
card: CreateTokenPayload.Card,
completion: @escaping () -> Void
) {
switch paymentType {
case .card:
processPayment(card, completion: completion)
case .whiteLabelInstallment(let payment):
processWhiteLabelInstallmentPayment(payment, card: card, completion: completion)
}
}

func didCancelCardPayment() {
didCancelPayment()
}
Expand Down Expand Up @@ -249,6 +264,12 @@ extension ChoosePaymentCoordinator: SelectSourceTypeDelegate {

extension ChoosePaymentCoordinator: SelectSourcePaymentDelegate {
func didSelectSourcePayment(_ payment: Source.Payment, completion: @escaping () -> Void) {
processPayment(payment, completion: completion)
if payment.sourceType.isWhiteLabelInstallment {
navigate(to: createCreditCardPaymentController(
paymentType: .whiteLabelInstallment(payment: payment)
))
} else {
processPayment(payment, completion: completion)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ extension SelectInstallmentTermsViewModel: SelectPaymentPresentableProtocol {
func viewContext(at index: Int) -> TableCellContext? {
guard let value = values.at(index) else { return nil }
let numberOfTermsTitleFormat = localized("installments.number-of-terms.text", "%d months")
let accessoryIcon = sourceType.isWhiteLabelInstallment ? Assets.Icon.next : Assets.Icon.redirect
return TableCellContext(
title: String.localizedStringWithFormat(numberOfTermsTitleFormat, value),
icon: nil,
accessoryIcon: UIImage(Assets.Icon.redirect)
accessoryIcon: UIImage(accessoryIcon)
)
}

Expand All @@ -57,6 +58,10 @@ extension SelectInstallmentTermsViewModel: SelectPaymentPresentableProtocol {
}

func viewShouldAnimateSelectedCell(at index: Int) -> Bool {
true
if sourceType.isWhiteLabelInstallment {
return false
} else {
return true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ extension SourceType: ViewPresentable {
return "TMBThanachart Bank"
case .installmentUOB:
return "United Overseas Bank"
case .installmentWhiteLabelKTC:
return "Krungthai Card (KTC)"
case .internetBankingBAY:
return "Bank of Ayudhya"
case .internetBankingBBL:
Expand Down Expand Up @@ -149,7 +151,7 @@ extension SourceType: ViewPresentable {
return "First Choice"
case .installmentKBank:
return "KBANK"
case .installmentKTC:
case .installmentKTC, .installmentWhiteLabelKTC:
return "KTC"
case .installmentMBB:
return "FPX/maybank"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

protocol CreditCardPaymentDelegate: AnyObject {
func didSelectCardPayment(_ card: CreateTokenPayload.Card, completion: @escaping () -> Void)
func didSelectCardPayment(paymentType: CreditCardPaymentOption, card: CreateTokenPayload.Card, completion: @escaping () -> Void)
func didCancelCardPayment()
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import Foundation
import os.log

enum CreditCardPaymentOption {
case card
case whiteLabelInstallment(payment: Source.Payment)
}

class CreditCardPaymentViewModel: CreditCardPaymentViewModelProtocol, CountryListViewModelProtocol {

let paymentType: CreditCardPaymentOption

var addressFields: [AddressField] = [.address, .city, .state, .postalCode]

var fieldForShippingAddressHeader: AddressField?

var isAddressFieldsVisible: Bool {
Expand All @@ -18,9 +26,10 @@ class CreditCardPaymentViewModel: CreditCardPaymentViewModelProtocol, CountryLis

private weak var delegate: CreditCardPaymentDelegate?

init(currentCountry: Country?, delegate: CreditCardPaymentDelegate?) {
init(currentCountry: Country?, paymentType: CreditCardPaymentOption, delegate: CreditCardPaymentDelegate?) {
self.delegate = delegate
self.selectedCountry = currentCountry
self.paymentType = paymentType
}

// MARK: CountryListViewModelProtocol
Expand Down Expand Up @@ -62,6 +71,6 @@ class CreditCardPaymentViewModel: CreditCardPaymentViewModelProtocol, CountryLis
postalCode: viewContext[.postalCode]
)

delegate?.didSelectCardPayment(card, completion: completion)
delegate?.didSelectCardPayment(paymentType: paymentType, card: card, completion: completion)
}
}
Loading

0 comments on commit 5f6b083

Please sign in to comment.