Skip to content

Commit

Permalink
3.2.0 release (#400)
Browse files Browse the repository at this point in the history
* 3.2.0 release

* Update 3.2.0 release date in CHANGELOG.md

Co-authored-by: chr-stripe <[email protected]>

---------

Co-authored-by: chr-stripe <[email protected]>
  • Loading branch information
sjl-stripe and chr-stripe authored Nov 15, 2023
1 parent bad7a34 commit 1cb7ad4
Show file tree
Hide file tree
Showing 74 changed files with 2,628 additions and 97 deletions.
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
This document details changes made to the SDK by version. The current status
of each release can be found in the [Support Lifecycle](SUPPORT.md).

## 3.2.0 - 2023-11-15

### Tap to Pay (localmobile)

- New: SetupIntents are now supported when using Tap-to-Pay.
- Fix: Fixes an issue where `Terminal.connectLocalMobileReader` returned a `TerminalErrorCode.NOT_CONNECTED_TO_READER` exception with the message "No active reader" after connecting to a reader object that was previously disconnected. This flow now successfully connects to the reader.

### Core

- Update: Adds `Charge::authorizationCode` to the sdk's [`Charge`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-charge/index.html) model when it is available.
- _Note for internet reader integrations, this feature requires [reader software version](https://stripe.com/docs/terminal/readers/bbpos-wisepos-e#reader-software-version) `2.18` or later to be installed on your internet reader._
- Update: Added `network` and `wallet` to [`CardPresentDetails`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-card-present-details/index.html).
- _Note for internet reader integrations, this feature requires [reader software version](https://stripe.com/docs/terminal/readers/bbpos-wisepos-e#reader-software-version) `2.19` or later to be installed on your internet reader._
- Update: The amount of time a reader can be used offline before needing to be activated online has been reduced to 30 days.
- Fix: Allows USB readers to be discovered on Android 14 devices and `targetSdkVersion 34`. Fixes part of [issue 387](https://github.com/stripe/stripe-terminal-android/issues/387).

## 3.1.1 - 2023-11-03

### Core
Expand Down Expand Up @@ -92,7 +108,7 @@ of each release can be found in the [Support Lifecycle](SUPPORT.md).
- Update: Move country validation to the backend to allow for more flexible country support.
- Update: Improve successful tap rate with Google Pay mobile wallets.
- Update: Add device-specific UX support for devices released in the last 6 months and popular Xiaomi devices.
- New: Add localized text to UX and error messages returned from the backend based on device locale.
- New: Localize error messages returned from the backend based on device locale.

## 2.20.1 - 2023-05-15

Expand Down
4 changes: 2 additions & 2 deletions Example/build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.8.22'
ext.kotlin_version = "1.8.22"

repositories {
google()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "com.android.tools.build:gradle:7.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
Expand Down
2 changes: 1 addition & 1 deletion Example/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Wed Jun 23 16:32:06 EDT 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
47 changes: 26 additions & 21 deletions Example/javaapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,45 +23,50 @@ android {
}

lint {
enable "Interoperability"
disable "UnusedResources"
disable "UnknownNullness"
disable "MergeRootFrame"
enable += "Interoperability"
disable += "UnusedResources"
disable += "UnknownNullness"
disable += "MergeRootFrame"
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

ext {
androidx_lifecycle_version = '2.6.2'
okhttp_version = '4.11.0'
retrofit_version = '2.9.0'
stripeTerminalVersion = "3.1.1"
androidx_lifecycle_version = "2.6.2"
okhttp_version = "4.11.0"
retrofit_version = "2.9.0"
stripeTerminalVersion = "3.2.0"
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation(fileTree(dir: "libs", include: ["*.jar"]))

implementation "com.google.android.material:material:1.9.0"
implementation "androidx.activity:activity:1.7.2"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.activity:activity:1.7.2")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")

// Annotations
implementation "org.jetbrains:annotations:24.0.1"
implementation("org.jetbrains:annotations:24.0.1")

// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$androidx_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel:$androidx_lifecycle_version"
implementation("androidx.lifecycle:lifecycle-livedata:$androidx_lifecycle_version")
implementation("androidx.lifecycle:lifecycle-viewmodel:$androidx_lifecycle_version")

// OK HTTP
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation("com.squareup.okhttp3:okhttp:$okhttp_version")

// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
implementation("com.squareup.retrofit2:converter-gson:$retrofit_version")

// Stripe Terminal library
implementation "com.stripe:stripeterminal:$stripeTerminalVersion"
implementation("com.stripe:stripeterminal:$stripeTerminalVersion")

// Leak canary
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.12"
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.stripe.example.javaapp;

import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.function.Consumer;

/**
* Wrapper for listeners to allow multiple listener registrations
* @param <T> any listener
*/
public class ListenerAnnouncer<T> {
private final HashSet<WeakReference<T>> listeners = new HashSet<>();

public void addListener(T listener) {
listeners.add(new WeakReference<>(listener));
}

public void removeListener(T listener) {
listeners.removeIf(tWeakReference -> {
T ref = tWeakReference.get();
return ref == listener;
});
}

protected void announce(Consumer<T> fn) {
listeners.forEach(tWeakReference -> {
T listener = tWeakReference.get();
if (listener != null) {
fn.accept(listener);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;

import com.stripe.example.javaapp.fragment.ConnectedReaderFragment;
import com.stripe.example.javaapp.fragment.offline.OfflinePaymentsLogFragment;
import com.stripe.example.javaapp.fragment.PaymentFragment;
import com.stripe.example.javaapp.fragment.TerminalFragment;
import com.stripe.example.javaapp.fragment.UpdateReaderFragment;
Expand All @@ -23,8 +26,10 @@
import com.stripe.example.javaapp.fragment.location.LocationCreateFragment;
import com.stripe.example.javaapp.fragment.location.LocationSelectionController;
import com.stripe.example.javaapp.fragment.location.LocationSelectionFragment;
import com.stripe.example.javaapp.model.OfflineBehaviorSelection;
import com.stripe.example.javaapp.network.TokenProvider;
import com.stripe.stripeterminal.Terminal;
import com.stripe.stripeterminal.external.OfflineMode;
import com.stripe.stripeterminal.external.callable.Cancelable;
import com.stripe.stripeterminal.external.callable.ReaderListener;
import com.stripe.stripeterminal.external.models.BatteryStatus;
Expand All @@ -41,10 +46,16 @@

import java.util.List;

@OptIn(markerClass = OfflineMode.class)
public class MainActivity extends AppCompatActivity implements
NavigationListener,
ReaderListener,
LocationSelectionController {
LocationSelectionController
{

public final OfflineModeHandler offlineModeHandler = new OfflineModeHandler(message -> {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Expand Down Expand Up @@ -127,8 +138,8 @@ public void onRequestExitWorkflow() {
* Callback function called to start a payment by the [PaymentFragment]
*/
@Override
public void onRequestPayment(long amount, @NotNull String currency, boolean skipTipping, boolean extendedAuth, boolean incrementalAuth) {
navigateTo(EventFragment.TAG, EventFragment.requestPayment(amount, currency, skipTipping, extendedAuth, incrementalAuth));
public void onRequestPayment(long amount, @NotNull String currency, boolean skipTipping, boolean extendedAuth, boolean incrementalAuth, OfflineBehaviorSelection offlineBehaviorSelection) {
navigateTo(EventFragment.TAG, EventFragment.requestPayment(amount, currency, skipTipping, extendedAuth, incrementalAuth, offlineBehaviorSelection));
}

/**
Expand Down Expand Up @@ -158,6 +169,15 @@ public void onSelectUpdateWorkflow() {
navigateTo(UpdateReaderFragment.TAG, new UpdateReaderFragment());
}

/**
* Callback function called once the view offline logs has been selected by the
* [ConnectedReaderFragment]
*/
@Override
public void onSelectViewOfflineLogs() {
navigateTo(OfflinePaymentsLogFragment.TAG, new OfflinePaymentsLogFragment());
}

// Terminal event callbacks

/**
Expand Down Expand Up @@ -308,7 +328,7 @@ private void initialize() {
try {
if (!Terminal.isInitialized()) {
Terminal.initTerminal(getApplicationContext(), LogLevel.VERBOSE, new TokenProvider(),
new TerminalEventListener());
TerminalEventListener.instance, TerminalOfflineListener.instance);
}
} catch (TerminalException e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.example.javaapp;

import com.stripe.example.javaapp.fragment.discovery.DiscoveryMethod;
import com.stripe.example.javaapp.model.OfflineBehaviorSelection;

import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -41,7 +42,7 @@ public interface NavigationListener {
/**
* Notify the `Activity` that the user wants to initiate a payment
*/
void onRequestPayment(long amount, @NotNull String currency, boolean skipTipping, boolean extendedAuth, boolean incrementalAuth);
void onRequestPayment(long amount, @NotNull String currency, boolean skipTipping, boolean extendedAuth, boolean incrementalAuth, OfflineBehaviorSelection offlineBehaviorSelection);

/**
* Notify the `Activity` that a [Reader] has been connected
Expand All @@ -63,6 +64,11 @@ public interface NavigationListener {
*/
void onSelectUpdateWorkflow();

/**
* Notify the `Activity` that the user wants to view the offline logs
*/
void onSelectViewOfflineLogs();

/**
* Notify the `Activity` that the user has requested to change the location.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.stripe.example.javaapp;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.lifecycle.MutableLiveData;

import com.stripe.example.javaapp.model.OfflineLog;
import com.stripe.example.javaapp.network.ApiClient;
import com.stripe.stripeterminal.Terminal;
import com.stripe.stripeterminal.external.OfflineMode;
import com.stripe.stripeterminal.external.callable.OfflineListener;
import com.stripe.stripeterminal.external.models.OfflineStatus;
import com.stripe.stripeterminal.external.models.PaymentIntent;
import com.stripe.stripeterminal.external.models.PaymentIntentStatus;
import com.stripe.stripeterminal.external.models.TerminalException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

@OptIn(markerClass = OfflineMode.class)
public class OfflineModeHandler implements OfflineListener {
interface Callback {
void makeToast(String message);
}

private final Callback callback;

private int successfulForwardCount = 0;
private int failedForwardCount = 0;

public List<OfflineLog> logs = new ArrayList<>();

public MutableLiveData<List<OfflineLog>> liveLogs = new MutableLiveData<>(new ArrayList<>());

public OfflineModeHandler(Callback callback) {
this.callback = callback;
}

public void createLog(String logString, @Nullable PaymentIntent details) {
logs.add(new OfflineLog(System.currentTimeMillis(), logString, details));
Collections.sort(logs);
liveLogs.postValue(logs);
}

@Override
public void onOfflineStatusChange(@NonNull OfflineStatus offlineStatus) {
switch (offlineStatus.getSdk().getNetworkStatus()) {
case UNKNOWN:
createLog("Transitioned state to unknown.", null);
case OFFLINE:
createLog("Transitioned state to offline.", null);
case ONLINE:
createLog("Transitioned state to online.", null);
}
}

@Override
public void onPaymentIntentForwarded(@NonNull PaymentIntent paymentIntent, @Nullable TerminalException e) {
if (e != null) {
failedForwardCount++;
String id = paymentIntent.getOfflineDetails() != null ? paymentIntent.getOfflineDetails().getId() : paymentIntent.getDescription();
callback.makeToast(String.format("⚠️ Error forwarding payment: %s\n%s", id, e.getErrorMessage()));

createLog(String.format("Error forwarding offline payment intent: %s\n%s", id, e.getErrorMessage()), paymentIntent);
} else {
successfulForwardCount++;
String offlineId = paymentIntent.getOfflineDetails() != null ? paymentIntent.getOfflineDetails().getId() : "null offline id";
String status = paymentIntent.getStatus() != null ? paymentIntent.getStatus().toString() : "null status";
createLog(String.format("Successfully forwarded offline payment intent: %s with offline id %s status %s",
paymentIntent.getId(), offlineId, status), paymentIntent);

if (paymentIntent.getStatus() == PaymentIntentStatus.REQUIRES_CAPTURE && paymentIntent.getId() != null) {
try {
ApiClient.capturePaymentIntent(paymentIntent.getId());
String message = String.format("Successfully captured offline payment intent for %s: %d%s", paymentIntent.getId(), paymentIntent.getAmount(), paymentIntent.getCurrency());
createLog(message, paymentIntent);
} catch (IOException ioException) {
String message = String.format("Error capturing offline payment intent for %s:%s", paymentIntent.getId(), ioException.getMessage());
createLog(message, paymentIntent);
}
}
}

if (Terminal.getInstance().getOfflineStatus().getSdk().getOfflinePaymentsCount() == 0) {
reportForwardCountsAndReset();
}
}

@Override
public void onForwardingFailure(@NonNull TerminalException e) {
callback.makeToast("⚠️ Error forwarding: " + e.getErrorMessage());
createLog("Did report forwarding error: " + e.getErrorMessage(), null);
}

private void reportForwardCountsAndReset() {
if (successfulForwardCount == 0 || failedForwardCount == 0) {
return;
}
callback.makeToast(String.format(Locale.getDefault(), "✅ Forwarded %d payment(s)\n⚠️ Failed to forward %d payment(s)", successfulForwardCount, failedForwardCount));
successfulForwardCount = 0;
failedForwardCount = 0;
}
}
Loading

0 comments on commit 1cb7ad4

Please sign in to comment.