diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index 82a8252..199188d 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -470,6 +470,14 @@ class UserOperation: IOperation { /** Proximity Check Data to be passed when OTP is handed to the app */ var proximityCheck: ProximityCheck? = null + + + /** + * Enum-like reason why the status has changed. + * + * Max 32 characters are expected. Possible values depend on the backend implementation and configuration. + */ + val statusReason: String? } ``` diff --git a/library/src/androidTest/java/IntegrationTests.kt b/library/src/androidTest/java/IntegrationTests.kt index 005bbf9..f6581c6 100644 --- a/library/src/androidTest/java/IntegrationTests.kt +++ b/library/src/androidTest/java/IntegrationTests.kt @@ -324,4 +324,30 @@ class IntegrationTests { } Assert.assertNull(authorizedFuture2.get(20, TimeUnit.SECONDS)) } + + @Test + fun testOperationCanceledWithReason() { + // create regular operation + val op = IntegrationUtils.createOperation(IntegrationUtils.Companion.Factors.F_2FA) + val cancelReason = "PREARRANGED_REASON" + // cancel the operation + IntegrationUtils.cancelOperation(op.operationId, cancelReason) + + val future = CompletableFuture?>() + val auth = PowerAuthAuthentication.possessionWithPassword(pin) + ops.getHistory(auth) { result -> + result.onSuccess { future.complete(it) } + .onFailure { future.completeExceptionally(it) } + } + + val operations = future.get(20, TimeUnit.SECONDS) + Assert.assertNotNull("Operations not retrieved", operations) + if (operations == null) { + return + } + + val opRecord = operations.firstOrNull { it.operation.id == op.operationId } + Assert.assertNotNull(opRecord) + Assert.assertTrue("${opRecord?.operation?.statusReason} should be PREARRANGED_REASON", opRecord?.operation?.statusReason == "PREARRANGED_REASON") + } } diff --git a/library/src/androidTest/java/IntegrationUtils.kt b/library/src/androidTest/java/IntegrationUtils.kt index 701553b..a67d6be 100644 --- a/library/src/androidTest/java/IntegrationUtils.kt +++ b/library/src/androidTest/java/IntegrationUtils.kt @@ -190,6 +190,11 @@ class IntegrationUtils { return makeCall(opBody, "$cloudServerUrl/v2/operations") } + @Throws + fun cancelOperation(operationId: String, reason: String): StatusResponse { + return makeCall("", "$cloudServerUrl/v2/operations/$operationId?statusReason=$reason", "DELETE") + } + @Throws fun createNonPersonalizedPACOperation(factors: Factors): NonPersonalisedTOTPOperationObject { val opBody = when (factors) { diff --git a/library/src/main/java/com/wultra/android/mtokensdk/api/operation/model/UserOperation.kt b/library/src/main/java/com/wultra/android/mtokensdk/api/operation/model/UserOperation.kt index cdedb64..c0eaf95 100644 --- a/library/src/main/java/com/wultra/android/mtokensdk/api/operation/model/UserOperation.kt +++ b/library/src/main/java/com/wultra/android/mtokensdk/api/operation/model/UserOperation.kt @@ -80,7 +80,15 @@ open class UserOperation( * Proximity Check Data to be passed when OTP is handed to the app */ @SerializedName("proximityCheck") - override var proximityCheck: ProximityCheck? = null + override var proximityCheck: ProximityCheck? = null, + + /** + * Enum-like reason why the status has changed. + * + * Max 32 characters are expected. Possible values depend on the backend implementation and configuration. + */ + @SerializedName("statusReason") + val statusReason: String? ) : IOperation, ExpirableOperation /**