Skip to content

Commit

Permalink
Merge branch 'feature/SP-59-Stöd-för-att-visa-betalmenyn' into 'featu…
Browse files Browse the repository at this point in the history
…re/native-payments'

Resolve SP-59 "Feature/ stöd för att visa betalmenyn"

See merge request swedbank-pay/swedbank-pay-sdk-ios!4
  • Loading branch information
mbalsiger committed Jun 28, 2024
2 parents cdd19c3 + 339871c commit f4316cf
Show file tree
Hide file tree
Showing 17 changed files with 1,204 additions and 417 deletions.
54 changes: 36 additions & 18 deletions SwedbankPaySDK.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions SwedbankPaySDK/Classes/Api/Helpers/CustomDateDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// Copyright 2024 Swedbank AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

class CustomDateDecoder: JSONDecoder {
let dateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(identifier: "UTC")
return formatter
}()

override init() {
super.init()

dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)

if let date = self.dateFormatter.date(from: dateStr) {
return date
}

return Date.distantPast
})
}
}
1 change: 1 addition & 0 deletions SwedbankPaySDK/Classes/Api/Models/IntegrationTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ enum IntegrationTaskRel: Codable, Equatable, Hashable {

struct ExpectationModel: Codable, Hashable {
let name: String?
let type: String?
let value: String?
}
43 changes: 37 additions & 6 deletions SwedbankPaySDK/Classes/Api/Models/MethodBaseModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

public enum MethodBaseModel: Codable, Equatable, Hashable {
case swish(prefills: [SwedbankPaySDK.SwishMethodPrefillModel]?, operations: [OperationOutputModel]?)
case creditCard(prefills: [SwedbankPaySDK.CreditCardMethodPrefillModel]?, operations: [OperationOutputModel]?, cardBrands: [String]?)
Expand Down Expand Up @@ -95,10 +97,18 @@ extension SwedbankPaySDK {
/// Swish native payment with a list of prefills
case swish(prefills: [SwishMethodPrefillModel]?)

var name: String {
case creditCard(prefills: [CreditCardMethodPrefillModel]?)

case webBased(identifier: String)

var identifier: String {
switch self {
case .swish:
return "Swish"
case .creditCard:
return "CreditCard"
case .webBased(identifier: let identifier):
return identifier
}
}
}
Expand All @@ -109,11 +119,32 @@ extension SwedbankPaySDK {
public let msisdn: String
}

/// Prefill information for Credit Card payment.
public struct CreditCardMethodPrefillModel: Codable, Hashable {
let rank: Int32?
let paymentToken: String?
let cardBrand: String?
let maskedPan: String?
let expiryDate: String?
public let rank: Int32
public let paymentToken: String
public let cardBrand: String
public let maskedPan: String
public let expiryDate: Date

public var expiryMonth: String {
let formatter = DateFormatter()
formatter.dateFormat = "MM"
formatter.timeZone = TimeZone(identifier: "UTC")

return formatter.string(from: expiryDate)
}

public var expiryYear: String {
let formatter = DateFormatter()
formatter.dateFormat = "YY"
formatter.timeZone = TimeZone(identifier: "UTC")

return formatter.string(from: expiryDate)
}

public var expiryString: String {
return expiryMonth + "/" + expiryYear
}
}
}
3 changes: 3 additions & 0 deletions SwedbankPaySDK/Classes/Api/Models/OperationOutputModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ enum OperationRel: Codable, Equatable, Hashable {
case acknowledgeFailedAttempt
case abortPayment
case eventLogging
case viewPayment

case unknown(String)

Expand All @@ -60,6 +61,7 @@ enum OperationRel: Codable, Equatable, Hashable {
case Self.acknowledgeFailedAttempt.rawValue: self = .acknowledgeFailedAttempt
case Self.abortPayment.rawValue: self = .abortPayment
case Self.eventLogging.rawValue: self = .eventLogging
case Self.viewPayment.rawValue: self = .viewPayment
default: self = .unknown(type)
}
}
Expand All @@ -81,6 +83,7 @@ enum OperationRel: Codable, Equatable, Hashable {
case .acknowledgeFailedAttempt: "acknowledge-failed-attempt"
case .abortPayment: "abort-payment"
case .eventLogging: "event-logging"
case .viewPayment: "view-payment"
case .unknown(let value): value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ extension SwedbankPaySDK {
/// Instrument with needed values to make a payment attempt.
public enum PaymentAttemptInstrument {
case swish(msisdn: String?)
case creditCard(paymentToken: String)
case creditCard(prefill: CreditCardMethodPrefillModel)

var name: String {
var identifier: String {
switch self {
case .swish:
return "Swish"
Expand Down
11 changes: 11 additions & 0 deletions SwedbankPaySDK/Classes/Api/Models/PaymentSessionModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

struct PaymentSessionModel: Codable, Hashable {
let culture: String?
let methods: [MethodBaseModel]?
let urls: UrlsModel?
}

extension PaymentSessionModel {
Expand All @@ -35,3 +38,11 @@ extension PaymentSessionModel {
return allOperations
}
}

struct UrlsModel: Codable, Hashable {
let completeUrl: URL?
let cancelUrl: URL?
let paymentUrl: URL?
let hostUrls: [URL]?
let termsOfServiceUrl: URL?
}
3 changes: 3 additions & 0 deletions SwedbankPaySDK/Classes/Api/SwedbankPayAPIConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ struct SwedbankPayAPIConstants {

static var requestTimeoutInterval = 10.0
static var sessionTimeoutInterval = 20.0
static var creditCardTimoutInterval = 30.0

static var notificationUrl = "https://fake.payex.com/notification"
}

private enum HTTPHeaderField: String {
Expand Down
92 changes: 80 additions & 12 deletions SwedbankPaySDK/Classes/Api/SwedbankPayAPIEnpointRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ import UIKit

protocol EndpointRouterProtocol {
var body: [String: Any?]? { get }
var requestTimeoutInterval: TimeInterval { get }
var sessionTimeoutInterval: TimeInterval { get }
}

struct SwedbankPayAPIEnpointRouter: EndpointRouterProtocol {
let model: OperationOutputModel
let culture: String?
let instrument: SwedbankPaySDK.PaymentAttemptInstrument?
let methodCompletionIndicator: String?
let cRes: String?

let sessionStartTimestamp: Date

var body: [String: Any?]? {
switch model.rel {
case .expandMethod:
return ["instrumentName": instrument?.name]
return ["instrumentName": instrument?.identifier]
case .startPaymentAttempt:
switch instrument {
case .swish(let msisdn):
Expand All @@ -41,9 +46,12 @@ struct SwedbankPayAPIEnpointRouter: EndpointRouterProtocol {
"screenWidth": String(Int32(UIScreen.main.nativeBounds.width)),
"screenColorDepth": String(24)]
]
case .creditCard(let paymentToken):
case .creditCard(let prefill):
return ["culture": culture,
"paymentToken": paymentToken,
"paymentToken": prefill.paymentToken,
"cardNumber": prefill.maskedPan,
"cardExpiryMonth": prefill.expiryMonth,
"cardExpiryYear": prefill.expiryYear,
"client": ["userAgent": SwedbankPaySDK.VersionReporter.userAgent,
"ipAddress": NetworkStatusProvider.getAddress(for: .wifi) ?? NetworkStatusProvider.getAddress(for: .cellular) ?? "",
"screenHeight": String(Int32(UIScreen.main.nativeBounds.height)),
Expand Down Expand Up @@ -74,10 +82,63 @@ struct SwedbankPayAPIEnpointRouter: EndpointRouterProtocol {
"service": ["name": "SwedbankPaySDK-iOS",
"version": SwedbankPaySDK.VersionReporter.currentVersion]
]
case .createAuthentication:
return ["methodCompletionIndicator": methodCompletionIndicator ?? "N",
"notificationUrl": SwedbankPayAPIConstants.notificationUrl,
"requestWindowSize": "FULLSCREEN",
"client": ["userAgent": SwedbankPaySDK.VersionReporter.userAgent,
"ipAddress": NetworkStatusProvider.getAddress(for: .wifi) ?? NetworkStatusProvider.getAddress(for: .cellular) ?? "",
"screenHeight": String(Int32(UIScreen.main.nativeBounds.height)),
"screenWidth": String(Int32(UIScreen.main.nativeBounds.width)),
"screenColorDepth": String(24)],
"browser": ["acceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"languageHeader": Locale.current.identifier,
"timeZoneOffset": TimeZone.current.offsetFromGMT(),
"javascriptEnabled": true]
]
case .completeAuthentication:
return ["cRes": cRes ?? "",
"client": ["userAgent": SwedbankPaySDK.VersionReporter.userAgent,
"ipAddress": NetworkStatusProvider.getAddress(for: .wifi) ?? NetworkStatusProvider.getAddress(for: .cellular) ?? ""],
]
default:
return nil
}
}

var requestTimeoutInterval: TimeInterval {
switch model.rel {
case .startPaymentAttempt:
switch instrument {
case .creditCard:
return SwedbankPayAPIConstants.creditCardTimoutInterval
default:
return SwedbankPayAPIConstants.requestTimeoutInterval
}
case .createAuthentication,
.completeAuthentication:
return SwedbankPayAPIConstants.creditCardTimoutInterval
default:
return SwedbankPayAPIConstants.requestTimeoutInterval
}
}

var sessionTimeoutInterval: TimeInterval {
switch model.rel {
case .startPaymentAttempt:
switch instrument {
case .creditCard:
return SwedbankPayAPIConstants.creditCardTimoutInterval
default:
return SwedbankPayAPIConstants.sessionTimeoutInterval
}
case .createAuthentication,
.completeAuthentication:
return SwedbankPayAPIConstants.creditCardTimoutInterval
default:
return SwedbankPayAPIConstants.sessionTimeoutInterval
}
}
}

extension SwedbankPayAPIEnpointRouter {
Expand All @@ -104,7 +165,7 @@ extension SwedbankPayAPIEnpointRouter {
let decodedData: T

do {
decodedData = try JSONDecoder().decode(T.self, from: data)
decodedData = try CustomDateDecoder().decode(T.self, from: data)
} catch {
throw error
}
Expand All @@ -131,6 +192,7 @@ extension SwedbankPayAPIEnpointRouter {
var request = URLRequest(url: url)
request.httpMethod = model.method
request.allHTTPHeaderFields = SwedbankPayAPIConstants.commonHeaders
request.timeoutInterval = requestTimeoutInterval

if let body = body, let jsonData = try? JSONSerialization.data(withJSONObject: body) {
request.httpBody = jsonData
Expand All @@ -151,14 +213,14 @@ extension SwedbankPayAPIEnpointRouter {
}

BeaconService.shared.log(type: .httpRequest(duration: Int32((Date().timeIntervalSince(requestStartTimestamp) * 1000.0).rounded()),
requestUrl: model.href ?? "",
method: model.method ?? "",
responseStatusCode: responseStatusCode,
values: values))

guard let data, let response = response as? HTTPURLResponse, !(500...599 ~= response.statusCode) else {
guard Date().timeIntervalSince(requestStartTimestamp) < SwedbankPayAPIConstants.requestTimeoutInterval &&
Date().timeIntervalSince(sessionStartTimestamp) < SwedbankPayAPIConstants.sessionTimeoutInterval else {
requestUrl: model.href ?? "",
method: model.method ?? "",
responseStatusCode: responseStatusCode,
values: values))

guard let response = response as? HTTPURLResponse, !(500...599 ~= response.statusCode) else {
guard Date().timeIntervalSince(requestStartTimestamp) < requestTimeoutInterval &&
Date().timeIntervalSince(sessionStartTimestamp) < sessionTimeoutInterval else {
handler(.failure(error ?? SwedbankPayAPIError.unknown))
return
}
Expand All @@ -172,6 +234,12 @@ extension SwedbankPayAPIEnpointRouter {
return
}

guard let data, 200...204 ~= response.statusCode else {
handler(.failure(error ?? SwedbankPayAPIError.unknown))

return
}

handler(.success(data))
}.resume()
}
Expand Down
2 changes: 1 addition & 1 deletion SwedbankPaySDK/Classes/Beacon/BeaconEndpointRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protocol BeaconEndpointRouterProtocol {
var body: [String: Any?]? { get }
}

struct BeaconEndpointRouter: EndpointRouterProtocol {
struct BeaconEndpointRouter: BeaconEndpointRouterProtocol {
let href: String?
let beacon: Beacon

Expand Down
Loading

0 comments on commit f4316cf

Please sign in to comment.