Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Amount and Conversion to new backend scheme #142

Merged
merged 4 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,26 @@ import Foundation
/// Amount attribute is 1 row in operation, that represents "Payment Amount"
public class WMTOperationAttributeAmount: WMTOperationAttribute {

/// Payment amount
///
/// Amount might not be precise (due to floating point conversion during deserialization from json)
/// use amountFormatted property instead when available
public let amount: Decimal

/// Currency
public let currency: String

/// Formatted amount for presentation.
///
/// This property will be properly formatted based on the response language.
/// For example when amount is 100 and the acceptLanguage is "cs" for czech,
/// the amountFormatted will be "100,00".
public let amountFormatted: String?
public let amountFormatted: String

/// Formatted currency to the locale based on acceptLanguage
///
/// For example when the currency is CZK, this property will be "Kč"
public let currencyFormatted: String?
public let currencyFormatted: String

/// Payment amount
///
/// Amount might not be precise (due to floating point conversion during deserialization from json)
/// use amountFormatted property instead when available
public let amount: Decimal?

/// Currency
public let currency: String?

/// Formatted value and currency to the locale based on acceptLanguage
///
Expand All @@ -52,24 +52,26 @@ public class WMTOperationAttributeAmount: WMTOperationAttribute {
case amount, amountFormatted, currency, currencyFormatted, valueFormatted
}

public init(label: AttributeLabel, amount: Decimal, currency: String, amountFormatted: String?, currencyFormatted: String?, valueFormatted: String?) {
self.amount = amount
self.currency = currency
public init(label: AttributeLabel, amountFormatted: String, currencyFormatted: String, valueFormatted: String?, amount: Decimal?, currency: String? ) {
self.amountFormatted = amountFormatted
self.currencyFormatted = currencyFormatted
self.valueFormatted = valueFormatted
self.amount = amount
self.currency = currency
super.init(type: .amount, label: label)
}

public required init(from decoder: Decoder) throws {

let c = try decoder.container(keyedBy: Keys.self)

amount = (try c.decode(Double.self, forKey: .amount) as NSNumber).decimalValue
currency = try c.decode(String.self, forKey: .currency)
amountFormatted = try? c.decode(String.self, forKey: .amountFormatted)
currencyFormatted = try? c.decode(String.self, forKey: .currencyFormatted)
valueFormatted = try? c.decode(String.self, forKey: .valueFormatted)
// For backward compatibility with legacy implementation, where the `amountFormatted` and `currencyFormatted` values might not be present,
// we directly decode from `amount` and `currency`.
amountFormatted = try c.decodeIfPresent(String.self, forKey: .amountFormatted) ?? c.decode(Decimal.self, forKey: .amount).description
currencyFormatted = try c.decodeIfPresent(String.self, forKey: .currencyFormatted) ?? c.decode(String.self, forKey: .currency)
valueFormatted = try c.decodeIfPresent(String.self, forKey: .valueFormatted)
amount = try c.decodeIfPresent(Decimal.self, forKey: .amount)
currency = try c.decodeIfPresent(String.self, forKey: .currency)
try super.init(from: decoder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,27 @@ public class WMTOperationAttributeAmountConversion: WMTOperationAttribute {

public struct Money {

/// Payment amount
///
/// Amount might not be precise (due to floating point conversion during deserialization from json)
/// use amountFormatted property instead when available
public let amount: Decimal

/// Currency
public let currency: String

/// Formatted amount for presentation.
///
/// This property will be properly formatted based on the response language.
/// For example when amount is 100 and the acceptLanguage is "cs" for czech,
/// the amountFormatted will be "100,00".
public let amountFormatted: String?
public let amountFormatted: String

/// Formatted currency to the locale based on acceptLanguage
///
/// For example when the currency is CZK, this property will be "Kč"
public let currencyFormatted: String?
public let currencyFormatted: String

/// Payment amount
///
/// Amount might not be precise (due to floating point conversion during deserialization from json)
/// use amountFormatted property instead when available
public let amount: Decimal?

/// Currency
public let currency: String?

/// Formatted currency and amount to the locale based on acceptLanguage
///
/// Both amount and currency are formatted, String will show e.g. "€" in front of the amount
Expand Down Expand Up @@ -75,19 +75,23 @@ public class WMTOperationAttributeAmountConversion: WMTOperationAttribute {
let c = try decoder.container(keyedBy: Keys.self)

self.dynamic = try c.decode(Bool.self, forKey: .dynamic)
// For backward compatibility with legacy implementation, where the `sourceAmountFormatted` and `sourceCurrencyFormatted` values might not be present,
// we directly decode from `sourceAmount` and `sourceCurrency`.
self.source = .init(
amount: try c.decode(Decimal.self, forKey: .sourceAmount),
currency: try c.decode(String.self, forKey: .sourceCurrency),
amountFormatted: try? c.decode(String.self, forKey: .sourceAmountFormatted),
currencyFormatted: try? c.decode(String.self, forKey: .sourceCurrencyFormatted),
valueFormatted: try? c.decode(String.self, forKey: .sourceValueFormatted)
amountFormatted: try c.decodeIfPresent(String.self, forKey: .sourceAmountFormatted) ?? c.decode(Decimal.self, forKey: .sourceAmount).description,
currencyFormatted: try c.decodeIfPresent(String.self, forKey: .sourceCurrencyFormatted) ?? c.decode(String.self, forKey: .sourceCurrency),
amount: try c.decodeIfPresent(Decimal.self, forKey: .sourceAmount),
currency: try c.decodeIfPresent(String.self, forKey: .sourceCurrency),
valueFormatted: try c.decodeIfPresent(String.self, forKey: .sourceValueFormatted)
)
// For backward compatibility with legacy implementation, where the `targetAmountFormatted` and `targetCurrencyFormatted` values might not be present,
// we directly decode from `targetAmount` and `targetCurrency`.
self.target = .init(
amount: try c.decode(Decimal.self, forKey: .targetAmount),
currency: try c.decode(String.self, forKey: .targetCurrency),
amountFormatted: try? c.decode(String.self, forKey: .targetAmountFormatted),
currencyFormatted: try? c.decode(String.self, forKey: .targetCurrencyFormatted),
valueFormatted: try? c.decode(String.self, forKey: .targetValueFormatted)
amountFormatted: try c.decodeIfPresent(String.self, forKey: .targetAmountFormatted) ?? c.decode(Decimal.self, forKey: .targetAmount).description,
currencyFormatted: try c.decodeIfPresent(String.self, forKey: .targetCurrencyFormatted) ?? c.decode(String.self, forKey: .targetCurrency),
amount: try c.decodeIfPresent(Decimal.self, forKey: .targetAmount),
currency: try c.decodeIfPresent(String.self, forKey: .targetCurrency),
valueFormatted: try c.decodeIfPresent(String.self, forKey: .targetValueFormatted)
)

try super.init(from: decoder)
Expand Down
72 changes: 72 additions & 0 deletions WultraMobileTokenSDKTests/NetworkingObjectsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,78 @@ class NetworkingObjectsTests: XCTestCase {
XCTAssert(op2.allowedSignatureType.signatureFactors.count == 1 && op2.allowedSignatureType.signatureFactors.contains(.possessionKnowledge))
}

func testOnlyAmountAndConversionAttributesLegacyBackend() {
let json = """
{"status":"OK", "currentTimestamp":"2023-02-10T12:30:42+0000", "responseObject":[{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3", "name":"authorize_payment", "data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017", "operationCreated":"2018-08-08T12:30:42+0000", "operationExpires":"2018-08-08T12:35:43+0000", "allowedSignatureType": {"type":"2FA", "variants": ["possession_knowledge", "possession_biometry"]}, "formData": {"title":"Potvrzení platby", "message":"Dobrý den,prosíme o potvrzení následující platby:", "attributes": [{"type":"AMOUNT", "id":"operation.amount", "label":"Částka", "amount":965165234082.23, "currency":"CZK"}, { "type": "AMOUNT_CONVERSION", "id": "operation.conversion", "label": "Conversion", "dynamic": true, "sourceAmount": 1.26, "sourceCurrency": "ETC", "targetAmount": 1710.98, "targetCurrency": "USD"}]}}]}
"""

guard let result = try? jsonDecoder.decode(WPNResponseArray<WMTUserOperation>.self, from: json.data(using: .utf8)!) else {
XCTFail("Failed to parse JSON data")
return
}

guard let amountAttr = result.responseObject?[0].formData.attributes[0] as? WMTOperationAttributeAmount else {
XCTFail("amount attribute not recognized")
return
}
XCTAssertEqual(Decimal(string: "965165234082.23"), amountAttr.amount)
XCTAssertEqual("CZK", amountAttr.currency)
XCTAssertEqual("965165234082.23", amountAttr.amountFormatted)
XCTAssertEqual("CZK", amountAttr.currencyFormatted)


guard let conversionAttr = result.responseObject?[0].formData.attributes[1] as? WMTOperationAttributeAmountConversion else {
XCTFail("conversion attribute not recognized")
return
}

XCTAssertEqual(Decimal(string: "1.26"), conversionAttr.source.amount)
XCTAssertEqual("ETC", conversionAttr.source.currency)
XCTAssertEqual(Decimal(string: "1710.98"), conversionAttr.target.amount)
XCTAssertEqual("USD", conversionAttr.target.currency)

XCTAssertEqual("1.26", conversionAttr.source.amountFormatted)
XCTAssertEqual("ETC", conversionAttr.source.currencyFormatted)
XCTAssertEqual("1710.98", conversionAttr.target.amountFormatted)
XCTAssertEqual("USD", conversionAttr.target.currencyFormatted)
}

func testAmountAndConversionAttributesOnlyFormattedValues() {
let json = """
{"status":"OK", "currentTimestamp":"2023-02-10T12:30:42+0000", "responseObject":[{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3", "name":"authorize_payment", "data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017", "operationCreated":"2018-08-08T12:30:42+0000", "operationExpires":"2018-08-08T12:35:43+0000", "allowedSignatureType": {"type":"2FA", "variants": ["possession_knowledge", "possession_biometry"]}, "formData": {"title":"Potvrzení platby", "message":"Dobrý den,prosíme o potvrzení následující platby:", "attributes": [{"type":"AMOUNT", "id":"operation.amount", "label":"Částka", "amountFormatted":"965165234082.23", "currencyFormatted":"CZK"}, { "type": "AMOUNT_CONVERSION", "id": "operation.conversion", "label": "Conversion", "dynamic": true, "sourceAmountFormatted": "1.26", "sourceCurrencyFormatted": "ETC", "targetAmountFormatted": "1710.98", "targetCurrencyFormatted": "USD"}]}}]}
""".trimmingCharacters(in: .whitespacesAndNewlines)

guard let result = try? jsonDecoder.decode(WPNResponseArray<WMTUserOperation>.self, from: json.data(using: .utf8)!) else {
XCTFail("Failed to parse JSON data")
return
}

guard let amountAttr = result.responseObject?[0].formData.attributes[0] as? WMTOperationAttributeAmount else {
XCTFail("amount attribute not recognized")
return
}

XCTAssertNil(amountAttr.amount)
XCTAssertNil(amountAttr.currency)
XCTAssertEqual("965165234082.23", amountAttr.amountFormatted)
XCTAssertEqual("CZK", amountAttr.currencyFormatted)

guard let conversionAttr = result.responseObject?[0].formData.attributes[1] as? WMTOperationAttributeAmountConversion else {
XCTFail("conversion attribute not recognized")
return
}

XCTAssertNil(conversionAttr.source.amount)
XCTAssertNil(conversionAttr.source.currency)
XCTAssertNil(conversionAttr.target.amount)
XCTAssertNil(conversionAttr.target.currency)

XCTAssertEqual("1.26", conversionAttr.source.amountFormatted)
XCTAssertEqual("ETC", conversionAttr.source.currencyFormatted)
XCTAssertEqual("1710.98", conversionAttr.target.amountFormatted)
XCTAssertEqual("USD", conversionAttr.target.currencyFormatted)
}

func testErrorResponse() {

let response = """
Expand Down
Loading