diff --git a/CHANGES.MD b/CHANGES.MD
index a5c1b4fd..6b793ce7 100644
--- a/CHANGES.MD
+++ b/CHANGES.MD
@@ -1,3 +1,8 @@
+3.19.0 (2025-04-24)
+=================
+- Added support for `$exchange_rate` complex field to `$transaction`, `$create_order`,
+ `$update_order` and `$wager` events, `$booking`, `$discount` and `$item` event fields
+
3.18.0 (2025-03-28)
=================
- Added support for `$card_bin_metadata` complex field to `$payment_method`
diff --git a/README.md b/README.md
index 13c06d15..d35d7d22 100644
--- a/README.md
+++ b/README.md
@@ -13,13 +13,13 @@ Java 1.7 or later.
com.siftscience
sift-java
- 3.18.0
+ 3.19.0
```
### Gradle
```
dependencies {
- compile 'com.siftscience:sift-java:3.18.0'
+ compile 'com.siftscience:sift-java:3.19.0'
}
```
### Other
diff --git a/build.gradle b/build.gradle
index 70b8bf0f..b5fd03c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ apply plugin: 'signing'
apply plugin: 'java-library-distribution'
group = 'com.siftscience'
-version = '3.18.0'
+version = '3.19.0'
repositories {
mavenCentral()
diff --git a/src/main/java/com/siftscience/Constants.java b/src/main/java/com/siftscience/Constants.java
index 4458a705..5c0b538e 100644
--- a/src/main/java/com/siftscience/Constants.java
+++ b/src/main/java/com/siftscience/Constants.java
@@ -3,6 +3,6 @@
public class Constants {
public static final String API_VERSION = "v205";
- public static final String LIB_VERSION = "3.18.0";
+ public static final String LIB_VERSION = "3.19.0";
public static final String USER_AGENT_HEADER = String.format("SiftScience/%s sift-java/%s", API_VERSION, LIB_VERSION);
}
diff --git a/src/main/java/com/siftscience/model/BaseOrderFieldSet.java b/src/main/java/com/siftscience/model/BaseOrderFieldSet.java
index 98faefba..e02f8bdc 100644
--- a/src/main/java/com/siftscience/model/BaseOrderFieldSet.java
+++ b/src/main/java/com/siftscience/model/BaseOrderFieldSet.java
@@ -11,6 +11,7 @@ public abstract class BaseOrderFieldSet>
@Expose @SerializedName(USER_EMAIL) private String userEmail;
@Expose @SerializedName("$amount") private Long amount;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName("$billing_address") private Address billingAddress;
@Expose @SerializedName("$shipping_address") private Address shippingAddress;
@Expose @SerializedName("$payment_methods") private List paymentMethods;
@@ -63,6 +64,15 @@ public T setCurrencyCode(String currencyCode) {
return (T) this;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public T setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return (T) this;
+ }
+
public Boolean getExpeditedShipping() {
return expeditedShipping;
}
diff --git a/src/main/java/com/siftscience/model/Booking.java b/src/main/java/com/siftscience/model/Booking.java
index ad3d40f1..b0515d7c 100644
--- a/src/main/java/com/siftscience/model/Booking.java
+++ b/src/main/java/com/siftscience/model/Booking.java
@@ -12,6 +12,7 @@ public class Booking {
@Expose @SerializedName("$end_time") private Long endTime;
@Expose @SerializedName("$price") private Long price;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName("$quantity") private Long quantity;
@Expose @SerializedName("$iata_carrier_code") private String iataCarrierCode;
@Expose @SerializedName("$guests") private List guests;
@@ -77,6 +78,15 @@ public Booking setCurrencyCode(String currencyCode) {
return this;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public Booking setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return this;
+ }
+
public Long getQuantity() {
return quantity;
}
diff --git a/src/main/java/com/siftscience/model/Discount.java b/src/main/java/com/siftscience/model/Discount.java
index 43bc5365..20ca9ced 100644
--- a/src/main/java/com/siftscience/model/Discount.java
+++ b/src/main/java/com/siftscience/model/Discount.java
@@ -7,6 +7,7 @@ public class Discount {
@Expose @SerializedName("$percentage_off") private Double percentageOff;
@Expose @SerializedName("$amount") private Long amount;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName("$minimum_purchase_amount") private Long minimumPurchaseAmount;
public Double getPercentageOff() {
@@ -36,6 +37,15 @@ public Discount setCurrencyCode(String currencyCode) {
return this;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public Discount setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return this;
+ }
+
public Long getMinimumPurchaseAmount() {
return minimumPurchaseAmount;
}
diff --git a/src/main/java/com/siftscience/model/ExchangeRate.java b/src/main/java/com/siftscience/model/ExchangeRate.java
new file mode 100644
index 00000000..ff703ac1
--- /dev/null
+++ b/src/main/java/com/siftscience/model/ExchangeRate.java
@@ -0,0 +1,27 @@
+package com.siftscience.model;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class ExchangeRate {
+ @Expose @SerializedName("$quote_currency_code") private String quoteCurrencyCode;
+ @Expose @SerializedName("$rate") private Float rate;
+
+ public String getQuoteCurrencyCode() {
+ return quoteCurrencyCode;
+ }
+
+ public ExchangeRate setQuoteCurrencyCode(String quoteCurrencyCode) {
+ this.quoteCurrencyCode = quoteCurrencyCode;
+ return this;
+ }
+
+ public Float getRate() {
+ return rate;
+ }
+
+ public ExchangeRate setRate(Float rate) {
+ this.rate = rate;
+ return this;
+ }
+}
diff --git a/src/main/java/com/siftscience/model/Item.java b/src/main/java/com/siftscience/model/Item.java
index 65ebbe7f..d5472cc6 100644
--- a/src/main/java/com/siftscience/model/Item.java
+++ b/src/main/java/com/siftscience/model/Item.java
@@ -10,6 +10,7 @@ public class Item {
@Expose @SerializedName("$product_title") private String productTitle;
@Expose @SerializedName("$price") private Long price;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName("$quantity") private Long quantity;
@Expose @SerializedName("$upc") private String upc;
@Expose @SerializedName("$sku") private String sku;
@@ -70,6 +71,15 @@ public String getCurrencyCode() {
return currencyCode;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public Item setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return this;
+ }
+
public Item setCurrencyCode(String currencyCode) {
this.currencyCode = currencyCode;
return this;
diff --git a/src/main/java/com/siftscience/model/TransactionFieldSet.java b/src/main/java/com/siftscience/model/TransactionFieldSet.java
index 4e55f547..e578ec2c 100644
--- a/src/main/java/com/siftscience/model/TransactionFieldSet.java
+++ b/src/main/java/com/siftscience/model/TransactionFieldSet.java
@@ -8,6 +8,7 @@
public class TransactionFieldSet extends BaseAppBrowserSiteBrandFieldSet {
@Expose @SerializedName("$amount") private Long amount;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName(USER_EMAIL) private String userEmail;
@Expose @SerializedName("$transaction_type") private String transactionType;
@Expose @SerializedName("$transaction_status") private String transactionStatus;
@@ -66,6 +67,15 @@ public TransactionFieldSet setCurrencyCode(String currencyCode) {
return this;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public TransactionFieldSet setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return this;
+ }
+
public String getUserEmail() {
return userEmail;
}
diff --git a/src/main/java/com/siftscience/model/WagerFieldSet.java b/src/main/java/com/siftscience/model/WagerFieldSet.java
index a549b787..aa7a7a9d 100644
--- a/src/main/java/com/siftscience/model/WagerFieldSet.java
+++ b/src/main/java/com/siftscience/model/WagerFieldSet.java
@@ -10,6 +10,7 @@ public class WagerFieldSet extends EventsApiRequestFieldSet {
@Expose @SerializedName("$wager_status") private String wagerStatus;
@Expose @SerializedName("$amount") private Long amount;
@Expose @SerializedName("$currency_code") private String currencyCode;
+ @Expose @SerializedName("$exchange_rate") private ExchangeRate exchangeRate;
@Expose @SerializedName("$wager_event_type") private String wagerEventType;
@Expose @SerializedName("$wager_event_name") private String wagerEventName;
@Expose @SerializedName("$wager_event_id") private String wagerEventId;
@@ -69,6 +70,15 @@ public WagerFieldSet setCurrencyCode(String currencyCode) {
return this;
}
+ public ExchangeRate getExchangeRate() {
+ return exchangeRate;
+ }
+
+ public WagerFieldSet setExchangeRate(ExchangeRate exchangeRate) {
+ this.exchangeRate = exchangeRate;
+ return this;
+ }
+
public String getWagerEventType() {
return wagerEventType;
}
diff --git a/src/test/java/com/siftscience/CreateOrderEventTest.java b/src/test/java/com/siftscience/CreateOrderEventTest.java
index ae756693..46aecc59 100644
--- a/src/test/java/com/siftscience/CreateOrderEventTest.java
+++ b/src/test/java/com/siftscience/CreateOrderEventTest.java
@@ -2,6 +2,7 @@
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_OK;
+import static java.util.Collections.singletonList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -11,6 +12,7 @@
import com.siftscience.model.Booking;
import com.siftscience.model.CreateOrderFieldSet;
import com.siftscience.model.DigitalOrder;
+import com.siftscience.model.ExchangeRate;
import com.siftscience.model.Item;
import com.siftscience.model.PaymentMethod;
import com.siftscience.model.Promotion;
@@ -1045,4 +1047,106 @@ public void testCreateOrderEventWithCryptoFields() throws JSONException, IOExcep
server.shutdown();
}
+
+ @Test
+ public void testCreateOrderEventWithExchangeRate() throws JSONException, IOException,
+ InterruptedException {
+
+ // The expected JSON payload of the request.
+ String expectedRequestBody = "{\n" +
+ " \"$type\" : \"$create_order\",\n" +
+ " \"$api_key\" : \"YOUR_API_KEY\",\n" +
+ " \"$user_id\" : \"billy_jones_301\",\n" +
+ " \"$order_id\" : \"ORDER-28168441\",\n" +
+ " \"$amount\" : 115940000,\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15\n" +
+ " },\n" +
+ " \"$bookings\" : [\n" +
+ " {\n" +
+ " \"$price\" : 49900000,\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"$promotions\" : [\n" +
+ " {\n" +
+ " \"$discount\" : {\n" +
+ " \"$amount\" : 5000000,\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"$items\" : [\n" +
+ " {\n" +
+ " \"$price\" : 39990000,\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}\n";
+
+ // Start a new mock server and enqueue a mock response.
+ MockWebServer server = new MockWebServer();
+ MockResponse response = new MockResponse();
+ response.setResponseCode(HTTP_OK);
+ response.setBody("{\n" +
+ " \"status\" : 0,\n" +
+ " \"error_message\" : \"OK\",\n" +
+ " \"time\" : 1327604222,\n" +
+ " \"request\" : \"" + TestUtils.unescapeJson(expectedRequestBody) + "\"\n" +
+ "}");
+ server.enqueue(response);
+ server.start();
+
+ // Create a new client and link it to the mock server.
+ SiftClient client = new SiftClient("YOUR_API_KEY", "YOUR_ACCOUNT_ID",
+ new OkHttpClient.Builder()
+ .addInterceptor(OkHttpUtils.urlRewritingInterceptor(server))
+ .build());
+
+ // Build and execute the request against the mock server.
+ // It includes booking, promotion and item with ExchangeRate
+ EventRequest request = client.buildRequest(
+ new CreateOrderFieldSet()
+ .setUserId("billy_jones_301")
+ .setOrderId("ORDER-28168441")
+ .setAmount(115940000L)
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f))
+ .setBookings(singletonList(TestUtils.sampleBookingWithExchangeRate()))
+ .setPromotions(singletonList(TestUtils.samplePromotionWithExchangeRate()))
+ .setItems(singletonList(TestUtils.sampleItemWithExchangeRate()))
+ );
+
+ EventResponse siftResponse = request.send();
+
+ // Verify the request.
+ RecordedRequest request1 = server.takeRequest();
+ Assert.assertEquals("POST", request1.getMethod());
+ Assert.assertEquals("/v205/events", request1.getPath());
+ JSONAssert.assertEquals(expectedRequestBody, request.getFieldSet().toJson(), true);
+
+ // Verify the response.
+ Assert.assertEquals(HTTP_OK, siftResponse.getHttpStatusCode());
+ Assert.assertEquals(0, (int) siftResponse.getBody().getStatus());
+ JSONAssert.assertEquals(response.getBody().readUtf8(),
+ siftResponse.getBody().toJson(), true);
+
+ server.shutdown();
+ }
}
diff --git a/src/test/java/com/siftscience/SiftRequestTest.java b/src/test/java/com/siftscience/SiftRequestTest.java
index e0f64501..b0d3228b 100644
--- a/src/test/java/com/siftscience/SiftRequestTest.java
+++ b/src/test/java/com/siftscience/SiftRequestTest.java
@@ -29,7 +29,7 @@ public void testUserAgentHeader() throws Exception {
// then
RecordedRequest recordedRequest = server.takeRequest();
- assertEquals("SiftScience/v205 sift-java/3.18.0",
+ assertEquals("SiftScience/v205 sift-java/3.19.0",
recordedRequest.getHeader("User-Agent"));
}
diff --git a/src/test/java/com/siftscience/TestUtils.java b/src/test/java/com/siftscience/TestUtils.java
index 48342149..59790ca0 100644
--- a/src/test/java/com/siftscience/TestUtils.java
+++ b/src/test/java/com/siftscience/TestUtils.java
@@ -5,6 +5,7 @@
import com.siftscience.model.CreditPoint;
import com.siftscience.model.DigitalOrder;
import com.siftscience.model.Discount;
+import com.siftscience.model.ExchangeRate;
import com.siftscience.model.Guest;
import com.siftscience.model.Item;
import com.siftscience.model.MerchantProfile;
@@ -170,6 +171,15 @@ static Item sampleItem2() {
.setQuantity(2L);
}
+ static Item sampleItemWithExchangeRate() {
+ return new Item()
+ .setPrice(39990000L)
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f));
+ }
+
static Booking sampleBooking() {
List guests = new ArrayList<>();
guests.add(sampleGuest1());
@@ -192,6 +202,15 @@ static Booking sampleBooking() {
.setQuantity(1L);
}
+ static Booking sampleBookingWithExchangeRate() {
+ return new Booking()
+ .setPrice(49900000L)
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f));
+ }
+
static OrderedFrom sampleOrderedFrom() {
return new OrderedFrom()
.setStoreId("123")
@@ -239,6 +258,15 @@ static Discount sampleDiscount2() {
.setCurrencyCode("USD");
}
+ static Discount sampleDiscountWithExchangeRate() {
+ return new Discount()
+ .setAmount(5000000L)
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f));
+ }
+
static CreditPoint sampleCreditPoint() {
return new CreditPoint()
.setAmount(100L)
@@ -270,6 +298,11 @@ static Promotion samplePromotion3() {
.setDiscount(sampleDiscount2());
}
+ static Promotion samplePromotionWithExchangeRate() {
+ return new Promotion()
+ .setDiscount(sampleDiscountWithExchangeRate());
+ }
+
static List sampleCategories() {
List categories = new ArrayList<>();
categories.add("Housing");
diff --git a/src/test/java/com/siftscience/TransactionEventTest.java b/src/test/java/com/siftscience/TransactionEventTest.java
index cf845282..bbd52b54 100644
--- a/src/test/java/com/siftscience/TransactionEventTest.java
+++ b/src/test/java/com/siftscience/TransactionEventTest.java
@@ -7,6 +7,7 @@
import com.siftscience.model.CardBinMetadata;
import com.siftscience.model.DigitalOrder;
+import com.siftscience.model.ExchangeRate;
import com.siftscience.model.PaymentMethod;
import com.siftscience.model.TransactionFieldSet;
import okhttp3.OkHttpClient;
@@ -876,4 +877,64 @@ public void testTransactionEventWithBinMetadata() throws Exception {
server.shutdown();
}
+
+ @Test
+ public void testTransactionEventWithExchangeRate() throws Exception {
+ String expectedRequestBody = "{\n" +
+ " \"$type\" : \"$transaction\",\n" +
+ " \"$api_key\" : \"YOUR_API_KEY\",\n" +
+ " \"$user_id\" : \"billy_jones_301\",\n" +
+ " \"$user_email\" : \"bill@gmail.com\",\n" +
+ " \"$amount\" : 506790000,\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15,\n" +
+ " },\n" +
+ "}";
+
+ // Start a new mock server and enqueue a mock response.
+ MockWebServer server = new MockWebServer();
+ MockResponse response = new MockResponse();
+ response.setResponseCode(HTTP_OK);
+ response.setBody("{\n" +
+ " \"status\" : 0,\n" +
+ " \"error_message\" : \"OK\",\n" +
+ " \"time\" : 1327604222,\n" +
+ " \"request\" : \"" + TestUtils.unescapeJson(expectedRequestBody) + "\"\n" +
+ "}");
+ server.enqueue(response);
+ server.start();
+
+ // Create a new client and link it to the mock server.
+ SiftClient client = new SiftClient("YOUR_API_KEY", "YOUR_ACCOUNT_ID",
+ new OkHttpClient.Builder()
+ .addInterceptor(OkHttpUtils.urlRewritingInterceptor(server))
+ .build());
+
+ // Build and execute the request against the mock server.
+ EventRequest request = client.buildRequest(new TransactionFieldSet()
+ .setUserId("billy_jones_301")
+ .setUserEmail("bill@gmail.com")
+ .setAmount(506790000L)
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f)));
+ EventResponse siftResponse = request.send();
+
+ // Verify the request.
+ RecordedRequest request1 = server.takeRequest();
+ Assert.assertEquals("POST", request1.getMethod());
+ Assert.assertEquals("/v205/events", request1.getPath());
+ JSONAssert.assertEquals(expectedRequestBody, request.getFieldSet().toJson(), true);
+
+ // Verify the response.
+ Assert.assertEquals(HTTP_OK, siftResponse.getHttpStatusCode());
+ Assert.assertEquals(0, (int) siftResponse.getBody().getStatus());
+ JSONAssert.assertEquals(response.getBody().readUtf8(),
+ siftResponse.getBody().toJson(), true);
+
+ server.shutdown();
+ }
}
diff --git a/src/test/java/com/siftscience/WagerFieldSetTest.java b/src/test/java/com/siftscience/WagerFieldSetTest.java
index dfd296c7..0f9edcfd 100644
--- a/src/test/java/com/siftscience/WagerFieldSetTest.java
+++ b/src/test/java/com/siftscience/WagerFieldSetTest.java
@@ -2,6 +2,7 @@
import static java.net.HttpURLConnection.HTTP_OK;
+import com.siftscience.model.ExchangeRate;
import com.siftscience.model.WagerFieldSet;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
@@ -22,7 +23,11 @@ public void testWagerEvent() throws Exception {
" \"$wager_type\" : \"win_1\",\n" +
" \"$wager_status\" : \"$accept\",\n" +
" \"$amount\" : 506790000,\n" +
- " \"$currency_code\" : \"USD\",\n" +
+ " \"$currency_code\" : \"EUR\",\n" +
+ " \"$exchange_rate\" : {\n" +
+ " \"$quote_currency_code\" : \"USD\",\n" +
+ " \"$rate\" : 1.15\n" +
+ " },\n" +
" \"$wager_event_type\" : \"sportsbook\",\n" +
" \"$wager_event_name\" : \"NFL\",\n" +
" \"$wager_event_id\" : \"NFL_2024_N1234\",\n" +
@@ -55,7 +60,10 @@ public void testWagerEvent() throws Exception {
.setWagerType("win_1")
.setWagerStatus("$accept")
.setAmount(506790000L)
- .setCurrencyCode("USD")
+ .setCurrencyCode("EUR")
+ .setExchangeRate(new ExchangeRate()
+ .setQuoteCurrencyCode("USD")
+ .setRate(1.15f))
.setWagerEventType("sportsbook")
.setWagerEventName("NFL")
.setWagerEventId("NFL_2024_N1234")