| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- import Flutter
- import UIKit
- import YooKassaPayments
- import Foundation
- var flutterResult: FlutterResult?
- var tokenizationModuleInput: TokenizationModuleInput?
- var flutterController: FlutterViewController?
- var yoomoneyController: UIViewController?
- public class SwiftYookassaPaymentsFlutterPlugin: NSObject, FlutterPlugin {
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "ru.yoomoney.yookassa_payments_flutter/yoomoney", binaryMessenger: registrar.messenger())
- let instance = SwiftYookassaPaymentsFlutterPlugin()
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- flutterResult = result
- // Tokenezation Flow
- if (call.method == YooMoneyService.tokenization.rawValue) {
- guard let data = call.arguments as? [String:AnyObject],
- let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
- let tokenizationModuleInputData = try? JSONDecoder().decode(TokenizationModuleInputData.self, from: jsonData)
- else {
- result(YooMoneyErrors.tokenizationData.rawValue)
- return
- }
-
- let controller = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController
- let inputData: TokenizationFlow = .tokenization(tokenizationModuleInputData)
- if let flutterVC = controller {
- let tokenezationViewController = TokenizationAssembly.makeModule(inputData: inputData, moduleOutput: flutterVC)
- yoomoneyController = tokenezationViewController;
- flutterController = flutterVC;
- flutterVC.present(tokenezationViewController, animated: true, completion: nil)
- }
- }
- // Confirmation Flow
- if (call.method == YooMoneyService.confirmation.rawValue) {
- guard let data = call.arguments as? [String:AnyObject],
- let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
- let conformationData = try? JSONDecoder().decode(ConformationData.self, from: jsonData)
- else {
- result(YooMoneyErrors.conformationData.rawValue)
- return
- }
- var paymentMethod: PaymentMethodType?
- switch conformationData.paymentMethod {
- case "bankCard":
- paymentMethod = .bankCard
- case "yooMoney":
- paymentMethod = .yooMoney
- case "sberbank":
- paymentMethod = .sberbank
- case "sbp":
- paymentMethod = .sbp
- default: break
- }
- guard
- let module = tokenizationModuleInput,
- let method = paymentMethod,
- let url = conformationData.url,
- let sheetController = yoomoneyController
- else {
- result(YooMoneyErrors.navigation.rawValue)
- return
- }
- let controller = UIApplication.shared.delegate?.window??.rootViewController as! FlutterViewController
- controller.present(sheetController, animated: true, completion: nil)
- module.startConfirmationProcess(
- confirmationUrl: url,
- paymentMethodType: method
- )
- }
- // BankCardRepeat Flow
- if (call.method == YooMoneyService.repeatPayment.rawValue) {
- guard let data = call.arguments as? [String:AnyObject],
- let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
- let bankCardRepeatModuleInputData = try? JSONDecoder().decode(BankCardRepeatModuleInputData.self, from: jsonData)
- else {
- result(YooMoneyErrors.repeatPaymentData.rawValue)
- return
- }
- let inputData: TokenizationFlow = .bankCardRepeat(bankCardRepeatModuleInputData)
- flutterController = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController
- if let controller = flutterController {
- let vc = TokenizationAssembly.makeModule(inputData: inputData, moduleOutput: controller)
- yoomoneyController = vc
- tokenizationModuleInput = vc
- controller.present(vc, animated: true, completion: nil)
- }
- }
- }
- }
- extension FlutterViewController: TokenizationModuleOutput {
- public func didFailConfirmation(error: YooKassaPayments.YooKassaPaymentsError?) {
- DispatchQueue.main.async {
- if let controller = yoomoneyController {
- controller.dismiss(animated: true)
- }
- }
- guard let result = flutterResult else { return }
- if let error = error {
- result("{\"status\":\"error\", \"error\": \"\(error.localizedDescription)\"}")
- }
- }
-
- public func tokenizationModule(
- _ module: TokenizationModuleInput,
- didTokenize token: Tokens,
- paymentMethodType: PaymentMethodType
- ) {
- tokenizationModuleInput = module
-
- if let result = flutterResult {
- result("{\"status\":\"success\", \"paymentToken\": \"\(token.paymentToken)\", \"paymentMethodType\": \"\(paymentMethodType.rawValue)\"}")
- DispatchQueue.main.async {
- if let controller = yoomoneyController {
- controller.dismiss(animated: true)
- }
- }
- }
- }
- public func didFinish(
- on module: TokenizationModuleInput,
- with error: YooKassaPaymentsError?
- ) {
- DispatchQueue.main.async {
- if let controller = yoomoneyController {
- controller.dismiss(animated: true)
- }
- }
- guard let result = flutterResult else { return }
- if let error = error {
- result("{\"status\":\"error\", \"error\": \"\(error.localizedDescription)\"}")
- } else {
- result("{\"status\":\"canceled\"}")
- }
- }
- public func didFinishConfirmation(paymentMethodType: PaymentMethodType) {
- guard let result = flutterResult else { return }
- DispatchQueue.main.async {
- if let controller = yoomoneyController {
- controller.dismiss(animated: true)
- }
- }
- result("{\"paymentMethodType\": \"\(paymentMethodType.rawValue)\"}")
- }
- }
- struct HostParameters: Codable, Equatable {
- let host: String?
- let paymentAuthorizationHost: String?
- let authHost: String?
- let configHost: String?
- enum CodingKeys: String, CodingKey {
- case host = "host"
- case paymentAuthorizationHost = "paymentAuthorizationHost"
- case authHost = "authHost"
- case configHost = "configHost"
- }
- }
- struct ConformationData: Codable, Equatable {
- let url: String?
- let paymentMethod: String?
- enum CodingKeys: String, CodingKey {
- case url = "url"
- case paymentMethod = "paymentMethod"
- }
- }
- enum YooMoneyService: String {
- case tokenization = "tokenization"
- case confirmation = "confirmation"
- case repeatPayment = "repeat"
- }
- enum YooMoneyErrors: String {
- case navigation = "ErrorNavigation"
- case tokenizationData = "ErrorTokenizationData"
- case conformationData = "ErrorConfirmationData"
- case repeatPaymentData = "ErrorRepeatPaymentData"
- case tokenizationResult = "ErrorTokenizationResult"
- }
- extension TokenizationModuleInputData: Decodable {
- enum CodingKeys: String, CodingKey {
- case clientApplicationKey = "clientApplicationKey"
- case shopName = "title"
- case shopId = "shopId"
- case purchaseDescription = "subtitle"
- case amount = "amount"
- case savePaymentMethod = "savePaymentMethod"
- case gatewayId = "gatewayId"
- case tokenizationSettings = "tokenizationSettings"
- case testModeSettings = "testModeSettings"
- case applePayMerchantIdentifier = "applePayMerchantIdentifier"
- case returnUrl = "returnUrl"
- case isLoggingEnabled = "isLoggingEnabled"
- case userPhoneNumber = "userPhoneNumber"
- case customizationSettings = "customizationSettings"
- case moneyAuthClientId = "moneyAuthClientId"
- case applicationScheme = "applicationScheme"
- case customerId = "customerId"
- case hostParameters = "hostParameters"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let clientApplicationKey = try values.decode(String.self, forKey: .clientApplicationKey)
- let shopName = try values.decode(String.self, forKey: .shopName)
- let shopId = try values.decode(String.self, forKey: .shopId)
- let purchaseDescription = try values.decode(String.self, forKey: .purchaseDescription)
- let amount = try values.decode(Amount.self, forKey: .amount)
- let gatewayId = try? values.decode(String.self, forKey: .gatewayId)
-
- let settings = try values.decode(TokenizationSettings.self, forKey: .tokenizationSettings)
- let tokenizationSettings = TokenizationSettings(paymentMethodTypes: settings.paymentMethodTypes)
-
- let testModeSettings = try? values.decode(TestModeSettings.self, forKey: .testModeSettings)
- let applePayMerchantIdentifier = try? values.decode(String.self, forKey: .applePayMerchantIdentifier)
- let returnUrl = try? values.decode(String.self, forKey: .returnUrl)
- let isLoggingEnabled = try values.decode(Bool.self, forKey: .isLoggingEnabled)
- let userPhoneNumber = try? values.decode(String.self, forKey: .userPhoneNumber)
- let customizationSettings = try values.decode(CustomizationSettings.self, forKey: .customizationSettings)
- let moneyAuthClientId = try? values.decode(String.self, forKey: .moneyAuthClientId)
- let applicationScheme = try? values.decode(String.self, forKey: .applicationScheme)
- let customerId = try? values.decode(String.self, forKey: .customerId)
- let hostParameters = try? values.decode(HostParameters.self, forKey: .hostParameters)
- var savePaymentMethod: SavePaymentMethod
- switch try values.decode(String.self, forKey: .savePaymentMethod) {
- case "SavePaymentMethod.on":
- savePaymentMethod = .on
- case "SavePaymentMethod.off":
- savePaymentMethod = .off
- default:
- savePaymentMethod = .userSelects
- }
- let userDefaults = UserDefaults.standard
- userDefaults.set(hostParameters != nil, forKey: "dev_host_preference")
- userDefaults.synchronize()
- self.init(
- clientApplicationKey: clientApplicationKey,
- shopName: shopName,
- shopId: shopId,
- purchaseDescription: purchaseDescription,
- amount: amount,
- gatewayId: gatewayId,
- tokenizationSettings: tokenizationSettings,
- testModeSettings: testModeSettings,
- returnUrl: returnUrl,
- isLoggingEnabled: isLoggingEnabled,
- userPhoneNumber: userPhoneNumber,
- customizationSettings: customizationSettings,
- savePaymentMethod: savePaymentMethod,
- moneyAuthClientId: moneyAuthClientId,
- applicationScheme: applicationScheme,
- customerId: customerId
- )
- }
- }
- extension BankCardRepeatModuleInputData: Decodable {
- enum CodingKeys: String, CodingKey {
- case clientApplicationKey = "clientApplicationKey"
- case shopName = "title"
- case purchaseDescription = "subtitle"
- case amount = "amount"
- case savePaymentMethod = "savePaymentMethod"
- case paymentMethodId = "paymentMethodId"
- case gatewayId = "gatewayId"
- case testModeSettings = "testModeSettings"
- case returnUrl = "returnUrl"
- case isLoggingEnabled = "isLoggingEnabled"
- case customizationSettings = "customizationSettings"
- case cardScanning = "cardScanning"
- case isSafeDeal = "isSafeDeal"
- case customerId = "customerId"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let clientApplicationKey = try values.decode(String.self, forKey: .clientApplicationKey)
- let shopName = try values.decode(String.self, forKey: .shopName)
- let purchaseDescription = try values.decode(String.self, forKey: .purchaseDescription)
- let amount = try values.decode(Amount.self, forKey: .amount)
- let paymentMethodId = try values.decode(String.self, forKey: .paymentMethodId)
- let gatewayId = try? values.decode(String.self, forKey: .gatewayId)
- let testModeSettings = try? values.decode(TestModeSettings.self, forKey: .testModeSettings)
- let returnUrl = try? values.decode(String.self, forKey: .returnUrl)
- let isLoggingEnabled = try values.decode(Bool.self, forKey: .isLoggingEnabled)
- let customizationSettings = try values.decode(CustomizationSettings.self, forKey: .customizationSettings)
- var savePaymentMethod: SavePaymentMethod
- switch try values.decode(String.self, forKey: .savePaymentMethod) {
- case "SavePaymentMethod.on":
- savePaymentMethod = .on
- case "SavePaymentMethod.off":
- savePaymentMethod = .off
- default:
- savePaymentMethod = .userSelects
- }
- self.init(
- clientApplicationKey: clientApplicationKey,
- shopName: shopName,
- purchaseDescription: purchaseDescription,
- paymentMethodId: paymentMethodId,
- amount: amount,
- testModeSettings: testModeSettings,
- returnUrl: returnUrl,
- isLoggingEnabled: isLoggingEnabled,
- customizationSettings: customizationSettings,
- savePaymentMethod: savePaymentMethod,
- gatewayId: gatewayId
- )
- }
- }
- extension Amount: Decodable {
- enum CodingKeys: String, CodingKey {
- case value = "value"
- case currency = "currency"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let stringValue = try values.decode(String.self, forKey: .value)
- guard let decimalValue = Decimal(string: stringValue) else { throw CommonError.decodingError }
- let currency = try values.decode(String.self, forKey: .currency)
- self.init(value: decimalValue, currency: .custom(currency))
- }
- }
- extension TokenizationSettings: Decodable {
- enum CodingKeys: String, CodingKey {
- case paymentMethodTypes = "paymentMethodTypes"
- case showYooKassaLogo = "showYooKassaLogo"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let paymentFlutterMethodTypes = try values.decode([String].self, forKey: .paymentMethodTypes)
- var paymentTypes: PaymentMethodTypes = []
- for type in paymentFlutterMethodTypes {
- switch type {
- case "PaymentMethod.bankCard":
- paymentTypes.insert(.bankCard)
- case "PaymentMethod.yooMoney":
- paymentTypes.insert(.yooMoney)
- case "PaymentMethod.sberbank":
- paymentTypes.insert(.sberbank)
- case "PaymentMethod.sbp":
- paymentTypes.insert(.sbp)
- default: break
- }
- }
- self.init(paymentMethodTypes: paymentTypes)
- }
- }
- extension TestModeSettings: Decodable {
- enum CodingKeys: String, CodingKey {
- case paymentAuthorizationPassed = "paymentAuthorizationPassed"
- case cardsCount = "cardsCount"
- case charge = "charge"
- case enablePaymentError = "enablePaymentError"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let paymentAuthorizationPassed = try values.decode(Bool.self, forKey: .paymentAuthorizationPassed)
- let cardsCount = try values.decode(Int.self, forKey: .cardsCount)
- let charge = try values.decode(Amount.self, forKey: .charge)
- let enablePaymentError = try values.decode(Bool.self, forKey: .enablePaymentError)
- self.init(
- paymentAuthorizationPassed: paymentAuthorizationPassed,
- cardsCount: cardsCount,
- charge: charge,
- enablePaymentError: enablePaymentError
- )
- }
- }
- extension CustomizationSettings: Decodable {
- enum CodingKeys: String, CodingKey {
- case mainScheme = "mainScheme"
- case showYooKassaLogo = "showYooKassaLogo"
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let schemeColor = try values.decode(Color.self, forKey: .mainScheme)
- let showYooKassaLogo = try values.decode(Bool.self, forKey: .showYooKassaLogo)
- self.init(mainScheme:
- UIColor(
- red: schemeColor.red,
- green: schemeColor.green,
- blue: schemeColor.blue,
- alpha: schemeColor.alpha
- ),
- showYooKassaLogo: showYooKassaLogo
- )
- }
- }
- public enum CommonError: Error {
- case decodingError
- }
- struct Color: Decodable {
- let red: CGFloat
- let green: CGFloat
- let blue: CGFloat
- let alpha: CGFloat
-
- init(
- red: CGFloat,
- green: CGFloat,
- blue: CGFloat,
- alpha: CGFloat
- ) {
- self.red = red
- self.green = green
- self.blue = blue
- self.alpha = alpha
- }
- enum CodingKeys: String, CodingKey {
- case red = "red"
- case blue = "blue"
- case green = "green"
- case alpha = "alpha"
- }
- init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- let red = try values.decode(CGFloat.self, forKey: .red)
- let blue = try values.decode(CGFloat.self, forKey: .blue)
- let green = try values.decode(CGFloat.self, forKey: .green)
- let alpha = try values.decode(CGFloat.self, forKey: .alpha)
- self.init(red: red / 255,
- green: green / 255,
- blue: blue / 255,
- alpha: alpha
- )
- }
- }
|