Skip to content
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ All notable changes to this project will be documented in this file.

## UNRELEASED
### Added
* ui: Add UI for deposit return vouchers (APPS-1643)
* core: Handle invalid items (APPS-2039)
* ui/core: Integrate new states for deposit return vouchers into the checkout process and handle them
### Changed
* ui: Change button message for a total price of zero and price text color for a negative total price (APPS-1939)
* core: Handle only a set of pre defined violations (APPS-2049)
### Removed
* ui/core: Remove everything related to the old deposit return voucher feature
* core: Remove handling an empty list of payment methods in the checkout info as error (APPS-2049)
### Fixed

## [0.80.3]
Expand Down
9 changes: 2 additions & 7 deletions core/src/main/java/io/snabble/sdk/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,7 @@ public enum Type {
* A product that needs to be user weighed. The price from {@link Product#getListPrice()}
* is a base price of 1000g
*/
UserWeighed(2),

/**
* A product that is used for deposit return voucher's
*/
DepositReturnVoucher(3);
UserWeighed(2);

private final int databaseValue;

Expand Down Expand Up @@ -656,4 +651,4 @@ public Product build() {
return product;
}
}
}
}
8 changes: 2 additions & 6 deletions core/src/main/java/io/snabble/sdk/ProductApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ private enum ApiProductType {
@SerializedName("weighable")
WEIGHABLE,
@SerializedName("deposit")
DEPOSIT,
@SerializedName("depositReturnVoucher")
DEPOSIT_RETURN_VOUCHER
DEPOSIT
}

private static class ApiScannableCode {
Expand Down Expand Up @@ -281,8 +279,6 @@ private Product toProduct(ApiProduct apiProduct) {
} else {
builder.setType(Product.Type.PreWeighed);
}
} else if (apiProduct.productType == ApiProductType.DEPOSIT_RETURN_VOUCHER) {
builder.setType(Product.Type.DepositReturnVoucher);
} else {
builder.setType(Product.Type.Article);
}
Expand All @@ -306,4 +302,4 @@ private Product toProduct(ApiProduct apiProduct) {

return builder.build();
}
}
}
3 changes: 0 additions & 3 deletions core/src/main/java/io/snabble/sdk/ProductDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,6 @@ public Cursor searchByFoldedName(String searchString, CancellationSignal cancell
return productQuery("JOIN searchByName ns ON ns.sku = p.sku " +
"WHERE ns.foldedName MATCH ? " +
"AND p.weighing != " + Product.Type.PreWeighed.getDatabaseValue() + " " +
"AND p.weighing != " + Product.Type.DepositReturnVoucher.getDatabaseValue() + " " +
"AND p.isDeposit = 0 " +
"AND availability != 2 " +
"LIMIT 100", new String[]{
Expand Down Expand Up @@ -1439,8 +1438,6 @@ public Cursor searchByCode(String searchString, CancellationSignal cancellationS

sb.append(") AND p.weighing != ");
sb.append(Product.Type.PreWeighed.getDatabaseValue());
sb.append(" AND p.weighing != ");
sb.append(Product.Type.DepositReturnVoucher.getDatabaseValue());
sb.append(" AND p.isDeposit = 0 ");
sb.append(" AND availability != 2");

Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/io/snabble/sdk/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.snabble.sdk.auth.SnabbleAuthorizationInterceptor
import io.snabble.sdk.checkout.Checkout
import io.snabble.sdk.codes.templates.CodeTemplate
import io.snabble.sdk.codes.templates.PriceOverrideTemplate
import io.snabble.sdk.codes.templates.depositReturnVoucher.DepositReturnVoucherProvider
import io.snabble.sdk.coupons.Coupon
import io.snabble.sdk.coupons.CouponSource
import io.snabble.sdk.coupons.Coupons
Expand Down Expand Up @@ -193,6 +194,13 @@ class Project internal constructor(
var codeTemplates = emptyList<CodeTemplate>()
private set

/**
* List of providers for deposit return vouchers.
* Each provider contains an id and a list of code templates used for parsing return voucher specific barcodes.
*/
var depositReturnVoucherProviders = emptyList<DepositReturnVoucherProvider>()
private set

/**
* List of code templates that are used when supplying an existing Product with a different
* barcode which contains a reduced price
Expand Down Expand Up @@ -468,6 +476,13 @@ class Project internal constructor(
}
this.codeTemplates = codeTemplates

val dvrProvider: JsonElement? = jsonObject["depositReturnVoucherProviders"]

if (dvrProvider?.isJsonArray == true) {
depositReturnVoucherProviders =
dvrProvider.asJsonArray.mapNotNull { drv -> DepositReturnVoucherProvider.fromJsonElement(drv) }
}

val priceOverrideTemplates = mutableListOf<PriceOverrideTemplate>()
jsonObject["priceOverrideCodes"]?.asJsonArray?.forEach {
val priceOverride = it.asJsonObject
Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/io/snabble/sdk/ViolationNotification.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.snabble.sdk

import io.snabble.sdk.checkout.ViolationType

/**
* A notification that a violation occurred
*/
Expand All @@ -9,9 +11,9 @@ data class ViolationNotification(
/** The local generated uuid of the affected item **/
val refersTo: String?,
/** The type of the violation **/
val type: String? = null,
val type: ViolationType? = null,
/** Non-localized message of the backend which sould be displayed
* if no localized message is available
*/
val fallbackMessage: String? = null,
)
)
25 changes: 21 additions & 4 deletions core/src/main/java/io/snabble/sdk/checkout/Checkout.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ class Checkout @JvmOverloads constructor(
persistentState.save()
}

var invalidItems: List<ShoppingCart.Item>?
get() = persistentState.invalidItems
private set(value) {
persistentState.invalidItems = value
persistentState.save()
}

/**
* List of coupons that were redeemed during this checkout
*/
Expand Down Expand Up @@ -303,12 +310,13 @@ class Checkout @JvmOverloads constructor(
notifyStateChanged(CheckoutState.INVALID_PRODUCTS)
}

override fun onNoAvailablePaymentMethodFound() {
notifyStateChanged(CheckoutState.NO_PAYMENT_METHOD_AVAILABLE)
override fun onInvalidItems(itemIds: List<String>) {
invalidItems = shoppingCart.filterNotNull().filter { it.id in itemIds }
notifyStateChanged(CheckoutState.INVALID_ITEMS)
}

override fun onInvalidDepositReturnVoucher() {
notifyStateChanged(CheckoutState.CONNECTION_ERROR)
override fun onNoAvailablePaymentMethodFound() {
notifyStateChanged(CheckoutState.NO_PAYMENT_METHOD_AVAILABLE)
}

override fun onUnknownError() {
Expand Down Expand Up @@ -445,6 +453,15 @@ class Checkout @JvmOverloads constructor(
notifyStateChanged(CheckoutState.PAYMENT_ABORTED)
return
}
val hasAnyFailedDrvRedemptions =
checkoutProcess?.depositReturnVouchers
?.any { it.state == DepositReturnVoucherState.REDEEMING_FAILED }
?: false

if (hasAnyFailedDrvRedemptions) {
notifyStateChanged(CheckoutState.DEPOSIT_RETURN_REDEMPTION_FAILED)
return
}

Logger.d("Polling for approval state...")
Logger.d("RoutingTarget = $routingTarget")
Expand Down
60 changes: 52 additions & 8 deletions core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import io.snabble.sdk.payment.PaymentCredentials
import io.snabble.sdk.Product
import io.snabble.sdk.coupons.Coupon
import io.snabble.sdk.FulfillmentState
import io.snabble.sdk.PaymentMethod
import io.snabble.sdk.Product
import io.snabble.sdk.coupons.Coupon
import io.snabble.sdk.payment.PaymentCredentials
import io.snabble.sdk.shoppingcart.data.cart.BackendCart
import java.io.Serializable
import java.lang.Exception
import java.util.*
import java.util.Date

/**
* Interface for the snabble Checkout API
Expand Down Expand Up @@ -90,8 +89,8 @@ interface CheckoutInfoResult {

fun onNoShopFound()
fun onInvalidProducts(products: List<Product>)
fun onInvalidItems(itemIds: List<String>)
fun onNoAvailablePaymentMethodFound()
fun onInvalidDepositReturnVoucher()
fun onUnknownError()
fun onConnectionError()
}
Expand All @@ -111,7 +110,9 @@ enum class LineItemType {
@SerializedName("default") DEFAULT,
@SerializedName("deposit") DEPOSIT,
@SerializedName("discount") DISCOUNT,
@SerializedName("coupon") COUPON
@SerializedName("coupon") COUPON,
@SerializedName("depositReturnVoucher") DEPOSIT_RETURN_VOUCHER,
@SerializedName("depositReturn") DEPOSIT_RETURN
}

enum class CheckState {
Expand Down Expand Up @@ -213,11 +214,28 @@ data class CheckoutInfo(
)

data class Violation(
val type: String? = null,
val type: ViolationType? = null,
val refersTo: String? = null,
val message: String? = null,
)

enum class ViolationType {
@SerializedName("deposit_return_voucher_already_redeemed")
DEPOSIT_RETURN_ALREADY_REDEEMED,

@SerializedName("deposit_return_voucher_duplicate")
DEPOSIT_RETURN_DUPLICATED,

@SerializedName("coupon_already_voided")
COUPON_ALREADY_VOIDED,

@SerializedName("coupon_currently_not_valid")
COUPON_CURRENTLY_NOT_VALID,

@SerializedName("coupon_invalid")
COUPON_INVALID,
}

data class LineItem(
val id: String? = null,
val amount: Int = 0,
Expand Down Expand Up @@ -317,11 +335,37 @@ data class Fulfillment(
get() = links?.get("self")?.href
}

data class DepositReturnVoucher(
@SerializedName("refersTo")
val refersTo: String,
@SerializedName("state")
val state: DepositReturnVoucherState
)

enum class DepositReturnVoucherState {
@SerializedName("pending")
PENDING,

@SerializedName("redeemed")
REDEEMED,

@SerializedName("redeemingFailed")
REDEEMING_FAILED,

@SerializedName("rolledback")
ROLLED_BACK,

@SerializedName("rollbackFailed")
ROLLBACK_FAILED
}

data class CheckoutProcessResponse(
val links: Map<String, Href>? = null,
val checks: List<Check> = emptyList(),
@SerializedName("orderID")
val orderId: String? = null,
@SerializedName("depositReturnVouchers")
val depositReturnVouchers: List<DepositReturnVoucher>? = null,
val aborted: Boolean = false,
val paymentMethod: PaymentMethod? = null,
val paymentInformation: PaymentInformation? = null,
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/io/snabble/sdk/checkout/CheckoutRetryer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ internal class CheckoutRetryer(project: Project, fallbackPaymentMethod: PaymentM
fail()
}

override fun onNoAvailablePaymentMethodFound() {
override fun onInvalidItems(itemIds: List<String>) {
fail()
}

override fun onInvalidDepositReturnVoucher() {
override fun onNoAvailablePaymentMethodFound() {
fail()
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/io/snabble/sdk/checkout/CheckoutState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ enum class CheckoutState {
*/
INVALID_PRODUCTS,

/**
* Invalid items detected. For example if an item can't be found
*/
INVALID_ITEMS,

/**
* No payment method available
*/
Expand All @@ -129,4 +134,9 @@ enum class CheckoutState {
* will not be communicated
*/
PAYMENT_TRANSFERRED,

/**
* One of the deposit return vouchers added couldn't be redeemed
*/
DEPOSIT_RETURN_REDEMPTION_FAILED
}
49 changes: 22 additions & 27 deletions core/src/main/java/io/snabble/sdk/checkout/DefaultCheckoutApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,45 +100,40 @@ class DefaultCheckoutApi(private val project: Project,
?: shoppingCart.totalPrice

val availablePaymentMethods = signedCheckoutInfo.getAvailablePaymentMethods()
if (availablePaymentMethods.isNotEmpty()) {
checkoutInfoResult?.onSuccess(signedCheckoutInfo, price, availablePaymentMethods)
} else {
checkoutInfoResult?.onConnectionError()
}
checkoutInfoResult?.onSuccess(signedCheckoutInfo, price, availablePaymentMethods)
}

override fun failure(obj: JsonObject) {
try {
val error = obj["error"].asJsonObject
val type = error["type"].asString
val error: JsonObject? = obj["error"].asJsonObject
val type: String? = error?.get("type")?.asString
when (type) {
"invalid_cart_item" -> {
val invalidSkus: MutableList<String> = ArrayList()
val arr = error["details"].asJsonArray

arr.forEach {
val sku = it.asJsonObject["sku"].asString
invalidSkus.add(sku)
val invalidSkus = error["details"].asJsonArray
.mapNotNull { it.asJsonObject["sku"].asString?.ifBlank { null } }

val invalidProducts = shoppingCart
.mapNotNull { it?.product }
.filter { invalidSkus.contains(it.sku) }

if (invalidProducts.isNotEmpty()) {
Logger.e("Invalid products found")
checkoutInfoResult?.onInvalidProducts(invalidProducts)
} else {
Logger.e("Invalid items found")
val invalidIds = error["details"].asJsonArray
.mapNotNull { it.asJsonObject["id"].asString }
checkoutInfoResult?.onInvalidItems(invalidIds)
}

val invalidProducts = mutableListOf<Product>()
for (i in 0..shoppingCart.size()) {
val product = shoppingCart[i].product
if (product != null) {
if (invalidSkus.contains(product.sku)) {
invalidProducts.add(product)
}
}
}
Logger.e("Invalid products")
checkoutInfoResult?.onInvalidProducts(invalidProducts)
}

"no_available_method" -> checkoutInfoResult?.onNoAvailablePaymentMethodFound()
"bad_shop_id", "shop_not_found" -> checkoutInfoResult?.onNoShopFound()
"invalid_deposit_return_voucher" -> checkoutInfoResult?.onInvalidDepositReturnVoucher()
else -> checkoutInfoResult?.onUnknownError()
}
} catch (e: Exception) {
} catch (e: IllegalStateException) {
error(e)
} catch (e: UnsupportedOperationException) {
error(e)
}
}
Expand Down
Loading
Loading