Skip to content

Commit 3ffe88d

Browse files
committed
Merge branch 'offline_checkouts'
2 parents f932ea4 + 4d77b70 commit 3ffe88d

File tree

7 files changed

+200
-86
lines changed

7 files changed

+200
-86
lines changed

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

Lines changed: 16 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,13 @@ public void run() {
101101
pollForResult();
102102
}
103103
};
104+
private CheckoutRetryer checkoutRetryer;
104105

105106
Checkout(Project project) {
106107
this.project = project;
107108
this.shoppingCart = project.getShoppingCart();
108109
this.checkoutApi = new CheckoutApi(project);
110+
this.checkoutRetryer = new CheckoutRetryer(project, getFallbackPaymentMethod());
109111

110112
HandlerThread handlerThread = new HandlerThread("Checkout");
111113
handlerThread.start();
@@ -206,8 +208,14 @@ public void checkout() {
206208

207209
notifyStateChanged(State.HANDSHAKING);
208210

209-
checkoutApi.createCheckoutInfo(project.getCheckedInShop(),
210-
shoppingCart.toBackendCart(),
211+
if (shop == null) {
212+
notifyStateChanged(State.NO_SHOP);
213+
return;
214+
}
215+
216+
final ShoppingCart.BackendCart backendCart = shoppingCart.toBackendCart();
217+
218+
checkoutApi.createCheckoutInfo(backendCart,
211219
clientAcceptedPaymentMethods,
212220
new CheckoutApi.CheckoutInfoResult() {
213221
@Override
@@ -252,7 +260,7 @@ public void connectionError() {
252260
if(fallback != null) {
253261
paymentMethod = fallback;
254262
priceToPay = shoppingCart.getTotalPrice();
255-
retryPostSilent();
263+
checkoutRetryer.add(backendCart);
256264
notifyStateChanged(State.WAIT_FOR_APPROVAL);
257265
} else {
258266
notifyStateChanged(State.CONNECTION_ERROR);
@@ -293,7 +301,7 @@ private void pay(final PaymentMethod paymentMethod, final PaymentCredentials pay
293301
notifyStateChanged(State.VERIFYING_PAYMENT_METHOD);
294302

295303
checkoutApi.createPaymentProcess(signedCheckoutInfo, paymentMethod, paymentCredentials,
296-
new CheckoutApi.PaymentProcessResult() {
304+
false, null, new CheckoutApi.PaymentProcessResult() {
297305
@Override
298306
public void success(CheckoutApi.CheckoutProcessResponse checkoutProcessResponse) {
299307
checkoutProcess = checkoutProcessResponse;
@@ -391,69 +399,6 @@ private boolean handleProcessResponse() {
391399
return false;
392400
}
393401

394-
395-
private void retryPostSilent() {
396-
final PaymentMethod pm = paymentMethod;
397-
398-
handler.postDelayed(new Runnable() {
399-
@Override
400-
public void run() {
401-
checkoutApi.createCheckoutInfo(shop, shoppingCart.toBackendCart(), clientAcceptedPaymentMethods,
402-
new CheckoutApi.CheckoutInfoResult() {
403-
@Override
404-
public void success(CheckoutApi.SignedCheckoutInfo signedCheckoutInfo,
405-
int onlinePrice,
406-
PaymentMethod[] availablePaymentMethods) {
407-
priceToPay = shoppingCart.getTotalPrice();
408-
409-
checkoutApi.createPaymentProcess(signedCheckoutInfo, pm, null,
410-
new CheckoutApi.PaymentProcessResult() {
411-
@Override
412-
public void success(CheckoutApi.CheckoutProcessResponse checkoutProcessResponse) {
413-
414-
}
415-
416-
@Override
417-
public void aborted() {
418-
419-
}
420-
421-
@Override
422-
public void error() {
423-
424-
}
425-
});
426-
}
427-
428-
@Override
429-
public void noShop() {
430-
431-
}
432-
433-
@Override
434-
public void invalidProducts(List<Product> products) {
435-
436-
}
437-
438-
@Override
439-
public void noAvailablePaymentMethod() {
440-
441-
}
442-
443-
@Override
444-
public void unknownError() {
445-
446-
}
447-
448-
@Override
449-
public void connectionError() {
450-
451-
}
452-
});
453-
}
454-
}, 2000);
455-
}
456-
457402
private void approve() {
458403
if (state != State.PAYMENT_APPROVED) {
459404
Logger.d("Payment approved");
@@ -568,6 +513,10 @@ public String getQRCodePOSContent() {
568513
return null;
569514
}
570515

516+
public void processPendingCheckouts() {
517+
518+
}
519+
571520
/**
572521
* Gets the current state of the Checkout.
573522
* <p>

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010

1111
import java.io.IOException;
1212
import java.util.ArrayList;
13+
import java.util.Date;
1314
import java.util.List;
1415
import java.util.Map;
1516

1617
import io.snabble.sdk.payment.PaymentCredentials;
18+
import io.snabble.sdk.utils.DateUtils;
1719
import io.snabble.sdk.utils.GsonHolder;
1820
import io.snabble.sdk.utils.JsonCallback;
1921
import io.snabble.sdk.utils.Logger;
@@ -120,6 +122,8 @@ public static class CheckoutProcessRequest {
120122
public SignedCheckoutInfo signedCheckoutInfo;
121123
public PaymentMethod paymentMethod;
122124
public PaymentInformation paymentInformation;
125+
public String finalizedAt;
126+
public Boolean processedOffline;
123127
}
124128

125129
public enum PaymentState {
@@ -215,8 +219,7 @@ public void onFailure(Call call, IOException e) {
215219
});
216220
}
217221

218-
public void createCheckoutInfo(final Shop shop,
219-
final ShoppingCart.BackendCart backendCart,
222+
public void createCheckoutInfo(final ShoppingCart.BackendCart backendCart,
220223
final PaymentMethod[] clientAcceptedPaymentMethods,
221224
final CheckoutInfoResult checkoutInfoResult) {
222225
String checkoutUrl = project.getCheckoutUrl();
@@ -226,12 +229,6 @@ public void createCheckoutInfo(final Shop shop,
226229
return;
227230
}
228231

229-
if (shop == null) {
230-
Logger.e("Could not checkout, no shop selected");
231-
checkoutInfoResult.noShop();
232-
return;
233-
}
234-
235232
final Request request = new Request.Builder()
236233
.url(Snabble.getInstance().absoluteUrl(checkoutUrl))
237234
.post(RequestBody.create(JSON, GsonHolder.get().toJson(backendCart)))
@@ -256,11 +253,6 @@ public void success(SignedCheckoutInfo signedCheckoutInfo) {
256253
price = project.getShoppingCart().getTotalPrice();
257254
}
258255

259-
if (price != project.getShoppingCart().getTotalPrice()) {
260-
Logger.w("Warning local price is different from remotely calculated price! (Local: "
261-
+ project.getShoppingCart().getTotalPrice() + ", Remote: " + price + ")");
262-
}
263-
264256
PaymentMethod[] availablePaymentMethods = signedCheckoutInfo.getAvailablePaymentMethods(clientAcceptedPaymentMethods);
265257
if (availablePaymentMethods != null && availablePaymentMethods.length > 0) {
266258
checkoutInfoResult.success(signedCheckoutInfo, price, availablePaymentMethods);
@@ -355,11 +347,21 @@ public void error(Throwable t) {
355347
public void createPaymentProcess(final SignedCheckoutInfo signedCheckoutInfo,
356348
final PaymentMethod paymentMethod,
357349
final PaymentCredentials paymentCredentials,
350+
final boolean processedOffline,
351+
final Date finalizedAt,
358352
final PaymentProcessResult paymentProcessResult) {
359353
CheckoutProcessRequest checkoutProcessRequest = new CheckoutProcessRequest();
360354
checkoutProcessRequest.paymentMethod = paymentMethod;
361355
checkoutProcessRequest.signedCheckoutInfo = signedCheckoutInfo;
362356

357+
if (processedOffline) {
358+
checkoutProcessRequest.processedOffline = true;
359+
360+
if (finalizedAt != null) {
361+
checkoutProcessRequest.finalizedAt = DateUtils.toRFC3339(finalizedAt);
362+
}
363+
}
364+
363365
if (paymentCredentials != null) {
364366
checkoutProcessRequest.paymentInformation = new PaymentInformation();
365367

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package io.snabble.sdk;
2+
3+
import android.content.Context;
4+
import android.content.SharedPreferences;
5+
import android.os.Handler;
6+
import android.os.Looper;
7+
8+
import com.google.gson.reflect.TypeToken;
9+
10+
import java.util.Date;
11+
import java.util.List;
12+
import java.util.concurrent.CopyOnWriteArrayList;
13+
import java.util.concurrent.CountDownLatch;
14+
15+
import io.snabble.sdk.utils.GsonHolder;
16+
import io.snabble.sdk.utils.Logger;
17+
18+
class CheckoutRetryer {
19+
private class SavedCart {
20+
final ShoppingCart.BackendCart backendCart;
21+
final Date finalizedAt;
22+
23+
SavedCart(ShoppingCart.BackendCart backendCart, Date finalizedAt) {
24+
this.backendCart = backendCart;
25+
this.finalizedAt = finalizedAt;
26+
}
27+
}
28+
29+
private Handler handler;
30+
private PaymentMethod fallbackPaymentMethod;
31+
private SharedPreferences sharedPreferences;
32+
private CheckoutApi checkoutApi;
33+
private CopyOnWriteArrayList<SavedCart> savedCarts;
34+
private CountDownLatch countDownLatch;
35+
36+
CheckoutRetryer(Project project, PaymentMethod fallbackPaymentMethod) {
37+
Context context = Snabble.getInstance().getApplication();
38+
this.sharedPreferences = context.getSharedPreferences("snabble_saved_checkouts_" + project.getId(), Context.MODE_PRIVATE);
39+
this.checkoutApi = new CheckoutApi(project);
40+
this.fallbackPaymentMethod = fallbackPaymentMethod;
41+
this.handler = new Handler(Looper.getMainLooper());
42+
43+
String json = sharedPreferences.getString("saved_carts", null);
44+
if (json != null) {
45+
TypeToken typeToken = new TypeToken<CopyOnWriteArrayList<SavedCart>>() {};
46+
savedCarts = GsonHolder.get().fromJson(json, typeToken.getType());
47+
} else {
48+
savedCarts = new CopyOnWriteArrayList<>();
49+
}
50+
51+
processSavedCheckouts();
52+
}
53+
54+
public void add(ShoppingCart.BackendCart backendCart) {
55+
savedCarts.add(new SavedCart(backendCart, new Date()));
56+
save();
57+
}
58+
59+
private void save() {
60+
String json = GsonHolder.get().toJson(savedCarts);
61+
62+
sharedPreferences.edit()
63+
.putString("saved_carts", json)
64+
.apply();
65+
}
66+
67+
public void processSavedCheckouts() {
68+
handler.post(new Runnable() {
69+
@Override
70+
public void run() {
71+
if (countDownLatch != null && countDownLatch.getCount() > 0) {
72+
return;
73+
}
74+
75+
countDownLatch = new CountDownLatch(savedCarts.size());
76+
77+
for (final SavedCart savedCart : savedCarts) {
78+
checkoutApi.createCheckoutInfo(savedCart.backendCart, null, new CheckoutApi.CheckoutInfoResult() {
79+
@Override
80+
public void success(CheckoutApi.SignedCheckoutInfo signedCheckoutInfo, int onlinePrice, PaymentMethod[] availablePaymentMethods) {
81+
checkoutApi.createPaymentProcess(signedCheckoutInfo, fallbackPaymentMethod, null,
82+
true, savedCart.finalizedAt, new CheckoutApi.PaymentProcessResult() {
83+
@Override
84+
public void success(CheckoutApi.CheckoutProcessResponse checkoutProcessResponse) {
85+
Logger.d("Successfully resend checkout " + savedCart.backendCart.session);
86+
removeSavedCart(savedCart);
87+
countDownLatch.countDown();
88+
}
89+
90+
@Override
91+
public void aborted() {
92+
countDownLatch.countDown();
93+
}
94+
95+
@Override
96+
public void error() {
97+
countDownLatch.countDown();
98+
}
99+
});
100+
}
101+
102+
@Override
103+
public void noShop() {
104+
countDownLatch.countDown();
105+
}
106+
107+
@Override
108+
public void invalidProducts(List<Product> products) {
109+
countDownLatch.countDown();
110+
}
111+
112+
@Override
113+
public void noAvailablePaymentMethod() {
114+
countDownLatch.countDown();
115+
}
116+
117+
@Override
118+
public void unknownError() {
119+
countDownLatch.countDown();
120+
}
121+
122+
@Override
123+
public void connectionError() {
124+
countDownLatch.countDown();
125+
}
126+
});
127+
}
128+
}
129+
});
130+
}
131+
132+
private void removeSavedCart(final SavedCart savedCart) {
133+
handler.post(new Runnable() {
134+
@Override
135+
public void run() {
136+
savedCarts.remove(savedCart);
137+
save();
138+
}
139+
});
140+
}
141+
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.TimeZone;
2020

2121
import io.snabble.sdk.codes.ScannedCode;
22+
import io.snabble.sdk.utils.DateUtils;
2223
import io.snabble.sdk.utils.GsonHolder;
2324
import io.snabble.sdk.utils.Logger;
2425
import io.snabble.sdk.utils.Utils;
@@ -36,16 +37,12 @@ public class Events {
3637
private Shop shop;
3738

3839
private Handler handler = new Handler(Looper.getMainLooper());
39-
private SimpleDateFormat simpleDateFormat;
4040
private boolean hasSentSessionStart = false;
4141

4242
@SuppressLint("SimpleDateFormat")
4343
public Events(Project project) {
4444
this.project = project;
4545

46-
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
47-
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
48-
4946
project.getShoppingCart().addListener(new ShoppingCart.SimpleShoppingCartListener() {
5047
@Override
5148
public void onChanged(ShoppingCart cart) {
@@ -158,7 +155,7 @@ private <T extends Payload> void post(final T payload, boolean debounce) {
158155
event.appId = Snabble.getInstance().getClientId();
159156
event.project = project.getId();
160157
event.shopId = shop.getId();
161-
event.timestamp = simpleDateFormat.format(new Date());
158+
event.timestamp = DateUtils.toRFC3339(new Date());
162159
event.payload = GsonHolder.get().toJsonTree(payload);
163160

164161
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void update() {
4040
handler.post(new Runnable() {
4141
@Override
4242
public void run() {
43-
checkoutApi.createCheckoutInfo(project.getCheckedInShop(), cart.toBackendCart(), null, new CheckoutApi.CheckoutInfoResult() {
43+
checkoutApi.createCheckoutInfo(cart.toBackendCart(), null, new CheckoutApi.CheckoutInfoResult() {
4444
@Override
4545
public void success(final CheckoutApi.SignedCheckoutInfo signedCheckoutInfo, int onlinePrice, PaymentMethod[] availablePaymentMethods) {
4646
handler.post(new Runnable() {

0 commit comments

Comments
 (0)