Untitled

 avatar
unknown
plain_text
a month ago
34 kB
5
Indexable
//
//  QrisPayAmountViewModel.swift
//  IDBank
//
//  Created by Myoungkyu.Shin on 11/06/2019.
//  Copyright (c) 2019 LINE Bank Indonesia Corporation. All rights reserved.
//

import RxSwift
import RxCocoa
import RxSwiftExt
import BuffettRemote

enum QrisPayAmountValidState {
    case inputAmount
    case sendMoney
    case validate
    
    var title: String {
        switch self {
        case .inputAmount:
            return LocalizableString.commonOkButton
        case .sendMoney, .validate:
            return LocalizableString.qrpaymentPayButton
        }
    }
    
    var isConfirmState: Bool { [.sendMoney, .validate].contains(self) }
}

enum QrisPayAmountResult {
    enum ErrorType: Error {
        case notEnough
        case dataIsNil
        case serverError(error: ResultError)
        case serverErrorWithPay(error: ResultError)
    }
    
    enum Validation {
        case ok
        case ready
        case overAccountBalance(message: String)
        case overOnetimeLimit(message: String)
        case underMinimumLimit(message: String)
        case invalid(message: String)
    }
}

enum QrisPayPendingStatus: Int {
    case unknown
    case first = 1
    case second = 2
    case last = 3
    
    func next() -> QrisPayPendingStatus {
        switch self {
        case .unknown:
            return .first
        case .first:
            return .second
        case .second:
            return .last
        case .last:
            return .unknown
        }
    }
}

extension QrisPayAmountResult.Validation: Equatable {
    static func == (lhs: QrisPayAmountResult.Validation, rhs: QrisPayAmountResult.Validation) -> Bool {
        switch (lhs, rhs) {
        case (.ok, .ok):
            return true
        case (.ready, .ready):
            return true
        case (.overAccountBalance, .overAccountBalance):
            return true
        case (.overOnetimeLimit, .overOnetimeLimit):
            return true
        case (.underMinimumLimit, .underMinimumLimit):
            return true
        case (.invalid, .invalid):
            return true
        default:
            return false
        }
    }
}

extension QrisPayAmountResult.Validation {
    var valid: Bool { self == .ok }
    
    var isError: Bool { errorMessage?.isEmpty != nil }
    
    var errorMessage: String? {
        switch self {
        case let .overAccountBalance(message):
            return message
        case let .overOnetimeLimit(message):
            return message
        case let .underMinimumLimit(message):
            return message
        case let .invalid(message):
            return message
        default:
            return nil
        }
    }
}

protocol QrisPayAmountViewModelType {
    var inputs: QrisPayAmountViewModelInputs { get }
    var outputs: QrisPayAmountViewModelOutputs { get }
}

protocol QrisPayAmountViewModelInputs {
    func setup(params: QrisPayParams)
    func fetch()
    func refreshAccountData()
    func fetchDisbursalLimit(payload: QrisPayAPIModel.QRISPaymentDisbursalLimitRequest)
    func validation()
    func amountChanged(to amount: Decimal)
    // User input amount
    func inputAmountChanged(to amount: Decimal)
    func addAmount(to amount: Decimal)
    func changeAccount(with account: AccountAPIModel.AccountSimpleInfo)
    func createParams(payInfo: QrisPayAmountViewModel.PayInfo)
    func pay(payload: QrisPayAmountViewModel.PayloadRequest)
    func tipAmount(to amount: Decimal)
    func changeInputState()
    func changeSendMoneyState()
    func updateState()
    func qrisPendingResponse(_ pendingResponse: QrisPayAPIModel.PayResult, _ isDisbursal: Bool)
    func allUsedQuickCreditPayment(_ isAll: Bool)
    func showResult()
}

protocol QrisPayAmountViewModelOutputs {
    var payParam: Observable<QrisPayParams?> { get }
    var cellModelObservable: Observable<QrisPayAmountCellModel?> { get }
    var accountList: Observable<AccountAPIModel.Accounts> { get }
    var disbursalLimit: Observable<QrisPayAPIModel.QRISPaymentDisbursalLimitResponse> { get }
    var validationResult: Driver<QrisPayAmountResult.Validation> { get }
    var inputValidationResult: Driver<QrisPayAmountResult.Validation> { get }
    var amountResult: Driver<String?> { get }
    var tips: Observable<QrisPayParams.Tips> { get }
    var fixedAmount: Observable<Bool> { get }
    var tipAmount: Observable<Decimal> { get }
    var createdParam: Observable<QrisPayAmountViewModel.PayloadRequest> { get }
    var completeTransfer: Observable<QrisPayment.Transaction> { get }
    var state: Driver<QrisPayAmountValidState> { get }
    var isConfirmState: Bool { get }
    var qrisPending: Observable<(QrisPayAPIModel.PayResult, Bool)> { get }
    var qrisPendingStatus: Driver<QrisPayPendingStatus> { get }
    var isAllUsedQuickCredit: Driver<Bool> { get }

    var invalidRecentlyFavorite: Driver<()> { get }
    var isLoading: Driver<Bool> { get }
    var error: Driver<QrisPayAmountResult.ErrorType> { get }
}

private extension QrisPayAmountViewModel {
    struct QrisPay: Codable {
        // account Identification1
        let acctIdentification1: String?
        // acquiring institution Id
        let acquInstId: String
        // additional data
        let addtnlData: String
        // additional Data National
        let addtnlDataNatnl: String
        // approval code
        let apprCode: String?
        // card acceptorId
        let cardAcceptId: String
        // card acceptor name & location
        let cardAcceptNmLoc: String
        // card acceptor terminal identification
        let cardAcceptTermId: String?
        // currency
        let ccy: String // length: 3
        // amount convenience fee
        let convFee: String?
        // receiver account holder
        let crAcctNm1: String? // length: 30
        // receiver account holder2
        let crAcctNm2: String? // length: 30
        // receiver account no
        let crAcctNo: String // length: 30
        // receiver account type
        let crAcctType: String? // length: 3
        // receiver bank code
        let crBankCd: String? // length: 6
        // receiver card no
        let crCardNo: String? // length: 50
        // withdrawal account holder
        let dbAcctNm: String
        // withdrawal account no
        let dbAcctNo: String // length: 30
        // forwarding institution Id
        let fwdInstId: String
        // issuer Id
        let issuerId: String
        // merchantCity
        let merchantCity: String?
        // merchants name
        let merchantName: String
        // merchants type
        let merchantType: String // length: 4
        // reserved2
        let reserved2: String?
        // reserved3
        let reserved3: String?
        // point of service entry mode
        let svcEntryMode: String
        // tips
        let tipsAmount: String?
        // transaction amount
        let trxAmt: String
        // quick credit disbursal Y/N
        let quickCreditDisbursalYn: String
        // loan Account number
        let loanAcctNo: String?
        // using loan amount
        let loanAmt: String?
    }
}

final class QrisPayAmountViewModel: QrisPayAmountViewModelType {
    typealias PayloadRequest = [String: Any]
    typealias RecentlyFavoriteModel = TransferAPIModel.RecentBookMark.TransferTransaction
    typealias LoanInfo = (quickCreditDisbursalYn: String, loanAcctNo: String?, loanAmt: String?)
    // swiftlint:disable superfluous_disable_command large_tuple
    typealias PayInfo = (withdrawMerchantPan: String, withdrawMerchantName: String, amount: Decimal, tip: Decimal, loanInfo: LoanInfo)
    
    private enum Constant {
        static let hotKeyAllAmountTag: Decimal = 4444_987_654_321
    }
    
    private let disposeBag = DisposeBag()
    
    // output
    private let cellModelRelay: BehaviorRelay<QrisPayAmountCellModel?> = .init(value: nil)
    private let accountListSubject: PublishSubject<AccountAPIModel.Accounts?> = .init()
    private let disbursalLimitRelay: BehaviorRelay<QrisPayAPIModel.QRISPaymentDisbursalLimitResponse?> = .init(value: nil)
    private let validationResultSubject: BehaviorSubject<QrisPayAmountResult.Validation> = .init(value: .ready)
    private let amountSubject: BehaviorSubject<String?> = .init(value: nil)
    private let inputValidationResultSubject: BehaviorSubject<QrisPayAmountResult.Validation> = .init(value: .ready)
    private let isLoadingRelay: BehaviorRelay<Bool> = .init(value: false)
    private let tipsSubject: BehaviorSubject<QrisPayParams.Tips> = .init(value: (.none, nil))
    private let enableAmountSubject: BehaviorSubject<Bool> = .init(value: true)
    private let createdParamSubject: PublishSubject<PayloadRequest> = .init()
    private let completeTransferSubject: PublishSubject<QrisPayment.Transaction> = .init()
    private let stateSubject: BehaviorRelay<QrisPayAmountValidState> = .init(value: .inputAmount)
    private let pendingResponseSubject: BehaviorSubject<(QrisPayAPIModel.PayResult, Bool)?> = .init(value: nil)
    private let pendingStatusRelay: BehaviorRelay<QrisPayPendingStatus> = .init(value: .unknown)
    private let errorSubject: PublishSubject<QrisPayAmountResult.ErrorType?> = .init()
    
    init() {
        setupRemote()
    }
    
    /// inputs
    private let recentlyFavoriteModelProperty: BehaviorRelay<RecentlyFavoriteModel?> = .init(value: nil)
    private let payParamProperty: BehaviorRelay<QrisPayParams?> = .init(value: nil)
    
    private var transferOptionViewModel: TransferOptionViewModelType?
    private let fetchProperty: PublishSubject<()> = .init()
    private let refreshAccountDataProperty: PublishSubject<()> = .init()
    private let fetchDisbursalLimitProperty: PublishSubject<QrisPayAPIModel.QRISPaymentDisbursalLimitRequest> = .init()
    private let validationProperty: PublishSubject<()> = .init()
    private let amountChangedProperty: PublishSubject<Decimal> = .init()
    private let inputAmountChangedProperty: PublishSubject<Decimal> = .init()
    private let addAmountProperty: PublishSubject<Decimal> = .init()
    private let tipAmountProperty: BehaviorSubject<Decimal> = .init(value: 0)
    private let currentAccountProperty: PublishSubject<AccountAPIModel.AccountSimpleInfo> = .init()
    private let createParamProperty: PublishSubject<PayInfo> = .init()
    private let payProperty: PublishSubject<PayloadRequest> = .init()
    private let showResultProperty: PublishSubject<Void> = .init()
    private let allUsedQuickCreditRelay: BehaviorRelay<Bool> = .init(value: false)
    
    var inputs: QrisPayAmountViewModelInputs { self }
    var outputs: QrisPayAmountViewModelOutputs { self }
}

extension QrisPayAmountViewModel: QrisPayAmountViewModelOutputs {
    var payParam: Observable<QrisPayParams?> {
        payParamProperty.asObservable()
    }
    
    var cellModelObservable: Observable<QrisPayAmountCellModel?> {
        cellModelRelay.asObservable()
    }

    var accountList: Observable<AccountAPIModel.Accounts> {
        accountListSubject.asObservable().filterNil()
    }

    var disbursalLimit: Observable<QrisPayAPIModel.QRISPaymentDisbursalLimitResponse> {
        disbursalLimitRelay.asObservable().filterNil()
    }

    var validationResult: Driver<QrisPayAmountResult.Validation> {
        validationResultSubject.asDriver(onErrorJustReturn: .ready)
    }
    
    var inputValidationResult: Driver<QrisPayAmountResult.Validation> {
        inputValidationResultSubject.asDriver(onErrorJustReturn: .ready)
    }

    var amountResult: Driver<String?> {
        amountSubject.asDriver(onErrorJustReturn: nil)
    }
    
    var tips: Observable<QrisPayParams.Tips> {
        tipsSubject.asObservable()
    }
    
    var fixedAmount: Observable<Bool> {
        enableAmountSubject.asObservable()
    }
    
    var tipAmount: Observable<Decimal> {
        tipAmountProperty.asObservable()
    }

    var createdParam: Observable<QrisPayAmountViewModel.PayloadRequest> {
        createdParamSubject.asObservable()
    }
    
    var completeTransfer: Observable<QrisPayment.Transaction> {
        completeTransferSubject.asObservable()
    }
    
    var state: Driver<QrisPayAmountValidState> {
        stateSubject.asDriver(onErrorJustReturn: .inputAmount)
    }
    
    var isConfirmState: Bool {
        stateSubject.value.isConfirmState
    }
    
    var qrisPending: Observable<(QrisPayAPIModel.PayResult, Bool)> {
        pendingResponseSubject.asObservable().filterNil()
    }
    
    var qrisPendingStatus: Driver<QrisPayPendingStatus> {
        pendingStatusRelay.asDriver(onErrorJustReturn: .unknown)
    }
    
    var isAllUsedQuickCredit: Driver<Bool> {
        allUsedQuickCreditRelay.asDriver(onErrorJustReturn: false)
    }

    var invalidRecentlyFavorite: Driver<()> {
        error.filter { $0.serverError == .invalidRecentBookmarkAccountNo }.mapTo(())
    }
    
    var isLoading: Driver<Bool> {
        isLoadingRelay.asDriver()
    }

    var error: Driver<QrisPayAmountResult.ErrorType> {
        errorSubject.asDriver(onErrorJustReturn: nil).filterNil()
    }
}

extension QrisPayAmountViewModel: QrisPayAmountViewModelInputs {
    func setup(params: QrisPayParams) {
//        recentlyFavoriteModelProperty.accept(recentlyFavorite)
        
        tipsSubject.onNext(params.tips)
        payParamProperty.accept(params)
    }

    func fetch() {
        fetchProperty.onNext(())
    }

    func refreshAccountData() {
        refreshAccountDataProperty.onNext(())
    }

    func fetchDisbursalLimit(payload: QrisPayAPIModel.QRISPaymentDisbursalLimitRequest) {
        fetchDisbursalLimitProperty.onNext(payload)
    }

    func validation() {
        validationResultSubject.onNext(.ready)
        validationProperty.onNext(())
    }

    func amountChanged(to amount: Decimal) {
        amountChangedProperty.onNext(amount)
    }
    
    func inputAmountChanged(to amount: Decimal) {
        inputAmountChangedProperty.onNext(amount)
    }

    func addAmount(to amount: Decimal) {
        addAmountProperty.onNext(amount)
    }

    func changeAccount(with account: AccountAPIModel.AccountSimpleInfo) {
        currentAccountProperty.onNext(account)
    }

    func createParams(payInfo: PayInfo) {
        createParamProperty.onNext(payInfo)
    }
    
    func pay(payload: QrisPayAmountViewModel.PayloadRequest) {
        payProperty.onNext(payload)
    }
    
    func tipAmount(to amount: Decimal) {
        tipAmountProperty.onNext(amount)
    }
    
    func changeInputState() {
        stateSubject.accept(.inputAmount)
    }
    
    func changeSendMoneyState() {
        stateSubject.accept(.sendMoney)
    }
    
    func updateState() {
        stateSubject.accept(isConfirmState ? .validate : .sendMoney)
    }
    
    func qrisPendingResponse(_ pendingResponse: QrisPayAPIModel.PayResult, _ isDisbursal: Bool) {
        pendingResponseSubject.onNext((pendingResponse, isDisbursal))
        
        pendingStatusRelay.accept(pendingStatusRelay.value.next())
        
        changeSendMoneyState()
    }
    
    func allUsedQuickCreditPayment(_ isAll: Bool) {
        allUsedQuickCreditRelay.accept(isAll)
    }
    
    func showResult() {
        showResultProperty.onNext(())
    }
    
//    func fetchDisbursalLimitQuickCredit(){
//        isLoadingRelay.accept(true)
//        
//        disposable += allUsedQuickCreditRelay
//            .flatMapLatest {
//                Remote<QuickCreditAPIModel.Main>.QuickCredit
//                    .main(loanAccountNo: $0)
//                    .useErrorLog()
//                    .asObservable()
//            }
//            .subscribe(onNext: { [weak self] in
//                guard let self = self else { return }
//                
//                self.isLoadingRelay.accept(false)
//                
//                switch $0 {
//                case let .success(value):
//                    self.infoSubject.onNext(QuickCreditHeaderPresentModel(value))
//                    self.hasAnyPrevBillsSubject.onNext(value.monthlyBill != nil)
//                    
//                case let .failure(error):
//                    self.errorSubject.onNext(error)
//                }
//            })
//        
//    }
}

// MARK: Factory
extension QrisPayAmountViewModel {
//    func createOptionViewModel(transferType: QrisPaymentAPIModel.PayType) -> QrisPayOptionViewModelType? {
//        switch transferType {
//        case .lineToLine:
//            return TransferLineBankViewModel()
//        case .lineToNonBank:
//            return TransferLineNonBankViewModel()
//        case .phoneToLineBank:
//            return TransferPhoneBankViewModel()
//        case .phoneToNonBank:
//            return TransferPhoneNonBankViewModel()
//        case .lineBankAccount, .otherBankAccount:
//            return TransferBankAccountViewModel()
//        case .myAccount:
//            return TransferMyAccountViewModel()
//        case .unknown:
//            return nil
//        }
//    }
}

// MARK: - Remote
private extension QrisPayAmountViewModel {
    func setupRemote() {
        fetchProperty
            .do(onNext: { [weak self] _ in
                self?.isLoadingRelay.accept(true)
            })
            .withLatestFrom(payParamProperty)
            .filterNil()
            .flatMapLatest { [weak self] param -> Observable<(QrisPayParams, QrisPayAPIModel.QRISPaymentDisbursalLimitResponse)> in
                guard let self = self else { return .empty() }
                let initData = QrisPayAPIModel.QRISPaymentDisbursalLimitRequest(loanAcctNo: 0, quickCreditDisbursalYn: "",amount: 0)
                self.fetchDisbursalLimit(payload: initData)

                return .create {
                    self.disbursalLimitRelay
                        .filterNil()
                        .map { (param, $0) }
                        .bind(to: $0)
                }
            }
            .flatMapLatest { [weak self] param, limit -> Observable<(QrisPayParams, QrisPayAPIModel.QRISPaymentDisbursalLimitResponse, AccountAPIModel.Accounts)> in
                guard let self = self else { return .empty() }

                return .create { observer -> Disposable in
                    Remote<AccountAPIModel.Accounts>.Account
                        .list()
                        .useErrorLog()
                        .asObservable()
                        .subscribe(onNext: { [weak self] in
                            guard let self = self else { return }

                            switch $0 {
                            case let .success(value):
                                self.accountListSubject.onNext(value)
                                observer.onNext((param, limit, value))
                            case let .failure(error):
                                self.errorSubject.onNext(.serverError(error: error))
                                observer.onCompleted()
                            }
                        })
                }
            }
            .bind(onNext: { [weak self] _, _, accountList in
                guard let self = self else { return }

                guard let currentAccount = accountList.list.filter({ $0.dpAcctYn }).first else {
                    self.errorSubject.onNext(.dataIsNil)
                    return
                }

                self.currentAccountProperty.onNext(currentAccount)
            }).disposed(by: disposeBag)

        refreshAccountDataProperty
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(true) })
            .flatMapLatest { _ -> Observable<Result<AccountAPIModel.Accounts>> in
                Remote<AccountAPIModel.Accounts>.Account
                    .list()
                    .useErrorLog()
                    .asObservable()
            }
            .compactMap { [weak self] result -> AccountAPIModel.Accounts? in
                guard let self = self else { return nil }

                switch result {
                case let .success(value):
                    return value
                case let .failure(error):
                    self.errorSubject.onNext(.serverError(error: error))
                    return nil
                }
            }
            .withLatestFrom(currentAccountProperty) { ($0, $1) }
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(false) })
            .bind(onNext: { [weak self] accountList, currentAccount in
                guard let self = self else { return }

                defer {
                    self.accountListSubject.onNext(accountList)
                }
                
                guard let currentAccount = accountList.list.first(where: { $0.acctNo == currentAccount.acctNo }) else {
                    self.errorSubject.onNext(.dataIsNil)
                    return
                }

                self.currentAccountProperty.onNext(currentAccount)
            }).disposed(by: disposeBag)

        fetchDisbursalLimitProperty
            .flatMapLatest { data -> Observable<Result<QrisPayAPIModel.QRISPaymentDisbursalLimitResponse>> in
                let payloadDisbursalLimit = QrisPayAPIModel.QRISPaymentDisbursalLimitRequest(loanAcctNo: data.loanAcctNo, quickCreditDisbursalYn: data.quickCreditDisbursalYn, amount: data.amount)
                
                return Remote<QrisPayAPIModel.QRISPaymentDisbursalLimitResponse>.QrisPayment
                    .disbursalLimit(payloadDisbursalLimit)
                    .useErrorLog()
                    .asObservable()
            }
            .bind(onNext: { [weak self] in
                guard let self = self else { return }

                switch $0 {
                case let .success(value):
                    self.disbursalLimitRelay.accept(value)
                case let .failure(error):
                    self.errorSubject.onNext(.serverError(error: error))
                }
            }).disposed(by: disposeBag)

        validationProperty
            .withLatestFrom(cellModelRelay) { $1 }
            .filterNil()
            .map { self.validation(to: $0.amount) }
            .bind(onNext: {
                self.validationResultSubject.onNext($0)
            }).disposed(by: disposeBag)

        amountChangedProperty
            .withLatestFrom(cellModelRelay) { ($0, $1) }
            .map { amount, cellModel -> (QrisPayAmountResult.Validation, Decimal) in
                let resultAmount = amount.compareLength(13) == .greaterThan ?
                    (cellModel?.amount ?? 0) : amount
                
                let validationResult = self.validation(to: resultAmount)
                cellModel?.amount = resultAmount
                
                return (validationResult, resultAmount)
            }.bind(onNext: {
                self.validationResultSubject.onNext($0.0)
                self.amountSubject.onNext($0.1 == 0 ? nil : $0.1.currencyFormatted(showSymbol: false))
            }).disposed(by: disposeBag)
        
        inputAmountChangedProperty
            .withLatestFrom(cellModelRelay) { ($0, $1) }
            .compactMap { [weak self] amount, cellModel -> (QrisPayAmountResult.Validation, Decimal)? in
                guard let self = self else { return nil }
                let resultAmount = amount.compareLength(13) == .greaterThan ?
                    (cellModel?.amount ?? 0) : amount

                let validationResult = self.validation(to: resultAmount)
                cellModel?.amount = resultAmount

                return (validationResult, resultAmount)
            }.bind(onNext: { [weak self] in
                guard let self = self else { return }
                self.inputValidationResultSubject.onNext($0.0)

                switch $0.0 {
                case .underMinimumLimit:
                    break
                default:
                    self.validationResultSubject.onNext($0.0)
                }
            }).disposed(by: disposeBag)
        
        tipAmountProperty
            .withLatestFrom(tipsSubject)
            .filter { $0.0 == .input }
            .withLatestFrom(cellModelRelay)
            .compactMap { [weak self] cellModel -> QrisPayAmountResult.Validation? in
                guard let self = self else { return nil }
                let validationResult = self.validation(to: cellModel?.amount ?? 0)
                return validationResult
            }.bind(onNext: { [weak self] in
                guard let self = self else { return }
                switch $0 {
                case .underMinimumLimit:
                    break
                default:
                    self.validationResultSubject.onNext($0)
                }
            }).disposed(by: disposeBag)
        
        addAmountProperty
            .withLatestFrom(cellModelRelay.filterNil()) { ($0, $1) }
            .bind(onNext: { value, cellModel in
                let result: Decimal = with(cellModel.amount + value) {
                    guard value != Constant.hotKeyAllAmountTag else {
                        return Decimal(cellModel.withdrawAccountBalance.intValue)
                    }

                    return $0
                }

                self.amountChangedProperty.onNext(result)
            }).disposed(by: disposeBag)
        
        currentAccountProperty
            .asObservable()
            .distinctUntilChanged()
            .withLatestFrom(payParamProperty) { ($0, $1) }
            .withLatestFrom(disbursalLimitRelay) { ($0, $1) }
            .bind(onNext: { [weak self] info, limit in
                guard let self = self, let param = info.1, let limit = limit else { return }

                self.makeCellModel(limit, param, account: .init(model: info.0))
                
                if let amount = param.transactionAmount {
                    self.amountChangedProperty.onNext(amount.decimalValue)
                    self.enableAmountSubject.onNext(false)
                }
            }).disposed(by: disposeBag)
        
        createParamProperty
            .withLatestFrom(payParam) { ($0, $1) }
            .withLatestFrom(currentAccountProperty) { ($0.0, $0.1, $1) }
            .map { [weak self] in
                self?.makeRequestParam($0.0, $0.1, $0.2)?.asDictionary
            }
            .filterNil()
            .bind(onNext: createdParamSubject.onNext)
            .disposed(by: disposeBag)
        
        payProperty
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(true) })
            .flatMapLatest { payload in
                Remote<QrisPayAPIModel.PayResult>.QrisPayment
                    .pay(payloadRequest: payload)
                    .useErrorLog()
                    .asObservable()
            }
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(false) })
            .bind(onNext: { [weak self] result in
                guard let self = self else { return }
                switch result {
                case let .success(response):
                    self.completeTransferSubject.onNext(self.makeTransaction(receiver: response))
                case let .failure(error):
                    self.errorSubject.onNext(.serverErrorWithPay(error: error))
                }
            }).disposed(by: disposeBag)
        
        showResultProperty
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(true) })
            .withLatestFrom(qrisPending)
            .flatMapLatest { payload in
                Remote<QrisPayAPIModel.PayResult>.QrisPayment
                    .showResult(payload.0)
                    .useErrorLog()
                    .asObservable()
            }
            .do(onNext: { [weak self] _ in self?.isLoadingRelay.accept(false) })
            .bind(onNext: { [weak self] result in
                guard let self = self else { return }
                switch result {
                case let .success(response):
                    self.completeTransferSubject.onNext(self.makeTransaction(receiver: response))
                case let .failure(error):
                    self.errorSubject.onNext(.serverError(error: error))
                }
            }).disposed(by: disposeBag)
    }
}

// MARK: - Private
private extension QrisPayAmountViewModel {
    func validation(to value: Decimal) -> QrisPayAmountResult.Validation {
        guard let formModel = cellModelRelay.value else {
            return .ready
        }
        
        if value < 0 {
            return .invalid(message: "You should be valid amount.")
        }
        
        if let tips = try? tipsSubject.value() {
            switch tips.0 {
            case .percentage:
                if let percentage = tips.1?.decimalValue, percentage > 0 {
                    tipAmountProperty.onNext(Decimal((value * percentage / 100).intValue))
                }
            case .amount:
                tipAmountProperty.onNext(tips.1?.decimalValue ?? 0)
            default:
                break
            }
        }

        if value == 0 {
            return .ready
        }
        
        let tipAmount: Decimal = (try? tipAmountProperty.value()) ?? 0
        
//        if !formModel.canWithdrawAmount(to: value + tipAmount) {
//            return .overAccountBalance(message: LocalizableString.amountExceedamountErrorDesc)
//        }
//
//        if formModel.isUnderOneTimeAmountLimit(amount: value) {
//            // formModel.representMinimumOneTimeAmount
//            return .underMinimumLimit(message: LocalizableString.amountMinimumamountErrorDesc(formModel.minTransferOneTimeLimit.intValue))
//        }
//
//        if formModel.isOverOneTimeAmountLimit(amount: value) {
//            // formModel.representMaximumOneTimeAmount
//            return .overOnetimeLimit(message: LocalizableString.amountOvertxnlimitErrorDesc(formModel.maxTransferOneTimeLimit.intValue))
//        }
        
        return .ok
    }

    func makeCellModel(_ limit: QrisPayAPIModel.QRISPaymentDisbursalLimitResponse,
                       _ param: QrisPayParams,
                       account: AccountPresentModel) {
        let cellModel = QrisPayAmountCellModel(limit, param, account: account)
        if let cell = cellModelRelay.value {
            cellModel.amount = cell.amount
            cellModel.memo = cell.memo
        }
        cellModelRelay.accept(cellModel)
    }
    
    func makeRequestParam(_ payInfo: PayInfo, _ payParam: QrisPayParams?, _ currentAccount: AccountAPIModel.AccountSimpleInfo) -> QrisPay? {
        
        guard let payParam = payParam else { return nil }
        
        let amount = "\((payInfo.amount))"//.leftPadding(toLength: 14, withPad: "0")
        let tip = payInfo.tip
        let dbAcctNm = BankUserService.shared.currentUser?.userName ?? ""
        
        return .init(acctIdentification1: nil,
                     acquInstId: payParam.acquInstId,
                     addtnlData: payParam.addtnlData,
                     addtnlDataNatnl: payParam.addtnlDataNatnl,
                     apprCode: nil,
                     cardAcceptId: payParam.cardAcceptId,
                     cardAcceptNmLoc: payParam.cardAcceptNmLoc,
                     cardAcceptTermId: payParam.cardAcceptTermId,
                     ccy: "360",
                     convFee: makeConvFee(tip, payParam),
                     crAcctNm1: nil,
                     crAcctNm2: payParam.crAcctNm2,
                     crAcctNo: payParam.merchantPan,
                     crAcctType: nil,
                     crBankCd: nil,
                     crCardNo: nil,
                     dbAcctNm: dbAcctNm, //currentAccount.acctTitle,
                     dbAcctNo: currentAccount.acctNo,
                     fwdInstId: "360002",
                     issuerId: "93600484   ",
                     merchantCity: payParam.merchantCity,
                     merchantName: payParam.merchantName ?? "",
                     merchantType: payParam.merchantType,
                     reserved2: nil,
                     reserved3: nil,
                     svcEntryMode: "012",
                     tipsAmount: "\(tip)",
                     trxAmt: amount,
                     quickCreditDisbursalYn: payInfo.loanInfo.quickCreditDisbursalYn,
                     loanAcctNo: payInfo.loanInfo.loanAcctNo,
                     loanAmt: payInfo.loanInfo.loanAmt
        )
    }
    
    func makeTransaction(receiver: QrisPayAPIModel.PayResult) -> QrisPayment.Transaction {
        .init(refNo: receiver.refNo ?? "",
              hisNo: receiver.hisNo,
              dbAcctNm: [receiver.dbAcctNm1, receiver.dbAcctNm2].compactMap { $0 }.joined(),
              dbAcctNo: receiver.dbAcctNo ?? "",
              crAcctNo: receiver.crAcctNo ?? "",
              trxAmt: receiver.trxAmt ?? "",
              tipAmount: receiver.tipsAmount ?? "",
              crAcctNm1: receiver.crAcctNm1,
              crAcctNm2: receiver.crAcctNm2,
              merchantName: receiver.merchantName ?? "",
              convFee: receiver.convFee ?? "",
              date: receiver.localTrxTimeStamp.date ?? Date(),
              disbursalAmount: receiver.loanAmt,
              totalUsedCredit: receiver.loanBalance,
              availableCredit: receiver.creditLimit,
              disburseFee: receiver.disburseFee
        )
    }
    
    private func makeConvFee(_ tip: Decimal, _ payParam: QrisPayParams?) -> String? {
        guard let payParam = payParam else { return "" }
        
        switch payParam.tipIndicator {
        case .input:
            let tipAmount: Decimal = (try? tipAmountProperty.value()) ?? 0
            return "C" + "\(tipAmount)00".leftPadding(toLength: 8, withPad: "0")
        case .amount:
            guard let tipValue = payParam.tipValueOfFixed else { return "" }
            return "D" + "\(tipValue)00".leftPadding(toLength: 8, withPad: "0")
        case .percentage:
            return "D" + "\(tip)00".leftPadding(toLength: 8, withPad: "0")
        default:
            return nil
        }
    }
    
    private func makeAddtnlData(_ dbAcctNm: String, _ mc: String) -> String {
        let count: Int
        let title: String
        if dbAcctNm.count > 30 {
            count = 30
            title = String(dbAcctNm[dbAcctNm.startIndex..<dbAcctNm.index(dbAcctNm.startIndex, offsetBy: 30)])
        } else {
            count = dbAcctNm.count
            title = dbAcctNm
        }
        
        return "PI04Q001CD\(String(format: "%02d", count))\(title)MC\(String(format: "%02d", mc.count))\(mc)"
    }
}
Editor is loading...
Leave a Comment