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")