Skip to content

Commit 37a4558

Browse files
authored
Merge pull request #32 from snabble/feature/coupon_violations
Add coupon violations
2 parents 1fdf013 + cd375ec commit 37a4558

File tree

14 files changed

+173
-9
lines changed

14 files changed

+173
-9
lines changed

core/src/main/java/io/snabble/sdk/ShoppingCart.java

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static io.snabble.sdk.Unit.PIECE;
44
import static io.snabble.sdk.Unit.PRICE;
55

6+
import androidx.annotation.NonNull;
67
import androidx.annotation.Nullable;
78
import androidx.annotation.RestrictTo;
89

@@ -11,24 +12,25 @@
1112
import java.math.BigDecimal;
1213
import java.util.ArrayList;
1314
import java.util.Collections;
15+
import java.util.Iterator;
1416
import java.util.List;
1517
import java.util.Objects;
1618
import java.util.UUID;
1719
import java.util.concurrent.CopyOnWriteArrayList;
1820
import java.util.concurrent.TimeUnit;
1921

2022
import io.snabble.sdk.auth.AppUser;
21-
import io.snabble.sdk.checkout.DefaultCheckoutApi;
2223
import io.snabble.sdk.checkout.LineItem;
2324
import io.snabble.sdk.checkout.LineItemType;
2425
import io.snabble.sdk.checkout.PaymentMethodInfo;
2526
import io.snabble.sdk.checkout.PriceModifier;
27+
import io.snabble.sdk.checkout.Violation;
2628
import io.snabble.sdk.codes.ScannedCode;
2729
import io.snabble.sdk.codes.templates.CodeTemplate;
2830
import io.snabble.sdk.utils.Dispatch;
2931
import io.snabble.sdk.utils.GsonHolder;
3032

31-
public class ShoppingCart {
33+
public class ShoppingCart implements Iterable<ShoppingCart.Item> {
3234
public enum ItemType {
3335
PRODUCT,
3436
LINE_ITEM,
@@ -58,6 +60,7 @@ public String getValue() {
5860
private long lastModificationTime;
5961
private List<Item> oldItems;
6062
private List<Item> items = new ArrayList<>();
63+
private final List<ViolationNotification> violationNotifications = new ArrayList<>();
6164
private int modCount = 0;
6265
private int addCount = 0;
6366
private Integer onlineTotalPrice;
@@ -209,6 +212,13 @@ public Item get(int index) {
209212
return items.get(index);
210213
}
211214

215+
@NonNull
216+
@Override
217+
public Iterator<Item> iterator() {
218+
return items.iterator();
219+
}
220+
221+
212222
public Item getExistingMergeableProduct(Product product) {
213223
if (product == null) {
214224
return null;
@@ -590,6 +600,8 @@ public static class Item {
590600
private transient ShoppingCart cart;
591601
private boolean isManualCouponApplied;
592602
private Coupon coupon;
603+
// The local generated UUID of a coupon which which will be used by the backend
604+
private String backendCouponId;
593605

594606
protected Item() {
595607
// for gson
@@ -600,6 +612,7 @@ private Item(ShoppingCart cart, Coupon coupon, ScannedCode scannedCode) {
600612
this.cart = cart;
601613
this.scannedCode = scannedCode;
602614
this.coupon = coupon;
615+
this.backendCouponId = UUID.randomUUID().toString();
603616
}
604617

605618
private Item(ShoppingCart cart, Product product, ScannedCode scannedCode) {
@@ -1155,15 +1168,15 @@ public BackendCart toBackendCart() {
11551168

11561169
if (cartItem.coupon != null) {
11571170
BackendCartItem couponItem = new BackendCartItem();
1158-
couponItem.id = UUID.randomUUID().toString();
1171+
couponItem.id = cartItem.coupon.getId();
11591172
couponItem.refersTo = item.id;
11601173
couponItem.amount = 1;
11611174
couponItem.couponID = cartItem.coupon.getId();
11621175
items.add(couponItem);
11631176
}
11641177
} else if (cartItem.getType() == ItemType.COUPON) {
11651178
BackendCartItem item = new BackendCartItem();
1166-
item.id = UUID.randomUUID().toString();
1179+
item.id = cartItem.backendCouponId;
11671180
item.amount = 1;
11681181

11691182
ScannedCode scannedCode = cartItem.getScannedCode();
@@ -1181,6 +1194,47 @@ public BackendCart toBackendCart() {
11811194
return backendCart;
11821195
}
11831196

1197+
@RestrictTo(RestrictTo.Scope.LIBRARY)
1198+
void resolveViolations(List<Violation> violations) {
1199+
for (Violation violation : violations) {
1200+
for (int i = items.size() - 1; i >= 0; i--) {
1201+
if (items.get(i).coupon != null && items.get(i).backendCouponId.equals(violation.getRefersTo())) {
1202+
Item item = items.get(i);
1203+
items.remove(item);
1204+
boolean found = false;
1205+
for(ViolationNotification notification: violationNotifications) {
1206+
if(notification.getRefersTo().equals(violation.getRefersTo())) {
1207+
found = true;
1208+
break;
1209+
}
1210+
}
1211+
if(!found) {
1212+
violationNotifications.add(new ViolationNotification(
1213+
item.coupon.getName(),
1214+
violation.getRefersTo(),
1215+
violation.getType(),
1216+
violation.getMessage()
1217+
));
1218+
}
1219+
}
1220+
}
1221+
}
1222+
notifyViolations();
1223+
}
1224+
1225+
/**
1226+
* Remove the handled ViolationNotifications.
1227+
* @param violations the handled ViolationNotifications.
1228+
*/
1229+
public void removeViolationNotification(List<ViolationNotification> violations) {
1230+
violationNotifications.removeAll(violations);
1231+
}
1232+
1233+
@NonNull
1234+
public List<ViolationNotification> getViolationNotifications() {
1235+
return violationNotifications;
1236+
}
1237+
11841238
/**
11851239
* Adds a {@link ShoppingCartListener} to the list of listeners if it does not already exist.
11861240
*
@@ -1190,6 +1244,9 @@ public void addListener(ShoppingCartListener listener) {
11901244
if (!listeners.contains(listener)) {
11911245
listeners.add(listener);
11921246
}
1247+
if (!violationNotifications.isEmpty()) {
1248+
listener.onViolationDetected(violationNotifications);
1249+
}
11931250
}
11941251

11951252
/**
@@ -1222,6 +1279,8 @@ public interface ShoppingCartListener {
12221279
void onOnlinePaymentLimitReached(ShoppingCart list);
12231280

12241281
void onTaxationChanged(ShoppingCart list, Taxation taxation);
1282+
1283+
void onViolationDetected(@NonNull List<ViolationNotification> violations);
12251284
}
12261285

12271286
public static abstract class SimpleShoppingCartListener implements ShoppingCartListener {
@@ -1263,14 +1322,13 @@ public void onTaxationChanged(ShoppingCart list, Taxation taxation) {
12631322
}
12641323

12651324
@Override
1266-
public void onCheckoutLimitReached(ShoppingCart list) {
1267-
1268-
}
1325+
public void onCheckoutLimitReached(ShoppingCart list) {}
12691326

12701327
@Override
1271-
public void onOnlinePaymentLimitReached(ShoppingCart list) {
1328+
public void onOnlinePaymentLimitReached(ShoppingCart list) {}
12721329

1273-
}
1330+
@Override
1331+
public void onViolationDetected(List<ViolationNotification> violations) {}
12741332
}
12751333

12761334
private void notifyItemAdded(final ShoppingCart list, final Item item) {
@@ -1348,6 +1406,15 @@ void notifyOnlinePaymentLimitReached(final ShoppingCart list) {
13481406
}
13491407
});
13501408
}
1409+
1410+
public void notifyViolations() {
1411+
Dispatch.mainThread(() -> {
1412+
for (ShoppingCartListener listener : listeners) {
1413+
listener.onViolationDetected(violationNotifications);
1414+
}
1415+
});
1416+
}
1417+
13511418
/**
13521419
* Notifies all {@link #listeners} that the shopping list was cleared of all entries.
13531420
*

core/src/main/java/io/snabble/sdk/ShoppingCartUpdater.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ private void commitCartUpdate(int modCount, SignedCheckoutInfo signedCheckoutInf
142142

143143
CheckoutInfo checkoutInfo = GsonHolder.get().fromJson(signedCheckoutInfo.getCheckoutInfo(), CheckoutInfo.class);
144144

145+
if (checkoutInfo.getViolations().size() > 0) {
146+
cart.resolveViolations(checkoutInfo.getViolations());
147+
}
148+
145149
Set<String> referrerIds = new HashSet<>();
146150
Set<String> requiredIds = new HashSet<>();
147151

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.snabble.sdk
2+
3+
/**
4+
* A notification that a violation accrued.
5+
*/
6+
data class ViolationNotification(
7+
val name: String?,
8+
val refersTo: String?,
9+
val type: String? = null,
10+
val fallbackMessage: String? = null,
11+
)

core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ data class SignedCheckoutInfo(
207207
data class CheckoutInfo(
208208
val price: Price? = null,
209209
val lineItems: List<LineItem> = emptyList(),
210+
val violations: List<Violation> = emptyList(),
211+
)
212+
213+
data class Violation(
214+
val type: String? = null,
215+
val refersTo: String? = null,
216+
val message: String? = null,
210217
)
211218

212219
data class LineItem(

ui/src/main/java/io/snabble/sdk/ui/cart/ShoppingCartView.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
import io.snabble.sdk.ShoppingCart;
3636
import io.snabble.sdk.Snabble;
3737
import io.snabble.sdk.Unit;
38+
import io.snabble.sdk.ViolationNotification;
3839
import io.snabble.sdk.ui.GestureHandler;
3940
import io.snabble.sdk.ui.R;
4041
import io.snabble.sdk.ui.SnabbleUI;
42+
import io.snabble.sdk.ui.checkout.ViolationNotificationUtils;
4143
import io.snabble.sdk.ui.telemetry.Telemetry;
4244
import io.snabble.sdk.ui.utils.I18nUtils;
4345
import io.snabble.sdk.ui.utils.SnackbarUtils;
@@ -102,6 +104,11 @@ public void onOnlinePaymentLimitReached(ShoppingCart list) {
102104
.create();
103105
alertDialog.show();
104106
}
107+
108+
@Override
109+
public void onViolationDetected(@NonNull List<ViolationNotification> violations) {
110+
ViolationNotificationUtils.showNotificationOnce(violations, getContext(), cart);
111+
}
105112
};
106113

107114
public ShoppingCartView(Context context) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@file:JvmName("ViolationNotificationUtils")
2+
package io.snabble.sdk.ui.checkout
3+
4+
import android.content.Context
5+
import androidx.appcompat.app.AlertDialog
6+
import io.snabble.sdk.ShoppingCart
7+
import io.snabble.sdk.ViolationNotification
8+
import io.snabble.sdk.ui.R
9+
import io.snabble.sdk.ui.utils.I18nUtils
10+
11+
/**
12+
* Build a localized error message from a list of `ViolationNotification`.
13+
*/
14+
fun List<ViolationNotification>.getMessage(context: Context) = joinToString("\n") {
15+
val res = context.resources
16+
when (it.type) {
17+
"coupon_invalid" -> res.getString(I18nUtils.getIdentifier(res, R.string.Snabble_Violations_couponInvalid), it.name)
18+
"coupon_currently_not_valid" -> res.getString(I18nUtils.getIdentifier(res, R.string.Snabble_Violations_couponCurrentlyNotValid), it.name)
19+
"coupon_already_voided" -> res.getString(I18nUtils.getIdentifier(res, R.string.Snabble_Violations_couponAlreadyVoided), it.name)
20+
else -> it.fallbackMessage.orEmpty()
21+
}
22+
}
23+
24+
/**
25+
* Show a dialog with all current violations. The implementation will make sure that the dialog won't be shown twice.
26+
*/
27+
fun List<ViolationNotification>.showNotificationOnce(context: Context, cart: ShoppingCart) {
28+
val message: String = getMessage(context)
29+
cart.removeViolationNotification(this)
30+
if (cart.violationNotifications.isNotEmpty()){
31+
AlertDialog.Builder(context)
32+
.setTitle(R.string.Snabble_Violations_title)
33+
.setMessage(message)
34+
.setPositiveButton(R.string.Snabble_OK, null)
35+
.show()
36+
}
37+
}

ui/src/main/java/io/snabble/sdk/ui/scanner/SelfScanningView.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import android.widget.FrameLayout;
2323
import android.widget.LinearLayout;
2424

25+
import androidx.annotation.NonNull;
2526
import androidx.appcompat.app.AlertDialog;
2627
import androidx.core.app.ActivityCompat;
2728
import androidx.core.content.res.ResourcesCompat;
@@ -40,9 +41,11 @@
4041
import io.snabble.sdk.Shop;
4142
import io.snabble.sdk.ShoppingCart;
4243
import io.snabble.sdk.Snabble;
44+
import io.snabble.sdk.ViolationNotification;
4345
import io.snabble.sdk.codes.ScannedCode;
4446
import io.snabble.sdk.ui.R;
4547
import io.snabble.sdk.ui.SnabbleUI;
48+
import io.snabble.sdk.ui.checkout.ViolationNotificationUtils;
4649
import io.snabble.sdk.ui.telemetry.Telemetry;
4750
import io.snabble.sdk.ui.utils.DelayedProgressDialog;
4851
import io.snabble.sdk.ui.utils.I18nUtils;
@@ -549,6 +552,11 @@ public void onOnlinePaymentLimitReached(ShoppingCart list) {
549552
project.getPriceFormatter().format(project.getMaxOnlinePaymentLimit())));
550553

551554
}
555+
556+
@Override
557+
public void onViolationDetected(@NonNull List<ViolationNotification> violations) {
558+
ViolationNotificationUtils.showNotificationOnce(violations, getContext(), shoppingCart);
559+
}
552560
};
553561

554562
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

ui/src/main/res/values-de/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<string name="Snabble.paydirekt.gotoWebsite">Zu paydirekt.de</string>
6565
<string name="Snabble.paydirekt.payNow">Jetzt mit paydirekt bezahlen</string>
6666
<string name="Snabble.paydirekt.savedAuthorization">Du hast die Snabble-App erfolgreich für paydirekt autorisiert. Um dies rückgängig zu machen, musst du dich bei deinem paydirekt-Konto auf der Webseite anmelden. Möchtest du paydirekt nicht mehr als Bezahlmethode angezeigt bekommen, kannst du sie hier einfach entfernen.</string>
67+
<string name="Snabble.paydirekt.title">paydirekt</string>
6768
<!-- SECTION: SnabbleAndroid -->
6869
<string name="Snabble.Payment.aborted">Der Bezahlvorgang wurde abgebrochen</string>
6970
<string name="Snabble.Payment.add">Bezahlverfahren hinzufügen</string>
@@ -312,5 +313,9 @@
312313
<string name="Snabble.Taxation.pleaseChoose">Bitte wählen</string>
313314
<string name="Snabble.TWINT.payNow">Jetzt mit TWINT bezahlen</string>
314315
<string name="Snabble.undo">Rückgängig</string>
316+
<string name="Snabble.Violations.couponAlreadyVoided">Der Coupon „%s“ wurde bereits benutzt und wird aus dem Warenkorb entfernt</string>
317+
<string name="Snabble.Violations.couponCurrentlyNotValid">Der Coupon „%s“ ist akuell ungültig und wird aus dem Warenkorb entfernt</string>
318+
<string name="Snabble.Violations.couponInvalid">Der Coupon „%s“ ist ungültig und wird aus dem Warenkorb entfernt</string>
319+
<string name="Snabble.Violations.title">Coupon ungültig</string>
315320
<string name="Snabble.Yes">Ja</string>
316321
</resources>

ui/src/main/res/values-en/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<string name="Snabble.paydirekt.gotoWebsite">Go to paydirekt.de</string>
6565
<string name="Snabble.paydirekt.payNow">Pay now using paydirekt</string>
6666
<string name="Snabble.paydirekt.savedAuthorization">You\'ve successfully authorized Snabble for paydirekt. To remove this authorization, you need to log in to your paydirekt account. If you do not want to use this payment method anymore, you can remove it here.</string>
67+
<string name="Snabble.paydirekt.title">paydirekt</string>
6768
<!-- SECTION: SnabbleAndroid -->
6869
<string name="Snabble.Payment.aborted">Payment process was cancelled</string>
6970
<string name="Snabble.Payment.add">Add payment method</string>
@@ -312,5 +313,9 @@
312313
<string name="Snabble.Taxation.pleaseChoose">Please choose</string>
313314
<string name="Snabble.TWINT.payNow">Pay now using TWINT</string>
314315
<string name="Snabble.undo">Undo</string>
316+
<string name="Snabble.Violations.couponAlreadyVoided">The coupon „%s“ is already redeemed and will be removed from cart</string>
317+
<string name="Snabble.Violations.couponCurrentlyNotValid">The coupon „%s“ is currently not valid and will be removed from cart</string>
318+
<string name="Snabble.Violations.couponInvalid">The coupon „%s“ is invalid and will be removed from cart</string>
319+
<string name="Snabble.Violations.title">Coupon invalid</string>
315320
<string name="Snabble.Yes">Yes</string>
316321
</resources>

ui/src/main/res/values-fr/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<string name="Snabble.paydirekt.gotoWebsite">Vers paydirekt.de</string>
6565
<string name="Snabble.paydirekt.payNow">Payer maintenant avec paydirekt</string>
6666
<string name="Snabble.paydirekt.savedAuthorization">Tu as autorisé avec succès l’application Snabble pour paydirekt. Pour l’annuler, tu devras te connecter à ton compte paydirekt sur le site Web. Si tu ne souhaites plus que paydirekt s’affiche comme mode de paiement, tu peux simplement le supprimer ici.</string>
67+
<string name="Snabble.paydirekt.title">paydirekt</string>
6768
<!-- SECTION: SnabbleAndroid -->
6869
<string name="Snabble.Payment.aborted">Le processus de paiement a été annulé</string>
6970
<string name="Snabble.Payment.add">Ajouter une procédure de paiement</string>

0 commit comments

Comments
 (0)