-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: Include swiftlint * feat: Plugin init + Setup Configurations (#1) Initialise plugin with required structure. Create structure to deal with Apple Pay. Despite being ready to deal with any kind of dictionary, provide an accelerator to read the configuration from the main bundle. Add Nimble and Quick through Cocoapods to use BDD for unit testing. * feat: Check Wallet and Payment Availability (#2) Add verification for wallet and payment availability. Payment verification is enhanced by also checking it against the configured payment networks and supported capabilities. * feat: Set Details and Trigger Payment (#3) Configure the missing payment details and, by mixing it with the configuration info, trigger the payment request. * refactor: Add DocC documentation and minor fixes. (#4) Add DocC documentation. Add empty value check and mandatory fields when fetching configuration properties. * fix: Payment Setup Verification Failed on Invalid Configuration (#5) Fix error when verifying payment setup on ReadyToPay method. If some payment network or merchant capabilities are missing, return the associated error. * fix: Errors and Contact Management (#6) Clean errors and its codes and messages accordingly. New OSPMTContact struct that allows the management of the correct shipping and billing information to use on a payment request. This is required due to a limitation on OutSystems related with nullable lists. Change the OSPMTConfigurationDelegate to OSPMTConfigurationModel, in order to comply with the new OutSystems structure. Clean code (privatise local methods and make OSPMTPayment's delegate property weak, in order to avoid possible retain cycles). * fix: Check if GivenName and FamilyName are empty (#7)
- Loading branch information
1 parent
80a1af4
commit 85bc27b
Showing
189 changed files
with
18,741 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
disabled_rules: | ||
- trailing_whitespace | ||
opt_in_rules: | ||
- empty_count | ||
- empty_string | ||
excluded: | ||
- Carthage | ||
- Pods | ||
- vendor | ||
- SwiftLint/Common/3rdPartyLib | ||
line_length: | ||
warning: 150 | ||
error: 200 | ||
ignores_function_declarations: true | ||
ignores_comments: true | ||
ignores_urls: true | ||
function_body_length: | ||
warning: 300 | ||
error: 500 | ||
function_parameter_count: | ||
warning: 6 | ||
error: 8 | ||
type_body_length: | ||
warning: 300 | ||
error: 500 | ||
file_length: | ||
warning: 1000 | ||
error: 1500 | ||
ignore_comment_only_lines: true | ||
cyclomatic_complexity: | ||
warning: 15 | ||
error: 25 | ||
reporter: "xcode" |
Large diffs are not rendered by default.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
OSPaymentsLib.xcodeproj/xcshareddata/xcschemes/OSPaymentsLib.xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
OSPaymentsLib.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
8 changes: 8 additions & 0 deletions
8
OSPaymentsLib.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>PreviewsEnabled</key> | ||
<false/> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/// All plugin errors that can be thrown | ||
enum OSPMTError: Int, CustomNSError, LocalizedError { | ||
case invalidConfiguration = 1 | ||
case walletNotAvailable = 3 | ||
case paymentNotAvailable = 5 | ||
case setupPaymentNotAvailable = 6 | ||
case invalidDecodeDetails = 8 | ||
case invalidEncodeScope = 9 | ||
case paymentTriggerPresentationFailed = 10 | ||
case paymentCancelled = 11 | ||
|
||
/// Textual description | ||
var errorDescription: String? { | ||
switch self { | ||
case .invalidConfiguration: | ||
return "Couldn't obtain the payment's informations from the configurations file." | ||
case .walletNotAvailable: | ||
return "The Apple Pay is not available in the device." | ||
case .paymentNotAvailable: | ||
return "There is no payment method configured." | ||
case .setupPaymentNotAvailable: | ||
return "There are no valid payment cards for the supported networks and/or capabilities." | ||
case .invalidDecodeDetails: | ||
return "Couldn't decode the payment details." | ||
case .invalidEncodeScope: | ||
return "Couldn't encode the payment scope." | ||
case .paymentTriggerPresentationFailed: | ||
return "Couldn't present the Apple Pay screen." | ||
case .paymentCancelled: | ||
return "Payment was cancelled by the user." | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import PassKit | ||
|
||
extension PKContactField { | ||
/// Allows the conversion of the contact text associated to Shipping and Billing Information into an object of `PKContactField` type. | ||
/// - Parameter text: Contact field text to convert. | ||
/// - Returns: The equivalent `PKContactField` object. | ||
static func convert(from text: String) -> PKContactField? { | ||
switch text.lowercased() { | ||
case "email": | ||
return .emailAddress | ||
case "name": | ||
return .name | ||
case "phone": | ||
return .phoneNumber | ||
case "postal_address": | ||
return .postalAddress | ||
default: | ||
return nil | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
OSPaymentsLib/Extensions/PKMerchantCapability+Adapter.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import PassKit | ||
|
||
extension PKMerchantCapability { | ||
/// Allows the conversion of the text associated to a Merchant Capability into an object of `PKMerchantCapability` type. | ||
/// - Parameter text: Merchant capability text to convert. | ||
/// - Returns: The equivalent `PKMerchantCapability` object. | ||
static func convert(from text: String) -> PKMerchantCapability? { | ||
switch text.lowercased() { | ||
case "debit": | ||
return .capabilityDebit | ||
case "credit": | ||
return .capabilityCredit | ||
case "3ds": | ||
return .capability3DS | ||
case "emv": | ||
return .capabilityEMV | ||
default: | ||
return nil | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import PassKit | ||
|
||
extension PKPassLibrary: OSPMTWalletAvailabilityDelegate { | ||
/// Verifies if the wallet is available for usage. | ||
/// - Returns: A boolean indicating if the wallet is available. | ||
static func isWalletAvailable() -> Bool { | ||
Self.isPassLibraryAvailable() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import PassKit | ||
|
||
extension PKPayment { | ||
/// Converts a `PKPayment` object into a `OSPMTScopeModel` one. Returns `nil` if it can't. | ||
/// - Returns: The corresponding `OSPMTScopeModel` object. Can also return `nil` if the conversion fails. | ||
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 | ||
} | ||
|
||
/// Converts a `PKPayment` object into a dictionary that relates to an `OSPMTDataModel` object. | ||
/// - Returns: The corresponding `OSPMTDataModel` dictionary object. | ||
private 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 | ||
} | ||
|
||
/// Takes the passed payment data object and converts it into a dictionary that relates to an `OSPMTTokenInfoModel` object. | ||
/// - Parameter paymentData: `Data` type object that contains information related to a payment token. | ||
/// - Returns: The corresponding `OSPMTTokenInfoModel` dictionary object. | ||
private func createTokenData(for paymentData: Data) -> [String: String] { | ||
// TODO: The type passed here will probably be changed into the Payment Service Provider's name when this is implemented. | ||
var result = [OSPMTTokenInfoModel.CodingKeys.type.rawValue: "Apple Pay"] | ||
|
||
if let token = String(data: paymentData, encoding: .utf8) { | ||
result[OSPMTTokenInfoModel.CodingKeys.token.rawValue] = token | ||
} | ||
|
||
return result | ||
} | ||
|
||
/// Takes the passed contact object and converts it into a dictionary that relates to an `OSPMTContactInfoModel` object. | ||
/// - Parameter contact: `PKContact` type object that contains information related to the filled Billing or Shipping Information | ||
/// - Returns: The corresponding `OSPMTContactInfoModel` dictionary object. | ||
private 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, !givenName.isEmpty, !familyName.isEmpty { | ||
result[OSPMTContactInfoModel.CodingKeys.name.rawValue] = "\(givenName) \(familyName)" | ||
} | ||
if let email = contact.emailAddress { | ||
result[OSPMTContactInfoModel.CodingKeys.email.rawValue] = email | ||
} | ||
|
||
return !result.isEmpty ? result : nil | ||
} | ||
|
||
/// Takes the passed address object and converts it into a dictionary that relates to an `OSPMTAddressModel` object. | ||
/// - Parameter postalAddress: `CNPostalAddress` type object that contains an address related to the filled Billing or Shipping Information. | ||
/// - Returns: The corresponding `OSPMTAddressModel` dictionary object. | ||
private func createAddressData(for postalAddress: CNPostalAddress) -> [String: String] { | ||
var result = [ | ||
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 | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
OSPaymentsLib/Extensions/PKPaymentAuthorizationController+Adapter.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import PassKit | ||
|
||
extension PKPaymentAuthorizationController: OSPMTApplePaySetupAvailabilityDelegate { | ||
/// Verifies if a payment is available for usage. | ||
/// - Returns: A boolean indicating if a payment is available. | ||
static func isPaymentAvailable() -> Bool { | ||
Self.canMakePayments() | ||
} | ||
|
||
/// Verifies if a payment is available for usage, given the passed payment networks and marchant capabilities | ||
/// - Parameters: | ||
/// - networks: Array of payment networks available by a merchant | ||
/// - merchantCapabilities: Bit set containing the payment capabilities available by a merchant. | ||
/// - Returns: A boolean indicating if the payment is available. | ||
static func isPaymentAvailable(using networks: [PKPaymentNetwork]?, and merchantCapabilities: PKMerchantCapability?) -> Bool { | ||
guard let networks = networks, let merchantCapabilities = merchantCapabilities else { return false } | ||
return Self.canMakePayments(usingNetworks: networks, capabilities: merchantCapabilities) | ||
} | ||
} | ||
|
||
extension PKPaymentAuthorizationController: OSPMTApplePayRequestTriggerDelegate { | ||
/// Triggers a payment request. The result is processed asyncrhonously and returned by the `completion` parameters. | ||
/// - Parameter completion: Block that returns the success of the payment request operation. | ||
func triggerPayment(_ completion: @escaping OSPMTRequestTriggerCompletion) { | ||
self.present(completion: completion) | ||
} | ||
|
||
/// Creates an object responsible for dealing with the payment request process, delegating the details to the passed parameter. | ||
/// - Parameters: | ||
/// - detailsModel: Payment details. | ||
/// - delegate: The object responsible for the request process' response. | ||
/// - Returns: An instance of the object or an error, it the instatiation fails. | ||
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 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: detailsModel.billingContact.isCustom | ||
? detailsModel.billingContact.contactArray | ||
: delegate.configuration.billingSupportedContacts | ||
) | ||
paymentRequest.requiredShippingContactFields = delegate.getContactFields( | ||
for: detailsModel.shippingContact.isCustom | ||
? detailsModel.shippingContact.contactArray | ||
: delegate.configuration.shippingSupportedContacts | ||
) | ||
paymentRequest.supportedCountries = delegate.configuration.supportedCountries | ||
paymentRequest.supportedNetworks = supportedNetworks | ||
|
||
let paymentAuthorizationController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) | ||
paymentAuthorizationController.delegate = delegate | ||
return .success(paymentAuthorizationController) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import PassKit | ||
|
||
extension PKPaymentNetwork { | ||
/// Allows the conversion of the text associated to a Payment Network into an object of `PKPaymentNetwork` type. | ||
/// - Parameter text: Payment network text to convert. | ||
/// - Returns: The equivalent `PKPaymentNetwork` object. | ||
static func convert(from text: String) -> PKPaymentNetwork? { | ||
switch text.lowercased() { | ||
case "amex": | ||
return .amex | ||
case "discover": | ||
return .discover | ||
case "visa": | ||
return .visa | ||
case "mastercard": | ||
return .masterCard | ||
default: | ||
return nil | ||
} | ||
} | ||
} |
Oops, something went wrong.