Swiftgram/submodules/TelegramCore/Sources/BotPaymentForm.swift
2021-03-24 23:33:47 +04:00

402 lines
16 KiB
Swift

import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
import SyncCore
public struct BotPaymentInvoiceFields: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let name = BotPaymentInvoiceFields(rawValue: 1 << 0)
public static let phone = BotPaymentInvoiceFields(rawValue: 1 << 1)
public static let email = BotPaymentInvoiceFields(rawValue: 1 << 2)
public static let shippingAddress = BotPaymentInvoiceFields(rawValue: 1 << 3)
public static let flexibleShipping = BotPaymentInvoiceFields(rawValue: 1 << 4)
public static let phoneAvailableToProvider = BotPaymentInvoiceFields(rawValue: 1 << 5)
public static let emailAvailableToProvider = BotPaymentInvoiceFields(rawValue: 1 << 6)
}
public struct BotPaymentPrice : Equatable {
public let label: String
public let amount: Int64
public init(label: String, amount: Int64) {
self.label = label
self.amount = amount
}
}
public struct BotPaymentInvoice : Equatable {
public let isTest: Bool
public let requestedFields: BotPaymentInvoiceFields
public let currency: String
public let prices: [BotPaymentPrice]
}
public struct BotPaymentNativeProvider : Equatable {
public let name: String
public let params: String
}
public struct BotPaymentShippingAddress: Equatable {
public let streetLine1: String
public let streetLine2: String
public let city: String
public let state: String
public let countryIso2: String
public let postCode: String
public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) {
self.streetLine1 = streetLine1
self.streetLine2 = streetLine2
self.city = city
self.state = state
self.countryIso2 = countryIso2
self.postCode = postCode
}
}
public struct BotPaymentRequestedInfo: Equatable {
public let name: String?
public let phone: String?
public let email: String?
public let shippingAddress: BotPaymentShippingAddress?
public init(name: String?, phone: String?, email: String?, shippingAddress: BotPaymentShippingAddress?) {
self.name = name
self.phone = phone
self.email = email
self.shippingAddress = shippingAddress
}
}
public enum BotPaymentSavedCredentials: Equatable {
case card(id: String, title: String)
public static func ==(lhs: BotPaymentSavedCredentials, rhs: BotPaymentSavedCredentials) -> Bool {
switch lhs {
case let .card(id, title):
if case .card(id, title) = rhs {
return true
} else {
return false
}
}
}
}
public struct BotPaymentForm : Equatable {
public let canSaveCredentials: Bool
public let passwordMissing: Bool
public let invoice: BotPaymentInvoice
public let providerId: PeerId
public let url: String
public let nativeProvider: BotPaymentNativeProvider?
public let savedInfo: BotPaymentRequestedInfo?
public let savedCredentials: BotPaymentSavedCredentials?
}
public enum BotPaymentFormRequestError {
case generic
}
extension BotPaymentInvoice {
init(apiInvoice: Api.Invoice) {
switch apiInvoice {
case let .invoice(flags, currency, prices):
var fields = BotPaymentInvoiceFields()
if (flags & (1 << 1)) != 0 {
fields.insert(.name)
}
if (flags & (1 << 2)) != 0 {
fields.insert(.phone)
}
if (flags & (1 << 3)) != 0 {
fields.insert(.email)
}
if (flags & (1 << 4)) != 0 {
fields.insert(.shippingAddress)
}
if (flags & (1 << 5)) != 0 {
fields.insert(.flexibleShipping)
}
if (flags & (1 << 6)) != 0 {
fields.insert(.phoneAvailableToProvider)
}
if (flags & (1 << 7)) != 0 {
fields.insert(.emailAvailableToProvider)
}
self.init(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map {
switch $0 {
case let .labeledPrice(label, amount):
return BotPaymentPrice(label: label, amount: amount)
}
})
}
}
}
extension BotPaymentRequestedInfo {
init(apiInfo: Api.PaymentRequestedInfo) {
switch apiInfo {
case let .paymentRequestedInfo(_, name, phone, email, shippingAddress):
var parsedShippingAddress: BotPaymentShippingAddress?
if let shippingAddress = shippingAddress {
switch shippingAddress {
case let .postAddress(streetLine1, streetLine2, city, state, countryIso2, postCode):
parsedShippingAddress = BotPaymentShippingAddress(streetLine1: streetLine1, streetLine2: streetLine2, city: city, state: state, countryIso2: countryIso2, postCode: postCode)
}
}
self.init(name: name, phone: phone, email: email, shippingAddress: parsedShippingAddress)
}
}
}
public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
return network.request(Api.functions.payments.getPaymentForm(msgId: messageId.id))
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
return .fail(.generic)
}
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
return postbox.transaction { transaction -> BotPaymentForm in
switch result {
case let .paymentForm(flags, _, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, apiUsers):
var peers: [Peer] = []
for user in apiUsers {
let parsed = TelegramUser(user: user)
peers.append(parsed)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
return updated
})
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
var parsedNativeProvider: BotPaymentNativeProvider?
if let nativeProvider = nativeProvider, let nativeParams = nativeParams {
switch nativeParams {
case let .dataJSON(data):
parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data)
}
}
let parsedSavedInfo = savedInfo.flatMap(BotPaymentRequestedInfo.init)
var parsedSavedCredentials: BotPaymentSavedCredentials?
if let savedCredentials = savedCredentials {
switch savedCredentials {
case let .paymentSavedCredentialsCard(id, title):
parsedSavedCredentials = .card(id: id, title: title)
}
}
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: providerId), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
}
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
}
}
public enum ValidateBotPaymentFormError {
case generic
case shippingNotAvailable
case addressStateInvalid
case addressPostcodeInvalid
case addressCityInvalid
case nameInvalid
case emailInvalid
case phoneInvalid
}
public struct BotPaymentShippingOption : Equatable {
public let id: String
public let title: String
public let prices: [BotPaymentPrice]
}
public struct BotPaymentValidatedFormInfo : Equatable {
public let id: String?
public let shippingOptions: [BotPaymentShippingOption]?
}
extension BotPaymentShippingOption {
init(apiOption: Api.ShippingOption) {
switch apiOption {
case let .shippingOption(id, title, prices):
self.init(id: id, title: title, prices: prices.map {
switch $0 {
case let .labeledPrice(label, amount):
return BotPaymentPrice(label: label, amount: amount)
}
})
}
}
}
public func validateBotPaymentForm(network: Network, saveInfo: Bool, messageId: MessageId, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
var flags: Int32 = 0
if saveInfo {
flags |= (1 << 0)
}
var infoFlags: Int32 = 0
if let _ = formInfo.name {
infoFlags |= (1 << 0)
}
if let _ = formInfo.phone {
infoFlags |= (1 << 1)
}
if let _ = formInfo.email {
infoFlags |= (1 << 2)
}
var apiShippingAddress: Api.PostAddress?
if let address = formInfo.shippingAddress {
infoFlags |= (1 << 3)
apiShippingAddress = .postAddress(streetLine1: address.streetLine1, streetLine2: address.streetLine2, city: address.city, state: address.state, countryIso2: address.countryIso2, postCode: address.postCode)
}
return network.request(Api.functions.payments.validateRequestedInfo(flags: flags, msgId: messageId.id, info: .paymentRequestedInfo(flags: infoFlags, name: formInfo.name, phone: formInfo.phone, email: formInfo.email, shippingAddress: apiShippingAddress)))
|> mapError { error -> ValidateBotPaymentFormError in
if error.errorDescription == "SHIPPING_NOT_AVAILABLE" {
return .shippingNotAvailable
} else if error.errorDescription == "ADDRESS_STATE_INVALID" {
return .addressStateInvalid
} else if error.errorDescription == "ADDRESS_POSTCODE_INVALID" {
return .addressPostcodeInvalid
} else if error.errorDescription == "ADDRESS_CITY_INVALID" {
return .addressCityInvalid
} else if error.errorDescription == "REQ_INFO_NAME_INVALID" {
return .nameInvalid
} else if error.errorDescription == "REQ_INFO_EMAIL_INVALID" {
return .emailInvalid
} else if error.errorDescription == "REQ_INFO_PHONE_INVALID" {
return .phoneInvalid
} else {
return .generic
}
}
|> map { result -> BotPaymentValidatedFormInfo in
switch result {
case let .validatedRequestedInfo(_, id, shippingOptions):
return BotPaymentValidatedFormInfo(id: id, shippingOptions: shippingOptions.flatMap {
return $0.map(BotPaymentShippingOption.init)
})
}
}
}
public enum BotPaymentCredentials {
case generic(data: String, saveOnServer: Bool)
case saved(id: String, tempPassword: Data)
case applePay(data: String)
}
public enum SendBotPaymentFormError {
case generic
case precheckoutFailed
case paymentFailed
case alreadyPaid
}
public enum SendBotPaymentResult {
case done
case externalVerificationRequired(url: String)
}
public func sendBotPaymentForm(account: Account, messageId: MessageId, validatedInfoId: String?, shippingOptionId: String?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
let apiCredentials: Api.InputPaymentCredentials
switch credentials {
case let .generic(data, saveOnServer):
var credentialsFlags: Int32 = 0
if saveOnServer {
credentialsFlags |= (1 << 0)
}
apiCredentials = .inputPaymentCredentials(flags: credentialsFlags, data: .dataJSON(data: data))
case let .saved(id, tempPassword):
apiCredentials = .inputPaymentCredentialsSaved(id: id, tmpPassword: Buffer(data: tempPassword))
case let .applePay(data):
apiCredentials = .inputPaymentCredentialsApplePay(paymentData: .dataJSON(data: data))
}
var flags: Int32 = 0
if validatedInfoId != nil {
flags |= (1 << 0)
}
if shippingOptionId != nil {
flags |= (1 << 1)
}
return account.network.request(Api.functions.payments.sendPaymentForm(flags: flags, msgId: messageId.id, requestedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, credentials: apiCredentials))
|> map { result -> SendBotPaymentResult in
switch result {
case let .paymentResult(updates):
account.stateManager.addUpdates(updates)
return .done
case let .paymentVerificationNeeded(url):
return .externalVerificationRequired(url: url)
}
}
|> `catch` { error -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
if error.errorDescription == "BOT_PRECHECKOUT_FAILED" {
return .fail(.precheckoutFailed)
} else if error.errorDescription == "PAYMENT_FAILED" {
return .fail(.paymentFailed)
} else if error.errorDescription == "INVOICE_ALREADY_PAID" {
return .fail(.alreadyPaid)
}
return .fail(.generic)
}
}
public struct BotPaymentReceipt : Equatable {
public let invoice: BotPaymentInvoice
public let info: BotPaymentRequestedInfo?
public let shippingOption: BotPaymentShippingOption?
public let credentialsTitle: String
}
public func requestBotPaymentReceipt(network: Network, messageId: MessageId) -> Signal<BotPaymentReceipt, NoError> {
return network.request(Api.functions.payments.getPaymentReceipt(msgId: messageId.id))
|> retryRequest
|> map { result -> BotPaymentReceipt in
switch result {
case let .paymentReceipt(_, _, _, invoice, _, info, shipping, _, _, credentialsTitle, _):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init)
let shippingOption = shipping.flatMap(BotPaymentShippingOption.init)
return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle)
}
}
}
public struct BotPaymentInfo: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let paymentInfo = BotPaymentInfo(rawValue: 1 << 0)
public static let shippingInfo = BotPaymentInfo(rawValue: 1 << 1)
}
public func clearBotPaymentInfo(network: Network, info: BotPaymentInfo) -> Signal<Void, NoError> {
var flags: Int32 = 0
if info.contains(.paymentInfo) {
flags |= (1 << 0)
}
if info.contains(.shippingInfo) {
flags |= (1 << 1)
}
return network.request(Api.functions.payments.clearSavedInfo(flags: flags))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}