diff --git a/PayrailsCSE.podspec b/PayrailsCSE.podspec index f59f001..85c8c66 100644 --- a/PayrailsCSE.podspec +++ b/PayrailsCSE.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'PayrailsCSE' - s.version = '0.1.1' + s.version = '0.2.0' s.summary = 'Payrails client-side encryption SDK' # This description is used to generate tags and improve search results. diff --git a/Sources/PayrailsCSE/PayrailsCSE.swift b/Sources/PayrailsCSE/PayrailsCSE.swift index 6e7205b..cc61bec 100644 --- a/Sources/PayrailsCSE/PayrailsCSE.swift +++ b/Sources/PayrailsCSE/PayrailsCSE.swift @@ -17,21 +17,16 @@ public struct PayrailsCSE { var cseConfig: CSEConfiguration? public init(data: String, version: String) { - print("Initializing config of version", version) let config = parseConfig(data: data) cseConfig = config } - func encryptCard(card: Card) throws -> String { + public func encryptCardData(card: Card) throws -> String { let jsonCard = try JSONEncoder().encode(card) - - guard let cseConfig = cseConfig else { - throw NSError(domain: "ConfigError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing Config"]) - } let header = JWEHeader(keyManagementAlgorithm: .RSAOAEP256, contentEncryptionAlgorithm: .A256CBCHS512) - let publicKey: SecKey = try getPublicKey(cseConfig.tokenization.publicKey) + let publicKey: SecKey = try getPublicKey(extractPublicKey()) let encrypter = Encrypter(keyManagementAlgorithm: .RSAOAEP256, contentEncryptionAlgorithm: .A256CBCHS512, encryptionKey: publicKey)! let jwe = try! JWE(header: header, payload: Payload(jsonCard), encrypter: encrypter) @@ -52,6 +47,10 @@ public struct PayrailsCSE { throw NSError(domain: "ConfigError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing Config"]) } + guard let tokenization = cseConfig.tokenization else { + throw NSError(domain: "ConfigError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing tokenization config"]) + } + let card = Card( holderReference: cseConfig.holderReference, cardNumber: cardNumber, @@ -61,25 +60,25 @@ public struct PayrailsCSE { securityCode: securityCode ) - let encryptedCard = try! encryptCard(card: card) - guard let tokenizeURL = URL(string: cseConfig.tokenization.links.tokenize.href) else { + let encryptedCard = try! encryptCardData(card: card) + guard let tokenizeURL = URL(string: tokenization.links.tokenize.href) else { throw NSError(domain: "URLParsingError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]) } - var request = URLRequest(url: tokenizeURL) - request.httpMethod = cseConfig.tokenization.links.tokenize.method + request.httpMethod = tokenization.links.tokenize.method request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(cseConfig.token)", forHTTPHeaderField: "Authorization") request.setValue(UUID().uuidString, forHTTPHeaderField: "x-idempotency-key") - + let encoder = JSONEncoder() let jsonRequest = try encoder.encode(TokenizationRequest( - id: cseConfig.tokenization.id, + id: tokenization.id, holderReference: cseConfig.holderReference, encryptedInstrumentDetails: encryptedCard, futureUsage: futureUsage, - storeInstrument: storeInstrument + storeInstrument: storeInstrument, + vaultProviderConfigId: tokenization.vaultProviderConfigId )) request.httpBody = jsonRequest @@ -101,7 +100,7 @@ public struct PayrailsCSE { if httpResponse.statusCode == 201 { let jsonResponse = try JSONDecoder().decode(Instrument.self, from: data) - + completion(.success(TokenizeResponse(code: httpResponse.statusCode, instrument: jsonResponse, errors: nil))) return } else { @@ -152,15 +151,45 @@ public struct PayrailsCSE { return publicKeyRef } + + private func extractPublicKey() throws -> String { + guard let cseConfig = cseConfig else { + throw NSError(domain: "ConfigError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing Config"]) + } + + if let tokenization = cseConfig.tokenization { + return tokenization.publicKey + } else if let vaultConfiguration = cseConfig.vaultConfiguration { + return vaultConfiguration.encryptionPublicKey + } else { + throw NSError(domain: "Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing publicKey in configuration"]) + } + } } public struct Card: Codable { - var holderReference: String - var cardNumber: String - var expiryMonth: String - var expiryYear: String - var holderName: String? - var securityCode: String? + public var holderReference: String? + public var cardNumber: String? + public var expiryMonth: String? + public var expiryYear: String? + public var holderName: String? + public var securityCode: String? + + public init( + holderReference: String?, + cardNumber: String?, + expiryMonth: String?, + expiryYear: String?, + holderName: String?, + securityCode: String? + ) { + self.holderReference = holderReference + self.cardNumber = cardNumber + self.expiryMonth = expiryMonth + self.expiryYear = expiryYear + self.holderName = holderName + self.securityCode = securityCode + } } struct TokenizationRequest: Codable { @@ -169,18 +198,26 @@ struct TokenizationRequest: Codable { let encryptedInstrumentDetails: String let futureUsage: FutureUsage? let storeInstrument: Bool? + let vaultProviderConfigId: String? } struct CSEConfiguration: Codable { let token: String let holderReference: String - let tokenization: Tokenization + let tokenization: Tokenization? + let vaultConfiguration: VaultConfiguration? +} + +struct VaultConfiguration: Codable { + let encryptionPublicKey: String + let providerConfigId: String? } struct Tokenization: Codable { let id: UUID let publicKey: String let links: Links + let vaultProviderConfigId: String? } struct Links: Codable { diff --git a/Tests/PayrailsCSETests/PayrailsCSETests.swift b/Tests/PayrailsCSETests/PayrailsCSETests.swift index baa3441..ab07b9f 100644 --- a/Tests/PayrailsCSETests/PayrailsCSETests.swift +++ b/Tests/PayrailsCSETests/PayrailsCSETests.swift @@ -2,4 +2,58 @@ import XCTest @testable import PayrailsCSE final class PayrailsCSETests: XCTestCase { + + func testConfigInitialization() { + + // Tokenization config + let data = "eyJ0b2tlbiI6ImV5SmhiR2NpT2lKU1V6STFOaUlzSW10cFpDSTZJalV4TURneE5qYzRMV1ppWXpFdE5ETXlNQzA1WkRnMkxUUTRabUU1WW1ZM01tVXdNQ0lzSW5SNWNDSTZJa3BYVkNKOS5leUpoZFdRaU9sc2lhSFIwY0RvdkwyeHZZMkZzYUc5emREbzRNRGd6TDJGMWRHZ2lYU3dpWlhod0lqb3hOekEyTURBNE5EY3lMQ0pvZEhSd2N6b3ZMM0JoZVhKaGFXeHpMbWx2TDJOc1lXbHRjeTlqZFhOMGIyMWZjMk52Y0dVaU9pSjdYQ0poYkd4dmQzTkJiR3hjSWpwbVlXeHpaU3hjSW1WNFpXTjFkR2x2Ymtsa1hDSTZYQ0pjSWl4Y0ltaHZiR1JsY2xKbFptVnlaVzVqWlZ3aU9sd2laV1ptWVhSY0lpeGNJblJ2YTJWdWFYcGhkR2x2Ymtsa1hDSTZYQ0ppWldZNE1qQTNZUzB4WmpRNUxUUXdNMll0T1RZMk15MWhPRGxoTURRNE9ESmxNbVZjSWl4Y0luUjVjR1ZjSWpwY0ltTnNhV1Z1ZEZ3aWZTSXNJbWx6Y3lJNkluQmhlWEpoYVd4eklpd2lhMmxrSWpvaU5URXdPREUyTnpndFptSmpNUzAwTXpJd0xUbGtPRFl0TkRobVlUbGlaamN5WlRBd0lpd2ljR1Z5YldsemMybHZibk1pT2xzaVkyeHBaVzUwT21WNFpXTjFkR2x2Ym5NNllYVjBhRzl5YVhwbElpd2lZMnhwWlc1ME9tVjRaV04xZEdsdmJuTTZZMjl1Wm1seWJTSXNJbU5zYVdWdWREcGxlR1ZqZFhScGIyNXpPbkpsWVdRaUxDSmpiR2xsYm5RNmFXNXpkSEoxYldWdWRITTZZM0psWVhSbElpd2lZMnhwWlc1ME9tbHVjM1J5ZFcxbGJuUnpPblJ2YTJWdWFYcGxJbDBzSW5OMVlpSTZJbTFsY21Ob1lXNTBMWE5rYXlKOS5ubzdwU2tUdVRlQkdKXzZMdE5ZTVppYmdUQzBYemYtcW5VZWxLVjl2a29ucDNIOV85S1FDNlVwQzhLM0dlcjNaUlFFLTc4dUM0cjBVSnl1dkpfSEh4SHNfNWd6U2FOOEVaNGNVWmFSYzhEZWVGdnM2T3VMWnI2VjgtUWQ5aVQ4cmxIMUJCUG43UGhRUFJ4NHhvTVl0Wlc4NlFUbTVnbWJHOEpsX0xJcDVPNE15ZkVnZDFCbGt2VjY0Z1lGOWxjaGgycGxUb1JnUVh6cjZwVFkxTDh2aVQxc1BOZlJWZHA3dUVuN3hhMmNyRDdET1JnUlZyUmJ2dC1zUHYyX1R1aFgzaGxYZkJfYjBvZmtSTW5hMDVVdWJiZkQwaGFaNnpIOERqREowdHFxMHl3ckdWWGJPNzV1YjdmTGdxbFdUcnBGQWU5VmV5OVhKdjZuaHFnOGx3TEpOQmciLCJob2xkZXJSZWZlcmVuY2UiOiJlZmZhdCIsImFtb3VudCI6eyJ2YWx1ZSI6IjEyLjUwIiwiY3VycmVuY3kiOiJFVVIifSwidG9rZW5pemF0aW9uIjp7ImlkIjoiYmVmODIwN2EtMWY0OS00MDNmLTk2NjMtYTg5YTA0ODgyZTJlIiwicHVibGljS2V5IjoiTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE1dGpTSGVLSVhtdEUybHZTRFZtY1pVeFFCc1RLdm5VTlZhZGNWeTRJQllpaDNUN3cyalUyald6S2hoeWNiYlBSZzcvcFpTWEY0aDhzNG9WZnhTSUxRN3NKRno3L2NUaVQzejAvbXR2aGdpQjdqZGZ3VTliblRmRG9INS9CMlZKZmlJcmlncWdzeXFiYzJ5RW5Xa2tjOURtUG9ybldsQ3NpL3YzMGY3WlhGU0szUStXRmFTQ1dGQUtYTXlyNHZnSXp5dkYvYi9CM3R2Si9LYXhBMnVMTFl6d2FpamZBVjcrQWJyZVFONmxmYXJ5NmZKQmJkVkNEZmlaMWFGY2VhMFRiZ0VLbWZ0dkI1OHd1bG5hOU4xYlluUFhFRnhUaVM0cFVScUxmUmNZNWRyTWVlOUFsNHhadEl3Q0M2ZXA2ZjN4ZzlxbkJYbVovckhhYzNTRTV2aG40ZFFJREFRQUIiLCJ2YXVsdFByb3ZpZGVyQ29uZmlnSWQiOiJiMjRmZjAwNy03MjhjLTQ1NWQtYjUxYy01NTYxMDhjY2RmNTkiLCJsaW5rcyI6eyJ0b2tlbml6ZSI6eyJtZXRob2QiOiJQT1NUIiwiaHJlZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvcGF5bWVudC9pbnN0cnVtZW50cy90b2tlbml6ZSJ9fX19" + + let version = "1.0.0" + + let cse = PayrailsCSE.init(data: data, version: version) + XCTAssertNotNil(cse.cseConfig?.tokenization, "Expected tokenization config to exist") + } + + func testCallingTokenizeWithDropInConfig() { + let data = "eyJ0b2tlbiI6ImV5SmhiR2NpT2lKU1V6STFOaUlzSW10cFpDSTZJalV4TURneE5qYzRMV1ppWXpFdE5ETXlNQzA1WkRnMkxUUTRabUU1WW1ZM01tVXdNQ0lzSW5SNWNDSTZJa3BYVkNKOS5leUpoZFdRaU9sc2lhSFIwY0RvdkwyeHZZMkZzYUc5emREbzRNRGd6TDJGMWRHZ2lYU3dpWlhod0lqb3hOekEyTURJeE16WXlMQ0pvZEhSd2N6b3ZMM0JoZVhKaGFXeHpMbWx2TDJOc1lXbHRjeTlqZFhOMGIyMWZjMk52Y0dVaU9pSjdYQ0poYkd4dmQzTkJiR3hjSWpwbVlXeHpaU3hjSW1WNFpXTjFkR2x2Ymtsa1hDSTZYQ0l3WWpkaFpqZ3laQzFpTW1FNExUUTFZVE10T1dVMllpMWxaR1UzWkdKaU56SXhNVEJjSWl4Y0ltaHZiR1JsY2xKbFptVnlaVzVqWlZ3aU9sd2laV1ptWVhSY0lpeGNJblJ2YTJWdWFYcGhkR2x2Ymtsa1hDSTZYQ0pjSWl4Y0luUjVjR1ZjSWpwY0ltTnNhV1Z1ZEZ3aWZTSXNJbWx6Y3lJNkluQmhlWEpoYVd4eklpd2lhMmxrSWpvaU5URXdPREUyTnpndFptSmpNUzAwTXpJd0xUbGtPRFl0TkRobVlUbGlaamN5WlRBd0lpd2ljR1Z5YldsemMybHZibk1pT2xzaVkyeHBaVzUwT21WNFpXTjFkR2x2Ym5NNllYVjBhRzl5YVhwbElpd2lZMnhwWlc1ME9tVjRaV04xZEdsdmJuTTZZMjl1Wm1seWJTSXNJbU5zYVdWdWREcGxlR1ZqZFhScGIyNXpPbkpsWVdRaUxDSmpiR2xsYm5RNmFXNXpkSEoxYldWdWRITTZZM0psWVhSbElpd2lZMnhwWlc1ME9tbHVjM1J5ZFcxbGJuUnpPblJ2YTJWdWFYcGxJbDBzSW5OMVlpSTZJbTFsY21Ob1lXNTBMWE5rYXlKOS5nb1dmdndKSWVYVFdqT0xSRy10T2F6bHE0VjNpNC0waW9RQklqcFVqNVhiUVlKdkVQY2N4ZUMzLVZ0Q2xINWhxRW1BNHcyMTIxbkhSRjBHZnZyYWc2R29zYUxkWkJUemhqdFVfcHgxSkxlUFhFVW90OFFOSXFGYmNWY0FtTkxOTmgteUdxaGJ1UlN3d2N5S05JcmhxUy1seF9qNlhERkc0TnlIellZQk5adGNyT2hwOGVONEh4a3RKMkppWVc4aWdmNVZnckVKTXpRWjFydEN4OWtZLW4tMUJuRDJJZnp6dnhIcUd6ODRRWkhlMkU4eEVxVDBkTGdqUVdrNy1vbjZtbEtyMTZOLTM0TkxSbnN2Q1l0YUp1TThOczRHZ0h5OVJSOXlLNnliZi1sMGUxdHBOY1RPdXVYdk92YzBXbk1tZm1WOUVLdFp6cUpOcENrbjZxT3pQU3ciLCJob2xkZXJSZWZlcmVuY2UiOiJlZmZhdCIsImFtb3VudCI6eyJ2YWx1ZSI6IjEyLjUwIiwiY3VycmVuY3kiOiJFVVIifSwiZXhlY3V0aW9uIjp7ImlkIjoiMGI3YWY4MmQtYjJhOC00NWEzLTllNmItZWRlN2RiYjcyMTEwIiwic3RhdHVzIjpbeyJjb2RlIjoiY3JlYXRlZCIsInRpbWUiOiIyMDI0LTAxLTIzVDEzOjU0OjIxLjMwMzE3ODk2MloifV0sImNyZWF0ZWRBdCI6IjIwMjQtMDEtMjNUMTM6NTQ6MjEuMzAzMTc4OTYyWiIsIm1lcmNoYW50UmVmZXJlbmNlIjoib3JkZXItYzg3NDg2NzctMTZmOS00OThmLTk1ODMtY2FmYjI0MzliYjU5IiwiaG9sZGVySWQiOiJiNjFlMjQ2Zi00YzAzLTRmMjUtYjgyMS1lMDQ5NzMzNTUwMzMiLCJob2xkZXJSZWZlcmVuY2UiOiJlZmZhdCIsImFtb3VudCI6eyJ2YWx1ZSI6IjEyLjUwIiwiY3VycmVuY3kiOiJFVVIifSwid29ya2Zsb3ciOnsiY29kZSI6InBheW1lbnQtYWNjZXB0YW5jZSIsInZlcnNpb24iOjJ9LCJtZXRhIjp7IkNJVCI6dHJ1ZSwiYWxsb3dOYXRpdmUzRFMiOmZhbHNlLCJjbGllbnRDb250ZXh0Ijp7ImlwQWRkcmVzcyI6IjIxNy4xMTAuMjM5LjEzMiIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJvc1R5cGUiOiJpb3MifSwiY3VzdG9tZXIiOnsiY291bnRyeSI6eyJjb2RlIjoiREUifSwicmVmZXJlbmNlIjoiZWZmYXQifSwib3JkZXIiOnsicmVmZXJlbmNlIjoib3JkZXItYzg3NDg2NzctMTZmOS00OThmLTk1ODMtY2FmYjI0MzliYjU5In0sInZhdWx0VHlwZSI6IlBheXJhaWxzIn0sImxpbmtzIjp7InNlbGYiOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL21lcmNoYW50L3dvcmtmbG93cy9wYXltZW50LWFjY2VwdGFuY2UvZXhlY3V0aW9ucy8wYjdhZjgyZC1iMmE4LTQ1YTMtOWU2Yi1lZGU3ZGJiNzIxMTAiLCJhdXRob3JpemUiOnsibWV0aG9kIjoiUE9TVCIsImhyZWYiOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL21lcmNoYW50L3dvcmtmbG93cy9wYXltZW50LWFjY2VwdGFuY2UvZXhlY3V0aW9ucy8wYjdhZjgyZC1iMmE4LTQ1YTMtOWU2Yi1lZGU3ZGJiNzIxMTAvYXV0aG9yaXplIn0sInN0YXJ0UGF5bWVudFNlc3Npb24iOnsibWV0aG9kIjoiUE9TVCIsImhyZWYiOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL21lcmNoYW50L3dvcmtmbG93cy9wYXltZW50LWFjY2VwdGFuY2UvZXhlY3V0aW9ucy8wYjdhZjgyZC1iMmE4LTQ1YTMtOWU2Yi1lZGU3ZGJiNzIxMTAvc3RhcnRQYXltZW50U2Vzc2lvbiJ9fSwiaW5pdGlhbFJlc3VsdHMiOlt7Imh0dHBDb2RlIjoyMDAsImJvZHkiOnsibmFtZSI6Imxvb2t1cCIsImFjdGlvbklkIjoiYzcyZWEyOTYtZGYzNC00MDVjLTllYzMtODliNTczYjQ4OWUzIiwiZXhlY3V0ZWRBdCI6IjIwMjQtMDEtMjNUMTM6NTQ6MjIuMjY4ODkyMjk1WiIsImRhdGEiOnsicGF5bWVudENvbXBvc2l0aW9uT3B0aW9ucyI6W3siaW50ZWdyYXRpb25UeXBlIjoiYWR5ZW5Ecm9wSW4iLCJkZXNjcmlwdGlvbiI6IkFkeWVuIERyb3AtSW4ifSx7ImludGVncmF0aW9uVHlwZSI6ImFwaSIsInBheW1lbnRNZXRob2RDb2RlIjoiYXBwbGVQYXkiLCJkZXNjcmlwdGlvbiI6IkFwcGxlUGF5IiwiY29uZmlnIjp7InBhcmFtZXRlcnMiOnsiY291bnRyeUNvZGUiOiJERSIsIm1lcmNoYW50Q2FwYWJpbGl0aWVzIjpbInN1cHBvcnRzM0RTIiwic3VwcG9ydHNDcmVkaXQiLCJzdXBwb3J0c0RlYml0Il0sIm1lcmNoYW50SWRlbnRpZmllciI6Im1lcmNoYW50LnBheXJhaWxzLnRlc3RpbmciLCJzdXBwb3J0ZWROZXR3b3JrcyI6WyJBTUVYIiwiRElTQ09WRVIiLCJJTlRFUkFDIiwiSkNCIiwiVklTQSIsIk1BU1RFUkNBUkQiXX0sInR5cGUiOiJDQVJEIn19XX0sImxpbmtzIjp7ImV4ZWN1dGlvbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzBiN2FmODJkLWIyYTgtNDVhMy05ZTZiLWVkZTdkYmI3MjExMCIsImF1dGhvcml6ZSI6eyJtZXRob2QiOiJQT1NUIiwiaHJlZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzBiN2FmODJkLWIyYTgtNDVhMy05ZTZiLWVkZTdkYmI3MjExMC9hdXRob3JpemUifSwic3RhcnRQYXltZW50U2Vzc2lvbiI6eyJtZXRob2QiOiJQT1NUIiwiaHJlZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzBiN2FmODJkLWIyYTgtNDVhMy05ZTZiLWVkZTdkYmI3MjExMC9zdGFydFBheW1lbnRTZXNzaW9uIn19fX1dfX0=" + + let version = "1.0.0" + + let cse = PayrailsCSE.init(data: data, version: version) + + do { + try cse.tokenize( + cardNumber: "4242424242424242", + expiryMonth: "12", + expiryYear: "30") { (result: Result) in } + } catch let error as NSError { + XCTAssertEqual(error.domain, "ConfigError") + XCTAssertEqual(error.userInfo[NSLocalizedDescriptionKey] as? String, "Missing tokenization config") + } catch { + XCTFail("Unexpected error type: \(error)") + } + } + + func testCallingEncryptCarDataWithDropInConfig() { + let data = "eyJ0b2tlbiI6ImV5SmhiR2NpT2lKU1V6STFOaUlzSW10cFpDSTZJalpsTnpSbE9HUXdMVGt4TlRrdE5ERTJZeTA1TUdabUxUUmtOMk5oWldNMFptSTNOU0lzSW5SNWNDSTZJa3BYVkNKOS5leUpoZFdRaU9sc2lhSFIwY0RvdkwyeHZZMkZzYUc5emREbzRNRGd6TDJGMWRHZ2lYU3dpWlhod0lqb3hOekEyTVRJMU16QXpMQ0pvZEhSd2N6b3ZMM0JoZVhKaGFXeHpMbWx2TDJOc1lXbHRjeTlqZFhOMGIyMWZjMk52Y0dVaU9pSjdYQ0poYkd4dmQzTkJiR3hjSWpwbVlXeHpaU3hjSW1WNFpXTjFkR2x2Ymtsa1hDSTZYQ0kyWldVNE5qaGtOUzB4TlRjd0xUUXpZVEl0T1RsbFlpMWhOalZoT1RWbE5HRmlOamRjSWl4Y0ltaHZiR1JsY2xKbFptVnlaVzVqWlZ3aU9sd2laV1ptWVhSY0lpeGNJblJ2YTJWdWFYcGhkR2x2Ymtsa1hDSTZYQ0pjSWl4Y0luUjVjR1ZjSWpwY0ltTnNhV1Z1ZEZ3aWZTSXNJbWx6Y3lJNkluQmhlWEpoYVd4eklpd2lhMmxrSWpvaU5tVTNOR1U0WkRBdE9URTFPUzAwTVRaakxUa3dabVl0TkdRM1kyRmxZelJtWWpjMUlpd2ljR1Z5YldsemMybHZibk1pT2xzaVkyeHBaVzUwT21WNFpXTjFkR2x2Ym5NNllYVjBhRzl5YVhwbElpd2lZMnhwWlc1ME9tVjRaV04xZEdsdmJuTTZZMjl1Wm1seWJTSXNJbU5zYVdWdWREcGxlR1ZqZFhScGIyNXpPbkpsWVdRaUxDSmpiR2xsYm5RNmFXNXpkSEoxYldWdWRITTZZM0psWVhSbElpd2lZMnhwWlc1ME9tbHVjM1J5ZFcxbGJuUnpPblJ2YTJWdWFYcGxJbDBzSW5OMVlpSTZJbTFsY21Ob1lXNTBMWE5rYXlKOS5ob3QtbE0wNHlwUEwtWHhYZ1ljSXQ0S3h3MTVLa1RGVGdzQWI2WHJYQXp1eFlEcFk3Y0RmZHhjZC16aVlDT05zUElxUWt2M05tWkRsOEU5aFl2bTlLb3o4MFhoejd6ZFA2SENtendVbXhrMHhoN3FlWm9DV0toV0U3VjdNX2hpckFWdnFkM2F3TjQ3d1ZlMlNISTVVd1lMYmtZNW41dXBySTQtS01Qb0E4SmVaNzVvWGctQ1NnV1VDUDV5RFB6R3cxVFZfMUQ4UDZabksySHN0LTBEeVVVN1kxRlZSQ1JKRFZadUVqUi01T2Y2UzVuZDRZZTFacnNhbUZrRnVPOUs3c3RlbHNFT0NNcXpQZkpBbHR4cUE1SkxoZDhQX2tXQS1iWUtWRGJWTmtvMFU1NXlLUktnam5pUFpZTmhCN2NzOWtaNTJTMFkxMjBvVGpHN2UxUk9DUWciLCJob2xkZXJSZWZlcmVuY2UiOiJlZmZhdCIsInZhdWx0Q29uZmlndXJhdGlvbiI6eyJ2YXVsdElkIjoiIiwidmF1bHRVcmwiOiIiLCJ0b2tlbiI6IiIsInN0YXR1cyI6ImVuYWJsZWQiLCJ2YXVsdFR5cGUiOiJQYXlyYWlscyIsInByb3ZpZGVySWQiOiJkZjMwMmRiZS02ZDUzLTRkN2MtYTkzMi05NzMzYzZjMTI4NmYiLCJwcm92aWRlckNvbmZpZ0lkIjoiYjI0ZmYwMDctNzI4Yy00NTVkLWI1MWMtNTU2MTA4Y2NkZjU5IiwibGlua3MiOnsic2F2ZUluc3RydW1lbnQiOnsibWV0aG9kIjoiUE9TVCIsImhyZWYiOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL3BheW1lbnQvaW5zdHJ1bWVudHMifX0sImVuY3J5cHRpb25QdWJsaWNLZXkiOiJNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXp5SDJpUlhoeVZLUlJOaGowUU1iUFRXNC9BSUd4bk43ZjFYcURzdTBvOXoxbEJLQ2tDaVc4YklLb1JhV0ZkVVludXdHclEyQ0R3MllOMlptcktFcnhUWm5PWEpIVHk1ek1EMG1panpTUEYxZXNkUndXSy83RG5laTJkMzdFbjJoOCtsQ3NKSjJYeElIbUUyRko5Z0JLYnEwY0RtOXYwNWRJaFNlSU54NW92Y1RTQWhIRzNKTHZXTFJWcy9oT2Nubi92bE93R01Oa2RhdElzQU5vNjZDZG1rWFNRNFFCWUY4TFVTZVZmWEtBYjFDVjVqcmkzQlMyckZEb1pZME1iVTZjLzlENzRieHJjaFlEbEZFNEFhL3dRbjFKTTZ4R005YllQTStiL1F1T0NNNmJtK0Z2THZsTGZYWTBoVFhSWkpldmR6YmJFQnV4ZU4yUnhpVlMrSytsd0lEQVFBQiJ9LCJhbW91bnQiOnsidmFsdWUiOiIxMi41MCIsImN1cnJlbmN5IjoiRVVSIn0sImV4ZWN1dGlvbiI6eyJpZCI6IjZlZTg2OGQ1LTE1NzAtNDNhMi05OWViLWE2NWE5NWU0YWI2NyIsInN0YXR1cyI6W3siY29kZSI6ImNyZWF0ZWQiLCJ0aW1lIjoiMjAyNC0wMS0yNFQxODo0Njo0My4zNDMxMTYwNVoifV0sImNyZWF0ZWRBdCI6IjIwMjQtMDEtMjRUMTg6NDY6NDMuMzQzMTE2MDVaIiwibWVyY2hhbnRSZWZlcmVuY2UiOiJvcmRlci00YTc2M2VkMS05ZTVkLTRiMTYtOTQ2My03ZTA4YmJjYWMyMjQiLCJob2xkZXJJZCI6ImM0ZTlkMGQ4LTljMTQtNDhhYi1iMGY5LTdkYWRkYTcyNDZmZCIsImhvbGRlclJlZmVyZW5jZSI6ImVmZmF0IiwiYW1vdW50Ijp7InZhbHVlIjoiMTIuNTAiLCJjdXJyZW5jeSI6IkVVUiJ9LCJ3b3JrZmxvdyI6eyJjb2RlIjoicGF5bWVudC1hY2NlcHRhbmNlIiwidmVyc2lvbiI6M30sIm1ldGEiOnsiQ0lUIjp0cnVlLCJhbGxvd05hdGl2ZTNEUyI6ZmFsc2UsImNsaWVudENvbnRleHQiOnsiaXBBZGRyZXNzIjoiMjE3LjExMC4yMzkuMTMyIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsIm9zVHlwZSI6ImlvcyJ9LCJjdXN0b21lciI6eyJjb3VudHJ5Ijp7ImNvZGUiOiJERSJ9LCJyZWZlcmVuY2UiOiJlZmZhdCJ9LCJvcmRlciI6eyJyZWZlcmVuY2UiOiJvcmRlci00YTc2M2VkMS05ZTVkLTRiMTYtOTQ2My03ZTA4YmJjYWMyMjQifSwidmF1bHRUeXBlIjoiUGF5cmFpbHMifSwibGlua3MiOnsic2VsZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzZlZTg2OGQ1LTE1NzAtNDNhMi05OWViLWE2NWE5NWU0YWI2NyIsImF1dGhvcml6ZSI6eyJtZXRob2QiOiJQT1NUIiwiaHJlZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzZlZTg2OGQ1LTE1NzAtNDNhMi05OWViLWE2NWE5NWU0YWI2Ny9hdXRob3JpemUifSwic3RhcnRQYXltZW50U2Vzc2lvbiI6eyJtZXRob2QiOiJQT1NUIiwiaHJlZiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4Ni9wdWJsaWMvbWVyY2hhbnQvd29ya2Zsb3dzL3BheW1lbnQtYWNjZXB0YW5jZS9leGVjdXRpb25zLzZlZTg2OGQ1LTE1NzAtNDNhMi05OWViLWE2NWE5NWU0YWI2Ny9zdGFydFBheW1lbnRTZXNzaW9uIn19LCJpbml0aWFsUmVzdWx0cyI6W3siaHR0cENvZGUiOjIwMCwiYm9keSI6eyJuYW1lIjoibG9va3VwIiwiYWN0aW9uSWQiOiIyNTc4NGNmNy04M2NiLTQwOWYtYWE2Ny01N2RjMDgxYjliZGEiLCJleGVjdXRlZEF0IjoiMjAyNC0wMS0yNFQxODo0Njo0My40MTc1MjYwOTNaIiwiZGF0YSI6eyJwYXltZW50Q29tcG9zaXRpb25PcHRpb25zIjpbeyJpbnRlZ3JhdGlvblR5cGUiOiJhcGkiLCJwYXltZW50TWV0aG9kQ29kZSI6ImNhcmQiLCJkZXNjcmlwdGlvbiI6IkNhcmQifV19LCJsaW5rcyI6eyJleGVjdXRpb24iOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL21lcmNoYW50L3dvcmtmbG93cy9wYXltZW50LWFjY2VwdGFuY2UvZXhlY3V0aW9ucy82ZWU4NjhkNS0xNTcwLTQzYTItOTllYi1hNjVhOTVlNGFiNjciLCJhdXRob3JpemUiOnsibWV0aG9kIjoiUE9TVCIsImhyZWYiOiJodHRwOi8vbG9jYWxob3N0OjgwODYvcHVibGljL21lcmNoYW50L3dvcmtmbG93cy9wYXltZW50LWFjY2VwdGFuY2UvZXhlY3V0aW9ucy82ZWU4NjhkNS0xNTcwLTQzYTItOTllYi1hNjVhOTVlNGFiNjcvYXV0aG9yaXplIn19fX1dfX0=" + + let version = "1.0.0" + + let cse = PayrailsCSE.init(data: data, version: version) + + do { + let encryptedInstrumentDetails = try cse.encryptCardData(card: Card.self( + holderReference: "reference", + cardNumber: "4242424242424242", + expiryMonth: "12", + expiryYear: "35", + holderName: "George", + securityCode: "123" + )) + + XCTAssertNotNil(encryptedInstrumentDetails) + } catch { + XCTFail("Unexpected error type: \(error)") + } + } }