Initial commit
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
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
|
||||
|
||||
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 inputData: TokenizationFlow = .tokenization(tokenizationModuleInputData)
|
||||
|
||||
let controller = UIApplication.shared.delegate?.window??.rootViewController as! FlutterViewController
|
||||
|
||||
let vc = TokenizationAssembly.makeModule(inputData: inputData, moduleOutput: controller)
|
||||
controller.present(vc, animated: true, completion: nil)
|
||||
|
||||
flutterController = controller
|
||||
yoomoneyController = vc
|
||||
}
|
||||
|
||||
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 "applePay":
|
||||
paymentMethod = .applePay
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
tokenizationModuleInput = vc
|
||||
controller.present(vc, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FlutterViewController: TokenizationModuleOutput {
|
||||
public func didSuccessfullyPassedCardSec(on module: TokenizationModuleInput) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let alertController = UIAlertController(
|
||||
title: "3D-Sec",
|
||||
message: "Successfully passed 3d-sec",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
let action = UIAlertAction(title: "OK", style: .default)
|
||||
alertController.addAction(action)
|
||||
self.dismiss(animated: true)
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
public func tokenizationModule(
|
||||
_ module: TokenizationModuleInput,
|
||||
didTokenize token: Tokens,
|
||||
paymentMethodType: PaymentMethodType
|
||||
) {
|
||||
tokenizationModuleInput = module
|
||||
|
||||
if let result = flutterResult {
|
||||
result("{\"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 { [weak self] in
|
||||
if let controller = yoomoneyController {
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func didSuccessfullyConfirmation(
|
||||
paymentMethodType: PaymentMethodType
|
||||
) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let alertController = UIAlertController(
|
||||
title: "Confirmation",
|
||||
message: "Successfully confirmation",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
let action = UIAlertAction(title: "OK", style: .default)
|
||||
alertController.addAction(action)
|
||||
self.dismiss(animated: true)
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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"
|
||||
|
||||
enum CustomizationKeys: String, CodingKey {
|
||||
case mainScheme = "mainScheme"
|
||||
case showYooKassaLogo = "showYooKassaLogo"
|
||||
}
|
||||
}
|
||||
|
||||
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 gatewayId = try? values.decode(String.self, forKey: .gatewayId)
|
||||
|
||||
let settings = try values.decode(TokenizationSettings.self, forKey: .tokenizationSettings)
|
||||
let customizationContainer = try values.nestedContainer(keyedBy: CodingKeys.CustomizationKeys.self, forKey: .customizationSettings)
|
||||
let showYooKassaLogo = try customizationContainer.decode(Bool.self, forKey: .showYooKassaLogo)
|
||||
let tokenizationSettings = TokenizationSettings(paymentMethodTypes: settings.paymentMethodTypes, showYooKassaLogo: showYooKassaLogo)
|
||||
|
||||
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,
|
||||
purchaseDescription: purchaseDescription,
|
||||
amount: amount,
|
||||
gatewayId: gatewayId,
|
||||
tokenizationSettings: tokenizationSettings,
|
||||
testModeSettings: testModeSettings,
|
||||
applePayMerchantIdentifier: applePayMerchantIdentifier,
|
||||
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 = "shopName"
|
||||
case purchaseDescription = "purchaseDescription"
|
||||
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 value = try values.decode(Double.self, forKey: .value)
|
||||
let currency = try values.decode(String.self, forKey: .currency)
|
||||
self.init(value: Decimal(value), 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.applePay":
|
||||
paymentTypes.insert(.applePay)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
let showYooKassaLogo = try values.decodeIfPresent(Bool.self, forKey: .showYooKassaLogo)
|
||||
self.init(paymentMethodTypes: paymentTypes, showYooKassaLogo: showYooKassaLogo ?? true)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let schemeColor = try values.decode(Color.self, forKey: .mainScheme)
|
||||
|
||||
self.init(mainScheme:
|
||||
UIColor(
|
||||
red: schemeColor.red,
|
||||
green: schemeColor.green,
|
||||
blue: schemeColor.blue,
|
||||
alpha: schemeColor.alpha
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#import <Flutter/Flutter.h>
|
||||
|
||||
@interface YookassaPaymentsFlutterPlugin : NSObject<FlutterPlugin>
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
#import "YookassaPaymentsFlutterPlugin.h"
|
||||
#if __has_include(<yookassa_payments_flutter/yookassa_payments_flutter-Swift.h>)
|
||||
#import <yookassa_payments_flutter/yookassa_payments_flutter-Swift.h>
|
||||
#else
|
||||
// Support project import fallback if the generated compatibility header
|
||||
// is not copied when this plugin is created as a library.
|
||||
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
|
||||
#import "yookassa_payments_flutter-Swift.h"
|
||||
#endif
|
||||
|
||||
@implementation YookassaPaymentsFlutterPlugin
|
||||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
|
||||
[SwiftYookassaPaymentsFlutterPlugin registerWithRegistrar:registrar];
|
||||
}
|
||||
@end
|
||||
Reference in New Issue
Block a user