Skip to content

Commit

Permalink
feat: Set Details and Trigger Payment
Browse files Browse the repository at this point in the history
Configure the missing payment details and, by mixing it with the configuration info, trigger the payment request.
  • Loading branch information
OS-ricardomoreirasilva committed Aug 4, 2022
1 parent a39c32d commit a58f9cb
Show file tree
Hide file tree
Showing 30 changed files with 1,684 additions and 47 deletions.
98 changes: 87 additions & 11 deletions OSPaymentsLib.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions OSPaymentsLib/Error/OSPMTError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ enum OSPMTError: Int, CustomNSError, LocalizedError {
case walletNotAvailable = 2
case paymentNotAvailable = 3
case setupPaymentNotAvailable = 4
case invalidDecodeDetails = 6
case paymentTriggerPresentationFailed = 7
case paymentTriggerNotCompleted = 8
case invalidEncodeScope = 9

var errorDescription: String? {
switch self {
Expand All @@ -14,6 +18,14 @@ enum OSPMTError: Int, CustomNSError, LocalizedError {
return "Payment is not available on this device."
case .setupPaymentNotAvailable:
return "Payment through the configured networks and capabilities is not available on this device."
case .invalidDecodeDetails:
return "Couldn't decode payment details."
case .paymentTriggerPresentationFailed:
return "Couldn't present the Apple Pay screen."
case .paymentTriggerNotCompleted:
return "Couldn't complete the payment trigger process."
case .invalidEncodeScope:
return "Couldn't encode payment scope."
}
}
}
18 changes: 18 additions & 0 deletions OSPaymentsLib/Extensions/PKContactField+Adapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import PassKit

extension PKContactField {
static func convert(from text: String) -> PKContactField? {
switch text.lowercased() {
case "email":
return .emailAddress
case "name":
return .name
case "phone":
return .phoneNumber
case "postaladdress":
return .postalAddress
default:
return nil
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import PassKit

protocol OSPMTWalletAvailabilityDelegate: AnyObject {
static func isWalletAvailable() -> Bool
}

extension PKPassLibrary: OSPMTWalletAvailabilityDelegate {
static func isWalletAvailable() -> Bool {
Self.isPassLibraryAvailable()
Expand Down
79 changes: 79 additions & 0 deletions OSPaymentsLib/Extensions/PKPayment+Adapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import PassKit

extension PKPayment {
func createScopeModel() -> OSPMTScopeModel? {
var result: [String: Any] = [OSPMTScopeModel.CodingKeys.paymentData.rawValue: self.createTokenDataData()]
if let shippingContact = self.shippingContact {
result[OSPMTScopeModel.CodingKeys.shippingInfo.rawValue] = self.createContactInfoData(for: shippingContact)
}

guard
let scopeData = try? JSONSerialization.data(withJSONObject: result),
let scopeModel = try? JSONDecoder().decode(OSPMTScopeModel.self, from: scopeData)
else { return nil }
return scopeModel
}

func createTokenDataData() -> [String: Any] {
var result: [String: Any] = [
OSPMTDataModel.CodingKeys.tokenData.rawValue: self.createTokenData(for: self.token.paymentData)
]
if let billingContact = self.billingContact {
result[OSPMTDataModel.CodingKeys.billingInfo.rawValue] = self.createContactInfoData(for: billingContact)
}
if let paymentMethodName = self.token.paymentMethod.displayName {
let cardInfo = paymentMethodName.components(separatedBy: " ")
if let cardNetwork = cardInfo.first, let cardDetails = cardInfo.last {
result[OSPMTDataModel.CodingKeys.cardDetails.rawValue] = cardDetails
result[OSPMTDataModel.CodingKeys.cardNetwork.rawValue] = cardNetwork
}
}

return result
}

func createTokenData(for paymentData: Data) -> [String: Any] {
var result: [String: Any] = [OSPMTTokenInfoModel.CodingKeys.type.rawValue: "Payment Service Provider Name"]

if let token = String(data: paymentData, encoding: .utf8) {
result[OSPMTTokenInfoModel.CodingKeys.token.rawValue] = token
}

return result
}

func createContactInfoData(for contact: PKContact) -> [String: Any] {
var result = [String: Any]()
if let address = contact.postalAddress {
result[OSPMTContactInfoModel.CodingKeys.address.rawValue] = self.createAddressData(for: address)
}
if let phoneNumber = contact.phoneNumber {
result[OSPMTContactInfoModel.CodingKeys.phoneNumber.rawValue] = phoneNumber.stringValue
}
if let name = contact.name, let givenName = name.givenName, let familyName = name.familyName {
result[OSPMTContactInfoModel.CodingKeys.name.rawValue] = "\(givenName) \(familyName)"
}
if let email = contact.emailAddress {
result[OSPMTContactInfoModel.CodingKeys.email.rawValue] = email
}

return result
}

func createAddressData(for postalAddress: CNPostalAddress) -> [String: Any] {
var result: [String: Any] = [
OSPMTAddressModel.CodingKeys.postalCode.rawValue: postalAddress.postalCode,
OSPMTAddressModel.CodingKeys.fullAddress.rawValue: postalAddress.street,
OSPMTAddressModel.CodingKeys.countryCode.rawValue: postalAddress.isoCountryCode,
OSPMTAddressModel.CodingKeys.city.rawValue: postalAddress.city
]
if !postalAddress.subAdministrativeArea.isEmpty {
result[OSPMTAddressModel.CodingKeys.administrativeArea.rawValue] = postalAddress.subAdministrativeArea
}
if !postalAddress.state.isEmpty {
result[OSPMTAddressModel.CodingKeys.state.rawValue] = postalAddress.state
}

return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import PassKit

extension PKPaymentAuthorizationController: OSPMTApplePaySetupAvailabilityDelegate {
static func isPaymentAvailable() -> Bool {
Self.canMakePayments()
}

static func isPaymentAvailable(using networks: [PKPaymentNetwork], and merchantCapabilities: PKMerchantCapability) -> Bool {
Self.canMakePayments(usingNetworks: networks, capabilities: merchantCapabilities)
}
}

extension PKPaymentAuthorizationController: OSPMTApplePayRequestTriggerDelegate {
static func createRequestTriggerBehaviour(for detailsModel: OSPMTDetailsModel, andDelegate delegate: OSPMTApplePayRequestBehaviour?) -> Result<OSPMTApplePayRequestTriggerDelegate, OSPMTError> {
guard
let delegate = delegate,
let merchantIdentifier = delegate.configuration.merchantID,
let countryCode = delegate.configuration.merchantCountryCode,
let merchantCapabilities = delegate.configuration.merchantCapabilities,
let paymentSummaryItems = delegate.getPaymentSummaryItems(for: detailsModel),
let requiredBillingContactFields = detailsModel.billingContactArray ?? delegate.configuration.billingSupportedContacts,
let requiredShippingContactFields = detailsModel.shippingContactArray ?? delegate.configuration.shippingSupportedContacts,
let supportedNetworks = delegate.configuration.supportedNetworks
else { return .failure(.invalidConfiguration) }

let paymentRequest = PKPaymentRequest()
paymentRequest.merchantIdentifier = merchantIdentifier
paymentRequest.countryCode = countryCode
paymentRequest.currencyCode = detailsModel.currency
paymentRequest.merchantCapabilities = merchantCapabilities
paymentRequest.paymentSummaryItems = paymentSummaryItems
paymentRequest.requiredBillingContactFields = delegate.getContactFields(for: requiredBillingContactFields)
paymentRequest.requiredShippingContactFields = delegate.getContactFields(for: requiredShippingContactFields)
paymentRequest.supportedNetworks = supportedNetworks

let paymentAuthorizationController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
paymentAuthorizationController.delegate = delegate
return .success(paymentAuthorizationController)
}

func triggerPayment(_ completion: @escaping OSPMTRequestTriggerCompletion) {
self.present(completion: completion)
}
}

This file was deleted.

49 changes: 49 additions & 0 deletions OSPaymentsLib/Models/OSPMTAddressModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
struct OSPMTAddressModel: Codable, Equatable {
let postalCode: String
let fullAddress: String
let countryCode: String
let city: String
let administrativeArea: String?
let state: String?

enum CodingKeys: String, CodingKey {
case postalCode, fullAddress, countryCode, city, administrativeArea, state
}

init(postalCode: String, fullAddress: String, countryCode: String, city: String, administrativeArea: String? = nil, state: String? = nil) {
self.postalCode = postalCode
self.fullAddress = fullAddress
self.countryCode = countryCode
self.city = city
self.administrativeArea = administrativeArea
self.state = state
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let postalCode = try container.decode(String.self, forKey: .postalCode)
let fullAddress = try container.decode(String.self, forKey: .fullAddress)
let countryCode = try container.decode(String.self, forKey: .countryCode)
let city = try container.decode(String.self, forKey: .city)
let administrativeArea = try container.decodeIfPresent(String.self, forKey: .administrativeArea)
let state = try container.decodeIfPresent(String.self, forKey: .state)
self.init(
postalCode: postalCode,
fullAddress: fullAddress,
countryCode: countryCode,
city: city,
administrativeArea: administrativeArea,
state: state
)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(postalCode, forKey: .postalCode)
try container.encode(fullAddress, forKey: .fullAddress)
try container.encode(countryCode, forKey: .countryCode)
try container.encode(city, forKey: .city)
try container.encodeIfPresent(administrativeArea, forKey: .administrativeArea)
try container.encodeIfPresent(state, forKey: .state)
}
}
40 changes: 40 additions & 0 deletions OSPaymentsLib/Models/OSPMTContactInfoModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
struct OSPMTContactInfoModel: Codable {
let address: OSPMTAddressModel?
let phoneNumber: String?
let name: String?
let email: String?

enum CodingKeys: String, CodingKey {
case address, phoneNumber, name, email
}

init(address: OSPMTAddressModel? = nil, phoneNumber: String? = nil, name: String? = nil, email: String? = nil) {
self.address = address
self.phoneNumber = phoneNumber
self.name = name
self.email = email
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let address = try container.decodeIfPresent(OSPMTAddressModel.self, forKey: .address)
let phoneNumber = try container.decodeIfPresent(String.self, forKey: .phoneNumber)
let name = try container.decodeIfPresent(String.self, forKey: .name)
let email = try container.decodeIfPresent(String.self, forKey: .email)
self.init(address: address, phoneNumber: phoneNumber, name: name, email: email)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(address, forKey: .address)
try container.encodeIfPresent(phoneNumber, forKey: .phoneNumber)
try container.encodeIfPresent(name, forKey: .name)
try container.encodeIfPresent(email, forKey: .email)
}
}

extension OSPMTContactInfoModel: Equatable {
static func == (lhs: OSPMTContactInfoModel, rhs: OSPMTContactInfoModel) -> Bool {
lhs.address == rhs.address && lhs.phoneNumber == rhs.phoneNumber && lhs.name == rhs.name && lhs.email == rhs.email
}
}
43 changes: 43 additions & 0 deletions OSPaymentsLib/Models/OSPMTDataModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
struct OSPMTDataModel: Codable {
let billingInfo: OSPMTContactInfoModel
let cardDetails: String
let cardNetwork: String
let tokenData: OSPMTTokenInfoModel

enum CodingKeys: String, CodingKey {
case billingInfo, cardDetails, cardNetwork, tokenData
}

init(billingInfo: OSPMTContactInfoModel, cardDetails: String, cardNetwork: String, tokenData: OSPMTTokenInfoModel) {
self.billingInfo = billingInfo
self.cardDetails = cardDetails
self.cardNetwork = cardNetwork
self.tokenData = tokenData
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let billingInfo = try container.decode(OSPMTContactInfoModel.self, forKey: .billingInfo)
let cardDetails = try container.decode(String.self, forKey: .cardDetails)
let cardNetwork = try container.decode(String.self, forKey: .cardNetwork)
let tokenData = try container.decode(OSPMTTokenInfoModel.self, forKey: .tokenData)
self.init(billingInfo: billingInfo, cardDetails: cardDetails, cardNetwork: cardNetwork, tokenData: tokenData)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(billingInfo, forKey: .billingInfo)
try container.encode(cardDetails, forKey: .cardDetails)
try container.encode(cardNetwork, forKey: .cardNetwork)
try container.encode(tokenData, forKey: .tokenData)
}
}

extension OSPMTDataModel: Equatable {
static func == (lhs: OSPMTDataModel, rhs: OSPMTDataModel) -> Bool {
lhs.billingInfo == rhs.billingInfo
&& lhs.cardDetails == rhs.cardDetails
&& lhs.cardNetwork == rhs.cardNetwork
&& lhs.tokenData == rhs.tokenData
}
}
53 changes: 53 additions & 0 deletions OSPaymentsLib/Models/OSPMTDetailsModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import PassKit

enum OSPMTStatus: String, Codable {
case final
case pending
}

struct OSPMTDetailsModel: Codable {
let amount: Decimal
let currency: String
let status: OSPMTStatus
let shippingContactArray: [String]?
let billingContactArray: [String]?

enum CodingKeys: String, CodingKey {
case amount
case currency
case status
case shippingContactArray = "shippingContacts"
case billingContactArray = "billingContacts"
}

init(amount: Decimal, currency: String, status: OSPMTStatus, shippingContactArray: [String]? = nil, billingContactArray: [String]? = nil) {
self.amount = amount
self.currency = currency
self.status = status
self.shippingContactArray = shippingContactArray
self.billingContactArray = billingContactArray
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let amount = try container.decode(Decimal.self, forKey: .amount)
let currency = try container.decode(String.self, forKey: .currency)
let status = try container.decode(OSPMTStatus.self, forKey: .status)
let shippingContactArray = try container.decodeIfPresent([String].self, forKey: .shippingContactArray)
let billingContactArray = try container.decodeIfPresent([String].self, forKey: .billingContactArray)
self.init(
amount: amount, currency: currency, status: status, shippingContactArray: shippingContactArray, billingContactArray: billingContactArray
)
}
}

// MARK: Apple Pay extension
extension OSPMTDetailsModel {
var paymentAmount: NSDecimalNumber {
NSDecimalNumber(decimal: self.amount)
}

var paymentSummaryItemType: PKPaymentSummaryItemType {
self.status == .final ? .final : .pending
}
}
Loading

0 comments on commit a58f9cb

Please sign in to comment.