Skip to content

Commit

Permalink
Updated Amount and Conversion to new backend scheme (#142)
Browse files Browse the repository at this point in the history
* #139: update attribute amount to correspond with new backend scheme

* #139: update conversion amount to correspond with new backend scheme

* Fix lint

* Replace try? with decodeIfPresent and Double for Decimal decoding & add tests
  • Loading branch information
Hopsaheysa authored Jan 12, 2024
1 parent 6a5942a commit a8ef3b9
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 40 deletions.
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

0 comments on commit a8ef3b9

Please sign in to comment.