diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit App/Base.lproj/Interface.storyboard b/SynchronyFinancial/SynchronyFinancial WatchKit App/Base.lproj/Interface.storyboard index 037b541..c2bdbb3 100644 --- a/SynchronyFinancial/SynchronyFinancial WatchKit App/Base.lproj/Interface.storyboard +++ b/SynchronyFinancial/SynchronyFinancial WatchKit App/Base.lproj/Interface.storyboard @@ -201,24 +201,33 @@ - + + + @@ -226,6 +235,26 @@ + + + + + + + + + + + + + + diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/FetchData.swift b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/FetchData.swift index 79efb3e..5426ea7 100644 --- a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/FetchData.swift +++ b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/FetchData.swift @@ -20,7 +20,7 @@ class FetchData { completion(false, NSError()) return } - + if let token = dict["access_token"]?.string { // now we should save our token somewhere safe (UserDefaults) UserDefaults.standard.set(token, forKey: "access_token") @@ -43,9 +43,6 @@ class FetchData { guard dict["status"]?.dictionaryValue["response_code"]?.string == "0" else { return } //print(json) - let formatter = DateFormatter() - formatter.locale = Locale.current - formatter.dateFormat = "yyyyMMdd" if let accounts = dict["account_number_list"]?.arrayValue { accounts.forEach { if let accountAlias = $0.dictionaryValue["account_alias"]?.string, @@ -53,7 +50,7 @@ class FetchData { let creditLimitString = $0.dictionaryValue["credit_limit"]?.string, let creditLimit = Double(creditLimitString), let payDueDateString = $0.dictionaryValue["next_payment_due_date"]?.string, - let paymentDueDate = formatter.date(from: payDueDateString), + let paymentDueDate = Defaults.careCreditDateFormatter.date(from: payDueDateString), let curBalString = $0.dictionaryValue["current_balance"]?.string, let currentBalance = Double(curBalString), let availCreditString = $0.dictionaryValue["available_credit"]?.string, @@ -81,20 +78,17 @@ class FetchData { var header = Defaults.headerForTransaction header["account_alias"] = accountAlias var transactions: [Transaction] = [] - let formatter = DateFormatter() - formatter.locale = Locale.current - formatter.dateFormat = "yyyyMMdd" Alamofire.request(Defaults.TRANS_HISTORY_URL, method: .post, parameters: header, encoding: JSONEncoding.default, headers: Defaults.authHeader).responseJSON { payload in switch payload.result { case .success(let value): let dict = JSON(value).dictionaryValue guard dict["status"]?.dictionaryValue["response_code"]?.string == "0" else { return } - + //let's first parse the pending transactions dict["pending_transaction_list"]?.arrayValue.forEach { if let desc = $0["description"].string, - let date = formatter.date(from: $0["transaction_date"].stringValue), + let date = Defaults.careCreditDateFormatter.date(from: $0["transaction_date"].stringValue), let amountString = $0["transaction_amount"].string, let amount = Double(amountString), let confirmationNum = $0["payment_confirmation_number"].string, @@ -103,15 +97,15 @@ class FetchData { let modifiable = $0["is_payment_modifiable"].string { let type: TransactionType = $0["payment_amount_type"].stringValue == "" ? .purchase : .reimbursement let isModifiable: Bool = modifiable == "Y" ? true : false - + transactions.append(Transaction(type: type, amount: amount, merchantID: desc, date: date, confirmationNum: confirmationNum, paymentId: paymentId, isPending: true, isModifiable: isModifiable)) } } - + // let's parse just processed transactions now dict["processed_transaction_list"]?.arrayValue.forEach { if let desc = $0["description"].string, - let date = formatter.date(from: $0["transaction_date"].stringValue), + let date = Defaults.careCreditDateFormatter.date(from: $0["transaction_date"].stringValue), let amountString = $0["transaction_amount"].string, let amount = Double(amountString) { let type: TransactionType = $0["payment_amount_type"].stringValue == "" ? .purchase : .reimbursement @@ -125,7 +119,7 @@ class FetchData { } } } - + static func getBankInfo(completion: @escaping ([BankAcct], Error?) -> Void) { var bankIds: [BankAcct] = [] Alamofire.request(Defaults.FETCH_BANKS_URL, method: .post, parameters: Defaults.headerForMulti, encoding: JSONEncoding.default, headers: Defaults.authHeader).responseJSON { payload in @@ -151,14 +145,13 @@ class FetchData { } } } - - static func cancelPayment(accountAlias: String, confirmationNum: String, paymentId: Int, completion: @escaping (String, Error?) -> Void){ - + + static func cancelPayment(accountAlias: String, confirmationNum: String, paymentId: Int, completion: @escaping (String, Error?) -> Void) { var paymentHeader = Defaults.headerForCancelPmt paymentHeader["account_alias"] = accountAlias paymentHeader["payment_confirmation_number"] = confirmationNum paymentHeader["payment_id"] = paymentId - + Alamofire.request(Defaults.CANCEL_PAYMENT_URL, method: .post, parameters: paymentHeader, encoding: JSONEncoding.default, headers: Defaults.authHeader).responseJSON { payload in switch payload.result { case .success(let value): @@ -172,4 +165,27 @@ class FetchData { } } } + + static func submitPayment(for alias: String, type: PaymentType, amount: Double, bankID: String, completion: @escaping (String, String, Error?) -> Void) { + var paymentHeader = Defaults.headerForPmt + paymentHeader["account_alias"] = alias + paymentHeader["bank_account_id"] = bankID + paymentHeader["payment_amount_type"] = type.rawValue + paymentHeader["payment_amount"] = amount + paymentHeader["scheduled_payment_post_date"] = Defaults.careCreditDateFormatter.string(from: Date()) + + Alamofire.request(Defaults.MAKE_PAYMENT_URL, method: .post, parameters: paymentHeader, encoding: JSONEncoding.default, headers: Defaults.authHeader).responseJSON { payload in + switch payload.result { + case .success(let value): + let dict = JSON(value).dictionaryValue + guard dict["status"]?.dictionaryValue["response_code"]?.string == "0" else { return } + if let paymentConfirmationNum = dict["payment_confirmation_number"]?.stringValue, + let paymentID = dict["payment_id"]?.stringValue { + completion(paymentConfirmationNum, paymentID, nil) + } + case .failure(let error): + NSLog("Error: \(error.localizedDescription)") + } + } + } } diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PayBillInterfaceController.swift b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PayBillInterfaceController.swift index 3593120..5facc30 100644 --- a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PayBillInterfaceController.swift +++ b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PayBillInterfaceController.swift @@ -60,6 +60,9 @@ class PayBillInterfaceController: WKInterfaceController { let minimumFormatted = String(format: "$%.2f", valid.minPayDue) minimumLabel.setText("Minimum Payment:\n\(minimumFormatted)") payMinimumButton.setTitle("Pay \(minimumFormatted)") + + payMinimumButton.setEnabled(valid.minPayDue > 0.0) + payBalanceButton.setEnabled(valid.curBalance > 0.0) } } } diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentDetailInterfaceController.swift b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentDetailInterfaceController.swift index 30ed7b8..99ab3b1 100644 --- a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentDetailInterfaceController.swift +++ b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentDetailInterfaceController.swift @@ -12,18 +12,25 @@ import Foundation class PaymentDetailInterfaceController: WKInterfaceController { var selectedAccount: Account? var dictForAcct: [String: Account] = [:] + var dictForPayment: [String: String] = [:] var paymentButtonArmed: Bool = false var paymentAmount: Double = 0.0 + @IBOutlet weak var contentGroup: WKInterfaceGroup! + @IBOutlet weak var activityIndicator: WKInterfaceImage! + @IBOutlet weak var activityIndicatorLabel: WKInterfaceLabel! @IBOutlet weak var detailButton: WKInterfaceButton! @IBOutlet weak var amount: WKInterfaceLabel! @IBOutlet weak var paymentButton: WKInterfaceButton! override func awake(withContext context: Any?) { super.awake(withContext: context) - guard let data = context as? [String: Any], let acct = data["acct"] as? Account, let amount = data["payment_amount"] as? Double else { - NSLog("Error getting account object and payment amount") - return + + guard let data = context as? [String: Any], + let acct = data["acct"] as? Account, + let amount = data["payment_amount"] as? Double else { + NSLog("Error getting account object and payment amount") + return } self.paymentAmount = amount @@ -33,15 +40,38 @@ class PaymentDetailInterfaceController: WKInterfaceController { @IBAction func paymentAction() { if paymentButtonArmed { - popToRootController() - } else { -// animate(withDuration: 0.75) { -// self.paymentButton.setBackgroundColor(UIColor.init(red: 141, green: 241, blue: 48, alpha: 1.0)) -// } + guard let alias = selectedAccount?.accountAlias else { return } + let cancel = WKAlertAction(title: "Cancel", style: .cancel, handler: {}) + let submit = WKAlertAction(title: "Pay Now", style: .default, handler: { + self.contentGroup.setHidden(true) + self.activityIndicator.configureForActivityIndicator() + self.activityIndicatorLabel.setHidden(false) + self.activityIndicatorLabel.setVerticalAlignment(.center) + let type: PaymentType = self.paymentAmount == self.selectedAccount?.curBalance ? .currentBal : .minimumDue - paymentButton.setBackgroundColor(#colorLiteral(red: 0.6092301607, green: 0.9366738796, blue: 0.2432599962, alpha: 1)) - paymentButtonArmed = true - paymentButton.setTitle("Pay Now") + // process this payment using default bank account (9999) until ability to change account is implemented + FetchData.submitPayment(for: alias, type: type, amount: self.paymentAmount, bankID: "9999") { confirmationNum, paymentID, error in + guard error == nil else { + let dismiss = WKAlertAction(title: "Dismiss", style: .cancel, handler: {}) + self.presentAlert(withTitle: "Error", message: "We were unable to process your payment at this time. Please try again later.", preferredStyle: .alert, actions: [dismiss]) + return + } + self.dictForPayment.updateValue(confirmationNum, forKey: "payment_confirmation_number") + self.dictForPayment.updateValue(paymentID, forKey: "payment_id") + self.activityIndicator.stopAnimatingAsIndicator() + self.activityIndicatorLabel.setHidden(true) + self.contentGroup.setHidden(false) + self.presentController(withName: "PaymentResult", context: self.dictForPayment) + self.popToRootController() + } + }) + presentAlert(withTitle: "Disclaimer", message: "By tapping \"Pay Now\", you are authorizing Synchrony Bank to process a one time payment in the amount of \(String(format: "$%.2f", paymentAmount)).", preferredStyle: .actionSheet, actions: [cancel, submit]) + } else { + animate(withDuration: 0.5) { + self.paymentButton.setBackgroundColor(#colorLiteral(red: 0.6092301607, green: 0.9366738796, blue: 0.2432599962, alpha: 1)) + self.paymentButtonArmed = true + self.paymentButton.setTitle("Pay Now") + } } } diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentResultInterfaceController.swift b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentResultInterfaceController.swift new file mode 100644 index 0000000..3208d1c --- /dev/null +++ b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/PaymentResultInterfaceController.swift @@ -0,0 +1,38 @@ +// +// PaymentResultInterfaceController.swift +// SynchronyFinancial WatchKit Extension +// +// Created by Alan Maynard on 3/29/19. +// Copyright © 2019 Alan Maynard. All rights reserved. +// + +import WatchKit +import Foundation + +class PaymentResultInterfaceController: WKInterfaceController { + @IBOutlet weak var confirmationNumberLabel: WKInterfaceLabel! + @IBOutlet weak var paymentIDLabel: WKInterfaceLabel! + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + + if let data = context as? [String: String], + let paymentConfirmation = data["payment_confirmation_number"], + let paymentID = data["payment_id"] { + confirmationNumberLabel.setText(paymentConfirmation) + paymentIDLabel.setText(paymentID) + setTitle("Done") + } + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + } + +} diff --git a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/TransactionCell.swift b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/TransactionCell.swift index e9d39bd..8825369 100644 --- a/SynchronyFinancial/SynchronyFinancial WatchKit Extension/TransactionCell.swift +++ b/SynchronyFinancial/SynchronyFinancial WatchKit Extension/TransactionCell.swift @@ -10,7 +10,6 @@ import Foundation import WatchKit class TransactionCell: NSObject { - @IBOutlet weak var transactionLabel: WKInterfaceLabel! @IBOutlet weak var valueLabel: WKInterfaceLabel! } diff --git a/SynchronyFinancial/SynchronyFinancial.xcodeproj/project.pbxproj b/SynchronyFinancial/SynchronyFinancial.xcodeproj/project.pbxproj index 5f786f7..ceb6fe6 100644 --- a/SynchronyFinancial/SynchronyFinancial.xcodeproj/project.pbxproj +++ b/SynchronyFinancial/SynchronyFinancial.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 11E6ADB92253FA050009922E /* BankAcct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E6ADB82253FA050009922E /* BankAcct.swift */; }; 11E6ADBA225401DB0009922E /* BankAcct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E6ADB82253FA050009922E /* BankAcct.swift */; }; 281283568A34D3C5D9C7B383 /* libPods-SynchronyFinancial WatchKit Extension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CAA6D46F907ADAABF49FD409 /* libPods-SynchronyFinancial WatchKit Extension.a */; }; - 481864A8224802BB0059CF7A /* PaymentDetailInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 481864A7224802BB0059CF7A /* PaymentDetailInterfaceController.swift */; }; 481864A9224802BB0059CF7A /* PaymentDetailInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 481864A7224802BB0059CF7A /* PaymentDetailInterfaceController.swift */; }; 48DA0058221D12E70081A500 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DA0057221D12E70081A500 /* AccountCell.swift */; }; 48F243072214C98600B9C894 /* AccountTableInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F243062214C98600B9C894 /* AccountTableInterfaceController.swift */; }; @@ -29,6 +28,7 @@ 678C38842230950100FEAAF6 /* AccountDetailsInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678C38832230950100FEAAF6 /* AccountDetailsInterfaceController.swift */; }; 678C3885223098C400FEAAF6 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F2430B2214CBF700B9C894 /* Account.swift */; }; 678C388622309F7D00FEAAF6 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DA0057221D12E70081A500 /* AccountCell.swift */; }; + 678C62BA224ECDFD0007AD53 /* PaymentResultInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678C62B9224ECDFD0007AD53 /* PaymentResultInterfaceController.swift */; }; 67BAC269219E254700713FEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BAC268219E254700713FEF /* AppDelegate.swift */; }; 67BAC26E219E254700713FEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 67BAC26C219E254700713FEF /* Main.storyboard */; }; 67BAC270219E254800713FEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67BAC26F219E254800713FEF /* Assets.xcassets */; }; @@ -104,6 +104,7 @@ 676392B322429DC800740A8C /* TransactionsInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsInterfaceController.swift; sourceTree = ""; }; 676392B52242A3F800740A8C /* TransactionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCell.swift; sourceTree = ""; }; 678C38832230950100FEAAF6 /* AccountDetailsInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailsInterfaceController.swift; sourceTree = ""; }; + 678C62B9224ECDFD0007AD53 /* PaymentResultInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentResultInterfaceController.swift; sourceTree = ""; }; 67BAC265219E254700713FEF /* SynchronyFinancial.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SynchronyFinancial.app; sourceTree = BUILT_PRODUCTS_DIR; }; 67BAC268219E254700713FEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 67BAC26D219E254700713FEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -204,6 +205,7 @@ 67BAC28A219E254900713FEF /* SynchronyFinancial WatchKit Extension */ = { isa = PBXGroup; children = ( + 678C62B9224ECDFD0007AD53 /* PaymentResultInterfaceController.swift */, 674BD1522239A39D0076AFD6 /* PayBillInterfaceController.swift */, 481864A7224802BB0059CF7A /* PaymentDetailInterfaceController.swift */, 676392B322429DC800740A8C /* TransactionsInterfaceController.swift */, @@ -486,6 +488,7 @@ 678C3885223098C400FEAAF6 /* Account.swift in Sources */, 67BAC28E219E254900713FEF /* ExtensionDelegate.swift in Sources */, 674BD1542239A39D0076AFD6 /* PayBillInterfaceController.swift in Sources */, + 678C62BA224ECDFD0007AD53 /* PaymentResultInterfaceController.swift in Sources */, 678C38842230950100FEAAF6 /* AccountDetailsInterfaceController.swift in Sources */, 673F396E21A644570051469E /* MainMenuInterfaceController.swift in Sources */, 67E17B87223812C2008871FE /* Defaults.swift in Sources */, diff --git a/SynchronyFinancial/SynchronyFinancial/Defaults.swift b/SynchronyFinancial/SynchronyFinancial/Defaults.swift index 375d8a3..55958a8 100644 --- a/SynchronyFinancial/SynchronyFinancial/Defaults.swift +++ b/SynchronyFinancial/SynchronyFinancial/Defaults.swift @@ -22,6 +22,13 @@ final class Defaults { static let token = UserDefaults.standard.string(forKey: "access_token") ?? "" static let authHeader = ["Authorization": "Bearer \(token)"] + static let careCreditDateFormatter: DateFormatter = { + var formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd" + formatter.locale = Locale.current + return formatter + }() + static var headerForLogin: [String: Any] = { return ["client_id": "carecredit", "client_secret": "", @@ -47,14 +54,9 @@ final class Defaults { var account_alias = "" // Dates - let formatter = DateFormatter() - formatter.locale = Locale.current - formatter.dateFormat = "yyyyMMdd" - // for now lets get transactions from the past 2 weeks - let start_date = formatter.string(from: Calendar.current.date(byAdding: .day, value: -14, to: Date()) ?? Date()) - let end_date = formatter.string(from: Date()) - + let start_date = careCreditDateFormatter.string(from: Calendar.current.date(byAdding: .day, value: -14, to: Date()) ?? Date()) + let end_date = careCreditDateFormatter.string(from: Date()) return ["account_alias": account_alias, "begin_sequence": "1", diff --git a/SynchronyFinancial/SynchronyFinancial/Transaction.swift b/SynchronyFinancial/SynchronyFinancial/Transaction.swift index 71c58b0..e24c2b3 100644 --- a/SynchronyFinancial/SynchronyFinancial/Transaction.swift +++ b/SynchronyFinancial/SynchronyFinancial/Transaction.swift @@ -12,6 +12,11 @@ public enum TransactionType: Int { case reimbursement = 1 } +public enum PaymentType: String { + case currentBal = "CBL" + case minimumDue = "MIN" +} + class Transaction: NSObject { var type: TransactionType var amount: Double