Skip to content

Commit 84ef0ac

Browse files
committed
support terminal handover + product resolver key listener
1 parent 9ed4dfd commit 84ef0ac

File tree

16 files changed

+199
-6
lines changed

16 files changed

+199
-6
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [0.17.3]
5+
6+
### Added
7+
- Added support for terminal handover
8+
- Added support for adding payment origins over terminal payments
9+
- Added OnKeyListener to PaymentResolver
10+
411
## [0.17.2]
512

613
### Changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ allprojects {
2323
}
2424

2525
project.ext {
26-
sdkVersion='0.17.2'
26+
sdkVersion='0.17.3'
2727
versionCode=1
2828

2929
compileSdkVersion=29

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public enum State {
4343
* Payment was approved and is currently processing.
4444
*/
4545
PAYMENT_PROCESSING,
46+
/**
47+
* Happens when a user want to save his transaction details when paying over a terminal.
48+
*
49+
* To continue call {@link #continuePaymentProcess()}.
50+
*/
51+
REQUEST_ADD_PAYMENT_ORIGIN,
4652
/**
4753
* The payment was approved. We are done.
4854
*/
@@ -81,6 +87,16 @@ public enum State {
8187
NO_SHOP,
8288
}
8389

90+
public class PaymentOrigin {
91+
public final String name;
92+
public final String iban;
93+
94+
PaymentOrigin(String name, String iban) {
95+
this.name = name;
96+
this.iban = iban;
97+
}
98+
}
99+
84100
private Project project;
85101
private CheckoutApi checkoutApi;
86102
private ShoppingCart shoppingCart;
@@ -102,6 +118,8 @@ public enum State {
102118
private PaymentMethod[] clientAcceptedPaymentMethods;
103119
private Shop shop;
104120
private List<Product> invalidProducts;
121+
private CheckoutApi.PaymentResult paymentResult;
122+
private boolean paymentResultHandled;
105123

106124
private Runnable pollRunnable = new Runnable() {
107125
@Override
@@ -408,6 +426,7 @@ public void success(CheckoutApi.CheckoutProcessResponse checkoutProcessResponse)
408426
checkoutProcess = checkoutProcessResponse;
409427

410428
if (handleProcessResponse()) {
429+
Logger.d("stop polling");
411430
handler.removeCallbacks(pollRunnable);
412431
}
413432
}
@@ -419,7 +438,9 @@ public void error() {
419438
}
420439
});
421440

422-
if (state == State.WAIT_FOR_APPROVAL || state == State.PAYMENT_APPROVED) {
441+
if (state == State.WAIT_FOR_APPROVAL
442+
|| state == State.PAYMENT_APPROVED
443+
|| state == State.REQUEST_ADD_PAYMENT_ORIGIN) {
423444
scheduleNextPoll();
424445
}
425446
}
@@ -431,6 +452,21 @@ private boolean handleProcessResponse() {
431452
return true;
432453
}
433454

455+
if (checkoutProcess.paymentResult != null
456+
&& checkoutProcess.paymentResult.ehiTechDaysName != null
457+
&& checkoutProcess.paymentResult.ehiTechDaysIban != null) {
458+
if (!paymentResultHandled) {
459+
if (paymentResult == null) {
460+
Logger.d("Request adding payment origin");
461+
paymentResult = checkoutProcess.paymentResult;
462+
paymentResultHandled = false;
463+
notifyStateChanged(State.REQUEST_ADD_PAYMENT_ORIGIN);
464+
}
465+
466+
return false;
467+
}
468+
}
469+
434470
if (checkoutProcess.paymentState == CheckoutApi.PaymentState.SUCCESSFUL) {
435471
approve();
436472
return true;
@@ -469,6 +505,14 @@ public void approveOfflineMethod() {
469505
}
470506
}
471507

508+
/** Continues the payment process after it stopped when requiring user interaction,
509+
* for example after REQUEST_ADD_PAYMENT_ORIGIN **/
510+
public void continuePaymentProcess() {
511+
Logger.d("Continue payment process");
512+
paymentResultHandled = true;
513+
paymentResult = null;
514+
}
515+
472516
public String getOrderId() {
473517
return checkoutProcess != null ? checkoutProcess.orderId : null;
474518
}
@@ -516,6 +560,14 @@ public PaymentMethod getSelectedPaymentMethod() {
516560
return paymentMethod;
517561
}
518562

563+
public PaymentMethod getPaymentMethodForPayment() {
564+
if (checkoutProcess != null) {
565+
return checkoutProcess.paymentMethod;
566+
}
567+
568+
return null;
569+
}
570+
519571
public int getPriceToPay() {
520572
return priceToPay;
521573
}
@@ -524,6 +576,17 @@ public List<Product> getInvalidProducts() {
524576
return invalidProducts;
525577
}
526578

579+
public PaymentOrigin getPaymentOrigin() {
580+
if (paymentResult != null
581+
&& paymentResult.ehiTechDaysIban != null
582+
&& paymentResult.ehiTechDaysName != null) {
583+
return new PaymentOrigin(paymentResult.ehiTechDaysName,
584+
paymentResult.ehiTechDaysIban);
585+
}
586+
587+
return null;
588+
}
589+
527590
/**
528591
* Gets all available payment methods, callable after {@link Checkout.State#REQUEST_PAYMENT_METHOD}.
529592
*/

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ public enum PaymentState {
143143
FAILED,
144144
}
145145

146+
public static class PaymentResult {
147+
public String ehiTechDaysIban;
148+
public String ehiTechDaysName;
149+
}
150+
146151
public static class CheckoutProcessResponse {
147152
public Map<String, Href> links;
148153
public Boolean supervisorApproval;
@@ -155,6 +160,7 @@ public static class CheckoutProcessResponse {
155160
public boolean modified;
156161
public PaymentInformation paymentInformation;
157162
public PaymentState paymentState;
163+
public PaymentResult paymentResult;
158164

159165
public String getSelfLink() {
160166
Href link = links.get("self");

core/src/main/java/io/snabble/sdk/payment/PaymentCredentials.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,35 @@ public static PaymentCredentials fromSEPA(String name, String iban) {
121121
return pc;
122122
}
123123

124+
public static PaymentCredentials fromEncryptedSEPA(String name, String obfuscatedId, String encryptedData) {
125+
PaymentCredentials pc = new PaymentCredentials();
126+
pc.type = Type.SEPA;
127+
128+
List<X509Certificate> certificates = Snabble.getInstance().getPaymentSigningCertificates();
129+
if (certificates.size() == 0) {
130+
return null;
131+
}
132+
133+
if (name == null || name.length() == 0) {
134+
throw new IllegalArgumentException("Invalid Name");
135+
}
136+
137+
pc.obfuscatedId = obfuscatedId;
138+
139+
X509Certificate certificate = certificates.get(0);
140+
pc.encryptedData = encryptedData;
141+
pc.encrypt();
142+
pc.signature = pc.sha256Signature(certificate);
143+
pc.brand = Brand.UNKNOWN;
144+
pc.appId = Snabble.getInstance().getConfig().appId;
145+
146+
if (pc.encryptedData == null) {
147+
return null;
148+
}
149+
150+
return pc;
151+
}
152+
124153
public static PaymentCredentials fromCreditCardData(String name, Brand brand, String obfuscatedId,
125154
String expirationMonth, String expirationYear,
126155
String hostedDataId, String storeId) {

ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutGatekeeperView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void onStateChanged(Checkout.State state) {
9393
checkoutIdCode.setVisibility(View.VISIBLE);
9494
String id = checkout.getId();
9595
if (id != null) {
96-
checkoutIdCode.setText("snabble:checkoutInfo:" + id);
96+
checkoutIdCode.setText("snabble:checkoutProcess:" + id);
9797
}
9898
break;
9999
case PAYMENT_PROCESSING:

ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutView.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
import android.view.View;
1414
import android.view.ViewGroup;
1515
import android.widget.FrameLayout;
16+
import android.widget.Toast;
1617
import android.widget.ViewAnimator;
1718

1819
import io.snabble.sdk.Checkout;
20+
import io.snabble.sdk.Snabble;
21+
import io.snabble.sdk.payment.PaymentCredentials;
22+
import io.snabble.sdk.ui.KeyguardHandler;
1923
import io.snabble.sdk.ui.R;
2024
import io.snabble.sdk.ui.SnabbleUI;
2125
import io.snabble.sdk.ui.SnabbleUICallback;
@@ -126,6 +130,24 @@ public void onClick(DialogInterface dialog, int which) {
126130
.create()
127131
.show();
128132
break;
133+
case REQUEST_ADD_PAYMENT_ORIGIN:
134+
new AlertDialog.Builder(getContext())
135+
.setMessage(R.string.Snabble_Payment_addPaymentOrigin)
136+
.setPositiveButton(R.string.Snabble_Yes, new DialogInterface.OnClickListener() {
137+
@Override
138+
public void onClick(DialogInterface dialog, int which) {
139+
addPaymentOrigin();
140+
}
141+
})
142+
.setNegativeButton(R.string.Snabble_No, new DialogInterface.OnClickListener() {
143+
@Override
144+
public void onClick(DialogInterface dialog, int which) {
145+
checkout.continuePaymentProcess();
146+
}
147+
})
148+
.create()
149+
.show();
150+
break;
129151
case PAYMENT_ABORTED:
130152
case DENIED_BY_PAYMENT_PROVIDER:
131153
case DENIED_BY_SUPERVISOR:
@@ -146,6 +168,43 @@ public void onClick(DialogInterface dialog, int which) {
146168
currentState = state;
147169
}
148170

171+
private void addPaymentOrigin() {
172+
if (Snabble.getInstance().getUserPreferences().isRequiringKeyguardAuthenticationForPayment()) {
173+
SnabbleUI.getUiCallback().requestKeyguard(new KeyguardHandler() {
174+
@Override
175+
public void onKeyguardResult(int resultCode) {
176+
if (resultCode == Activity.RESULT_OK) {
177+
addPaymentCredentials();
178+
checkout.continuePaymentProcess();
179+
}
180+
}
181+
});
182+
} else {
183+
addPaymentCredentials();
184+
checkout.continuePaymentProcess();
185+
}
186+
}
187+
188+
public void addPaymentCredentials() {
189+
Checkout.PaymentOrigin paymentOrigin = checkout.getPaymentOrigin();
190+
191+
if (paymentOrigin != null) {
192+
final PaymentCredentials pc = PaymentCredentials.fromSEPA(
193+
paymentOrigin.name,
194+
paymentOrigin.iban);
195+
196+
if (pc == null) {
197+
Toast.makeText(getContext(), "Could not verify payment credentials", Toast.LENGTH_LONG)
198+
.show();
199+
} else {
200+
Snabble.getInstance().getPaymentCredentialsStore().add(pc);
201+
Telemetry.event(Telemetry.Event.PaymentMethodAdded, pc.getType().name());
202+
203+
checkout.continuePaymentProcess();
204+
}
205+
}
206+
}
207+
149208
private void displayPaymentView() {
150209
switch (checkout.getSelectedPaymentMethod()) {
151210
case TEGUT_EMPLOYEE_CARD:

ui/src/main/java/io/snabble/sdk/ui/checkout/PaymentMethodView.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class PaymentMethodView extends FrameLayout implements PaymentCredentialsStore.C
5858
icons.put(PaymentMethod.QRCODE_POS, R.drawable.snabble_ic_pm_checkstand);
5959
icons.put(PaymentMethod.QRCODE_OFFLINE, R.drawable.snabble_ic_pm_checkstand);
6060
icons.put(PaymentMethod.TEGUT_EMPLOYEE_CARD, R.drawable.snabble_ic_pm_tegut);
61-
icons.put(PaymentMethod.GATEKEEPER_TERMINAL, R.drawable.snabble_ic_pm_checkstand);
61+
icons.put(PaymentMethod.GATEKEEPER_TERMINAL, R.drawable.snabble_ic_pm_sco);
6262
}
6363

6464
private Checkout checkout;
@@ -419,6 +419,8 @@ public void onBindViewHolder(final PaymentMethodView.ViewHolder holder, final in
419419
matrix.setSaturation(0);
420420
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
421421
imageView.setColorFilter(filter);
422+
} else {
423+
imageView.clearColorFilter();
422424
}
423425

424426
TextView textView = holder.text;

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class ProductConfirmationDialog {
6363

6464
private DialogInterface.OnDismissListener onDismissListener;
6565
private DialogInterface.OnShowListener onShowListener;
66-
66+
private DialogInterface.OnKeyListener onKeyListener;
6767

6868
public ProductConfirmationDialog(Context context,
6969
Project project) {
@@ -84,6 +84,7 @@ public void show(Product product, ScannedCode scannedCode) {
8484

8585
alertDialog.setOnShowListener(onShowListener);
8686
alertDialog.setOnDismissListener(onDismissListener);
87+
alertDialog.setOnKeyListener(onKeyListener);
8788

8889
quantity = view.findViewById(R.id.quantity);
8990
subtitle = view.findViewById(R.id.subtitle);
@@ -285,7 +286,7 @@ private void updatePrice() {
285286
}
286287
}
287288

288-
private void addToCart() {
289+
public void addToCart() {
289290
// its possible that the onClickListener gets called before a dismiss is dispatched
290291
// and when that happens the product is already null
291292
if (cartItem == null) {
@@ -395,4 +396,8 @@ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissList
395396
public void setOnShowListener(DialogInterface.OnShowListener onShowListener) {
396397
this.onShowListener = onShowListener;
397398
}
399+
400+
public void setOnKeyListener(DialogInterface.OnKeyListener onKeyListener) {
401+
this.onKeyListener = onKeyListener;
402+
}
398403
}

0 commit comments

Comments
 (0)