diff --git a/plugin.xml b/plugin.xml index 2dbc595..06e4c46 100644 --- a/plugin.xml +++ b/plugin.xml @@ -54,20 +54,20 @@ - - + - - - - - - + + + + + + + + + - diff --git a/src/ios/AppDelegate.swift b/src/ios/AppDelegate.swift index c419c29..834e0b3 100644 --- a/src/ios/AppDelegate.swift +++ b/src/ios/AppDelegate.swift @@ -1,6 +1,5 @@ import UIKit -import Stripe // For Cordova, we can create an AppDelegate extension: // https://stackoverflow.com/a/29288792 diff --git a/src/ios/PluginConfig.swift b/src/ios/PluginConfig.swift deleted file mode 100644 index d302ffa..0000000 --- a/src/ios/PluginConfig.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Alamofire - -public class StripePaymentsPluginConfig { - public var publishableKey: String? = nil - public var ephemeralKeyUrl: String? = nil - public var appleMerchantId: String? = nil - public var companyName: String? = nil - public var requestPaymentImmediately: Boolean? = true - public var extraHTTPHeaders: [HTTPHeader]? = [] - // TODO: - // We can add an option to execute the charge API-side, in which case - // the developer would also need to provide their 'charge' endpoint, - // meaning that the success/fail return value becomes meaningful. - // The extraHTTPHeaders now allows us to do that, to be done later.. - - // TODO need xcode for this - func parseExtraHeaders(dict: [String:String]) { - // extraHTTPHeaders.push(new HTTPHeader(dict[something])) - } -} - -let PluginConfig = StripePaymentsPluginConfig() diff --git a/src/ios/APIClient.swift b/src/ios/StripeAPIClient.swift similarity index 94% rename from src/ios/APIClient.swift rename to src/ios/StripeAPIClient.swift index a2326e0..fef1ba0 100644 --- a/src/ios/APIClient.swift +++ b/src/ios/StripeAPIClient.swift @@ -1,9 +1,9 @@ import Alamofire import Stripe -class APIClient: NSObject, STPCustomerEphemeralKeyProvider { +class StripeAPIClient: NSObject, STPCustomerEphemeralKeyProvider { - static let shared = APIClient() + static let shared = StripeAPIClient() var ephemeralKeyUrl = "" diff --git a/src/ios/PaymentOptions.swift b/src/ios/StripePaymentOptions.swift similarity index 77% rename from src/ios/PaymentOptions.swift rename to src/ios/StripePaymentOptions.swift index 16d6b6b..7eebb3f 100644 --- a/src/ios/PaymentOptions.swift +++ b/src/ios/StripePaymentOptions.swift @@ -1,15 +1,15 @@ -public struct PaymentOptions { +public struct StripePaymentOptions { // must be in smallest unit e.g. 1000 for $10.00 - public var price: UInt32 = 0 + public var price: Int = 0 // 'USD', 'MXN', 'JPY', 'GBP' etc. uppercase. public var currency: String = "USD" // 'US', 'PH', the ISO 2-letter code, uppercase. public var country: String = "US" init(dict: [String:Any]) { - price = dict["price"] as? UInt32 ?? 0 + price = dict["price"] as? Int ?? 0 currency = dict["currency"] as? String ?? "USD" country = dict["country"] as? String ?? "US" } -} \ No newline at end of file +} diff --git a/src/ios/StripePaymentsPlugin-Bridging-Header.h b/src/ios/StripePaymentsPlugin-Bridging-Header.h deleted file mode 100644 index b156388..0000000 --- a/src/ios/StripePaymentsPlugin-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import diff --git a/src/ios/StripePaymentsPlugin.swift b/src/ios/StripePaymentsPlugin.swift index 443bfc2..319cfad 100644 --- a/src/ios/StripePaymentsPlugin.swift +++ b/src/ios/StripePaymentsPlugin.swift @@ -12,9 +12,9 @@ import Stripe @objc(StripePaymentsPlugin) class StripePaymentsPlugin: CDVPlugin, STPPaymentContextDelegate { - private var paymentStatusCallback: String? = nil - private let customerContext: STPCustomerContext - private let paymentContext: STPPaymentContext + private var paymentStatusCallback: String = "" + private var customerContext: STPCustomerContext! + private var paymentContext: STPPaymentContext! override func pluginInitialize() { super.pluginInitialize() @@ -27,8 +27,8 @@ import Stripe // MARK: Init Method - @objc(init:) - public func init(command: CDVInvokedUrlCommand) { + @objc(beginStripe:) + public func beginStripe(command: CDVInvokedUrlCommand) { let error = "The Stripe Publishable Key and ephemeral key generation URL are required" guard let dict = command.arguments[0] as? [String:Any] ?? nil else { @@ -43,10 +43,10 @@ import Stripe PluginConfig.ephemeralKeyUrl = dict["ephemeralKeyUrl"] as? String ?? "" PluginConfig.appleMerchantId = dict["appleMerchantId"] as? String ?? "" PluginConfig.companyName = dict["companyName"] as? String ?? "" - PluginConfig.requestPaymentImmediately = dict["requestPaymentImmediately"] as? Boolean ?? true + PluginConfig.requestPaymentImmediately = dict["requestPaymentImmediately"] as? Bool ?? true - if headersDict = dict["extraHTTPHeaders"] as? [String:String] { - PluginConfig.parseExtraHeaders(headersDict) + if let headersDict = dict["extraHTTPHeaders"] as? [String:String] { + PluginConfig.parseExtraHeaders(dict: headersDict) } if !self.verifyConfig() { @@ -54,7 +54,7 @@ import Stripe return } - APIClient.shared.ephemeralKeyUrl = PluginConfig.ephemeralKeyUrl + StripeAPIClient.shared.ephemeralKeyUrl = PluginConfig.ephemeralKeyUrl STPPaymentConfiguration.shared().companyName = PluginConfig.companyName STPPaymentConfiguration.shared().publishableKey = PluginConfig.publishableKey @@ -62,7 +62,7 @@ import Stripe STPPaymentConfiguration.shared().appleMerchantIdentifier = PluginConfig.appleMerchantId } - customerContext = STPCustomerContext(keyProvider: APIClient.shared) + customerContext = STPCustomerContext(keyProvider: StripeAPIClient.shared) paymentContext = STPPaymentContext(customerContext: customerContext) paymentContext.delegate = self @@ -90,18 +90,24 @@ import Stripe return } - let paymentOptions = PaymentOptions(options) + let paymentOptions = PaymentOptions(dict: options) paymentContext.paymentAmount = paymentOptions.price paymentContext.paymentCurrency = paymentOptions.currency paymentContext.paymentCountry = paymentOptions.country + // Allow these to be overridden + PluginConfig.requestPaymentImmediately = options["requestPaymentImmediately"] as? Bool ?? PluginConfig.requestPaymentImmediately + if let headersDict = options["extraHTTPHeaders"] as? [String:String] { + PluginConfig.parseExtraHeaders(dict: headersDict) + } + // This dialog collects a payment method from the user. When they close it, you get a context // change event with the payment info. NO charge has been created at that point, NO source // has been created from the payment method. All that has happened is the user entered // payment data and clicked 'ok'. That's all. // After that dialog closes - after paymentContextDidChange is called with // a selectedPaymentMethod - THEN you want to call requestPayment. - paymentContext.presentPaymentMethodsViewController() + paymentContext.presentPaymentOptionsViewController() successCallback(command.callbackId, [ "status": "PAYMENT_DIALOG_SHOWN" ]) } @@ -126,57 +132,58 @@ import Stripe // MARK: STPPaymentContextDelegate func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) { - let alertController = UIAlertController( - preferredStyle: .alert, - retryHandler: { (action) in - // Retry payment context loading - paymentContext.retryLoading() - } - ) - - var message = error?.localizedDescription ?? "" + var message = error.localizedDescription var callbackMessage: String = "" - if let customerKeyError = error as? APIClient.CustomerKeyError { + if let customerKeyError = error as? StripeAPIClient.CustomerKeyError { switch customerKeyError { case .ephemeralKeyUrl: // Fail silently until base url string is set - callbackMessage = "[ERROR]: Please assign a value to `APIClient.shared.ephemeralKeyUrl` before continuing. See `StripePaymentsPlugin.swift`." + callbackMessage = "[ERROR]: Please assign a value to `StripeAPIClient.shared.ephemeralKeyUrl` before continuing. See `StripePaymentsPlugin.swift`." case .invalidResponse: // Use customer key specific error message - callbackMessage = "[ERROR]: Missing or malformed response when attempting to call `APIClient.shared.createCustomerKey`. Please check internet connection and backend response." + callbackMessage = "[ERROR]: Missing or malformed response when attempting to call `StripeAPIClient.shared.createCustomerKey`. Please check internet connection and backend response." message = "Could not retrieve customer information" } } else { // Use generic error message - callbackMessage = "[ERROR]: Unrecognized error while loading payment context: \(error)" - message = error.localizedDescription ?? "Could not retrieve payment information" + callbackMessage = "[ERROR]: Unrecognized error while loading payment context: \(error.localizedDescription)" + message = "Could not retrieve payment information" } print(callbackMessage) errorCallback(paymentStatusCallback, ["error": callbackMessage], keepCallback: true) - alertController.setMessage(message) // ?? + let alertController = UIAlertController( + title: "", + message: message, + preferredStyle: .alert + ) + let retry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in + // Retry payment context loading + self.paymentContext.retryLoading() + }) + alertController.addAction(retry) self.viewController.present(alertController, animated: true, completion: nil) } func paymentContextDidChange(_ paymentContext: STPPaymentContext) { - var isLoading = paymentContext.isLoading - var isPaymentReady = paymentContext.selectedPaymentMethod != nil + let isLoading = paymentContext.loading + let isPaymentReady = paymentContext.selectedPaymentOption != nil var label = "" var image = "" // https://stackoverflow.com/questions/11592313/how-do-i-save-a-uiimage-to-a-file - if selectedPaymentMethod = paymentContext.selectedPaymentMethod { - label = selectedPaymentMethod.label + if let selectedPaymentOption = paymentContext.selectedPaymentOption { + label = selectedPaymentOption.label image = "" let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) if let filePath = paths.first?.appendingPathComponent("StripePaymentMethod.jpg") { // Save image. do { - try UIImageJPEGRepresentation(selectedPaymentMethod.image, 1)?.write(to: filePath, options: .atomic) - image = filePath + try selectedPaymentOption.image.jpegData(compressionQuality: 1)?.write(to: filePath, options: .atomic) + image = filePath.absoluteString } catch { } } @@ -230,11 +237,9 @@ import Stripe case .error: // Use generic error message print("[ERROR]: Unrecognized error while finishing payment: \(String(describing: error))"); - self.viewController.present(UIAlertController(message: "Could not complete payment"), animated: true) - resultMsg = [ "status": "PAYMENT_COMPLETED_ERROR", - error: "[ERROR]: Unrecognized error while finishing payment: \(String(describing: error))" + "error": "[ERROR]: Unrecognized error while finishing payment: \(String(describing: error))" ] errorCallback(paymentStatusCallback, resultMsg, keepCallback: true) @@ -247,26 +252,25 @@ import Stripe } func successCallback(_ callbackId: String, _ data: [String:Any?], keepCallback: Bool = false) { - var pluginResult = CDVPluginResult( + let pluginResult = CDVPluginResult( status: .ok, - messageAs: data + messageAs: data as [AnyHashable : Any] ) pluginResult?.setKeepCallbackAs(keepCallback) self.commandDelegate!.send(pluginResult, callbackId: callbackId) } func errorCallback(_ callbackId: String, _ data: [String:Any?], keepCallback: Bool = false) { - var pluginResult = CDVPluginResult( + let pluginResult = CDVPluginResult( status: .error, - messageAs: data + messageAs: data as [AnyHashable : Any] ) pluginResult?.setKeepCallbackAs(keepCallback) self.commandDelegate!.send(pluginResult, callbackId: callbackId) } func verifyConfig() -> Bool { - return PluginConfig.publishableKey != nil && !PluginConfig.publishableKey!.isEmpty - && PluginConfig.ephemeralKeyUrl != nil && !PluginConfig.ephemeralKeyUrl!.isEmpty + return !PluginConfig.publishableKey.isEmpty && !PluginConfig.ephemeralKeyUrl.isEmpty } } diff --git a/src/ios/StripePaymentsPluginConfig.swift b/src/ios/StripePaymentsPluginConfig.swift new file mode 100644 index 0000000..3641b54 --- /dev/null +++ b/src/ios/StripePaymentsPluginConfig.swift @@ -0,0 +1,27 @@ +import Alamofire + +// TODO: +// We can add an option to execute the charge API-side, in which case +// the developer would also need to provide their 'charge' endpoint, +// meaning that the success/fail return value becomes meaningful. +// The extraHTTPHeaders now allows us to do that, to be done later.. + +public class StripePaymentsPluginConfig { + public var publishableKey: String = "" + public var ephemeralKeyUrl: String = "" + public var appleMerchantId: String = "" + public var companyName: String = "" + public var requestPaymentImmediately: Bool = true + public var extraHTTPHeaders: HTTPHeaders = [:] + + // TODO need xcode for this + func parseExtraHeaders(dict: [String:String]) { + // extraHTTPHeaders.push(new HTTPHeader(dict[something])) + // this actually needs to replace them..dunno. I mean they'll just have + // duplicates and HTTPHeaders should be able to resolve them by updating the header + // if they're already there, using the latest value (later index in array). + // must confirm that works. + } +} + +let PluginConfig = StripePaymentsPluginConfig() diff --git a/www/StripePaymentsPlugin.js b/www/StripePaymentsPlugin.js index 0b4a57d..c748629 100644 --- a/www/StripePaymentsPlugin.js +++ b/www/StripePaymentsPlugin.js @@ -19,7 +19,7 @@ var paymentStatusCallbackProcessor = function (state) { * @param {object} config {publishableKey, ephemeralKeyUrl, appleMerchantId, companyName} */ StripePaymentsPlugin.prototype.init = function (config, successCallback, errorCallback) { - exec(successCallback, errorCallback, 'StripePaymentsPlugin', 'init', [config]); + exec(successCallback, errorCallback, 'StripePaymentsPlugin', 'beginStripe', [config]); }; /**