Skip to content

Commit

Permalink
Merge pull request #6 from SiftScience/arjun_decisions_api
Browse files Browse the repository at this point in the history
(For AlexL) Support for Apply Decisions API in sift-java client
  • Loading branch information
akrishnaiah authored Jan 18, 2017
2 parents a7eb3df + 7b7a3db commit 814ad84
Show file tree
Hide file tree
Showing 15 changed files with 579 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGES.MD
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.3 (2016-01-17)
==================

- Add support for decisions API.

1.2 (2016-12-14)
==================

Expand Down
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,20 +197,50 @@ WorkflowStatusRequest request = client.buildRequest(new WorkflowStatusFieldSet()
.setWorkflowRunId("someid"));
```

### Apply Decision API

[API Docs](https://siftscience.com/developers/docs/java/decisions-api/apply-decision)

To apply a decision to a user, create a request with accountId, userId, and ApplyDecisionFieldSet.
```java
ApplyDecisionRequest request = client.buildRequest(
new ApplyDecisionFieldSet()
.setAccountId("your_account_id")
.setUserId("a_user_id")
.setDecisionId("decision_id")
.setSource(DecisionSource.AUTOMATED_RULE)
.setDescription("description of decision applied")
);
```

To apply a decision to an order, create a request with accountId, userId, orderId and ApplyDecisionFieldSet.
```java
ApplyDecisionRequest request = client.buildRequest(
new ApplyDecisionFieldSet()
.setAccountId("your_account_id")
.setUserId("a_user_id")
.setOrderId("a_order_id")
.setDecisionId("decision_id")
.setSource(DecisionSource.MANUAL_REVIEW)
.setAnalyst("[email protected]")
.setDescription("description of decision applied")
);
```

#### Get decisions

[API Docs](https://siftscience.com/developers/docs/java/decisions-api/get-decisions)

To retrieve available decisions, build a request with a GetDecisionsFieldSet.
```java
GetDecisions request = client.buildRequest(new GetDecisionsFieldSet()
GetDecisionsRequest request = client.buildRequest(new GetDecisionsFieldSet()
.setAbuseTypes(ImmutableList.of(AbuseType.PAYMENT_ABUSE, AbuseType.CONTENT_ABUSE))
.setAccountId("your_account_id"));
```

Additionally, this field set supports filtering on results by entity and abuse type(s).
```java
GetDecisions request = client.buildRequest(new GetDecisionsFieldSet()
GetDecisionsRequest request = client.buildRequest(new GetDecisionsFieldSet()
.setAccountId("your_account_id"))
.setEntityType(EntityType.ORDER)
.setAbuseTypes(ImmutableList.of(AbuseType.PAYMENT_ABUSE, AbuseType.CONTENT_ABUSE))
Expand All @@ -220,7 +250,7 @@ Pagination is also supported, with offset by index (`from`) and limit (`limit`).
The default `limit` is to return up to 100 results.
The default offset value `from` is 0.
```java
GetDecisions request = client.buildRequest(new GetDecisionsFieldSet()
GetDecisionsRequest request = client.buildRequest(new GetDecisionsFieldSet()
.setAccountId("your_account_id"))
.setFrom(15)
.setLimit(10);
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'signing'
apply plugin: 'java-library-distribution'

group = 'com.siftscience'
version = '1.2'
version = '1.3'
sourceCompatibility = 1.7
targetCompatibility = 1.7

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/siftscience/ApplyDecisionRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.siftscience;

import java.io.IOException;

import com.siftscience.model.ApplyDecisionFieldSet;
import okhttp3.*;

public class ApplyDecisionRequest extends SiftRequest<ApplyDecisionResponse>{

ApplyDecisionRequest(HttpUrl baseUrl, OkHttpClient okClient, ApplyDecisionFieldSet fields) {
super(baseUrl, okClient, fields);
}

@Override
protected HttpUrl path(HttpUrl baseUrl) {
HttpUrl.Builder path = baseUrl.newBuilder("v3/accounts")
.addPathSegment(((ApplyDecisionFieldSet) fieldSet).getAccountId())
.addPathSegment("users")
.addPathSegment(((ApplyDecisionFieldSet) fieldSet).getUserId());

String orderId = ((ApplyDecisionFieldSet) fieldSet).getOrderId();
if (orderId != null && !orderId.isEmpty()) {
path.addPathSegment("orders").addPathSegment(orderId);
}

return path.addPathSegment("decisions").build();
}

@Override
ApplyDecisionResponse buildResponse(okhttp3.Response response, FieldSet requestFields) throws IOException {
return new ApplyDecisionResponse(response, requestFields);
}

@Override
protected void modifyRequestBuilder(Request.Builder builder) {
super.modifyRequestBuilder(builder);
builder.header("Authorization", Credentials.basic(fieldSet.getApiKey(),""));
}

}
24 changes: 24 additions & 0 deletions src/main/java/com/siftscience/ApplyDecisionResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.siftscience;

import com.siftscience.model.DecisionLog;
import com.siftscience.model.ApplyDecisionResponseBody;
import okhttp3.Response;

import java.io.IOException;

public class ApplyDecisionResponse extends SiftResponse<DecisionLog>{

ApplyDecisionResponse(Response okResponse, FieldSet requestBody) throws IOException {
super(okResponse, requestBody);
}

@Override
public void populateBodyFromJson(String jsonBody) {
body = DecisionLog.fromJson(jsonBody);
}

public DecisionLog getDecisionLog() {
return body;
}

}
58 changes: 55 additions & 3 deletions src/main/java/com/siftscience/FieldSet.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.siftscience;

import com.google.gson.*;
import com.siftscience.exception.InvalidApiKeyException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.siftscience.exception.InvalidFieldException;
import com.siftscience.exception.MissingFieldException;
import com.siftscience.model.GetDecisionFieldSet;
import com.siftscience.model.GetDecisionsResponseBody;

import java.lang.reflect.Type;
import java.util.Map;


/**
* FieldSet represents a set of fields/parameters to send along with an API request. It handles
* JSON serialization, field validation, and provides support for custom fields. One important
Expand Down Expand Up @@ -37,6 +47,8 @@ public abstract class FieldSet<T extends FieldSet<T>> {
protected static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(FieldSet.class, new FieldSetDeserializer())
.registerTypeHierarchyAdapter(FieldSet.class, new FieldSetSerializer())
.registerTypeAdapter(GetDecisionsResponseBody.Decision.class,
new DecisionSetDeserializer())
.create();

// Every Events API request will have a reserved "$type" field that never changes for that
Expand Down Expand Up @@ -99,6 +111,46 @@ public void validate() {
}
}

/**
* Custom serialization adapter for DecisionSet
*/
private static class DecisionSetDeserializer implements
JsonDeserializer<GetDecisionsResponseBody.Decision> {
@Override
public GetDecisionsResponseBody.Decision deserialize(JsonElement json,
Type t,
JsonDeserializationContext ctx)
throws JsonParseException {
GetDecisionsResponseBody.Decision decision = defaultGson.fromJson(json, t);
JsonObject asJsonObject = json.getAsJsonObject();
if (asJsonObject.has("abuse_type")) {
try {
decision.setAbuseType(GetDecisionFieldSet.AbuseType.valueOf(asJsonObject
.get("abuse_type").getAsString().toUpperCase()));
} catch (IllegalArgumentException e) {
//Unable to deserialize abuseType
}
}
if (asJsonObject.has("category")) {
try {
decision.setCategory(GetDecisionFieldSet.DecisionCategory.valueOf(asJsonObject
.get("category").getAsString().toUpperCase()));
} catch (IllegalArgumentException e) {
//Unable to deserialize category type
}
}
if (asJsonObject.has("entity_type")) {
try {
decision.setEntityType(GetDecisionFieldSet.EntityType.valueOf(asJsonObject
.get("entity_type").getAsString().toUpperCase()));
} catch (IllegalArgumentException e) {
//Unable to deserialize entityType
}
}
return decision;
}
}

/**
* Custom serialization adapter for all FieldSets.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/siftscience/SiftClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public EventRequest buildRequest(FieldSet fields) {
return new EventRequest(baseUrl, okClient, fields);
}

public ApplyDecisionRequest buildRequest(ApplyDecisionFieldSet fields) {
setupApiKey(fields);
return new ApplyDecisionRequest(baseApi3Url, okClient, fields);
}

public GetDecisionsRequest buildRequest(GetDecisionFieldSet fields) {
setupApiKey(fields);
return new GetDecisionsRequest(baseApi3Url, okClient, fields);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/siftscience/SiftRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public T send() throws IOException {
// Ok now that the fieldSet is valid, construct and send the request.
Request.Builder okRequestBuilder = new Request.Builder().url(this.url());
modifyRequestBuilder(okRequestBuilder);
T response = buildResponse(okClient.newCall(okRequestBuilder.build()).execute(), fieldSet);
Request request = okRequestBuilder.build();
T response = buildResponse(okClient.newCall(request).execute(), fieldSet);

// If not successful but no exception happened yet, dig deeper into the response so we
// can manually throw an appropriate exception.
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/com/siftscience/model/ApplyDecisionFieldSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.siftscience.model;

import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.siftscience.FieldSet;
import com.siftscience.exception.MissingFieldException;

import java.lang.reflect.Type;

public class ApplyDecisionFieldSet extends FieldSet<ApplyDecisionFieldSet> {

public enum DecisionSource {
MANUAL_REVIEW("manual_review"),
AUTOMATED_RULE("automated_rule"),
CHARGEBACK("chargeback");

private final String value;

DecisionSource(String value) {
this.value = value;
}

@Override
public String toString(){
return value;
}

static class DecisionSourceDeserializer implements JsonDeserializer<DecisionSource> {
@Override
public DecisionSource deserialize(JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
return DecisionSource.valueOf(json.getAsString().toUpperCase());
}
}

static class DecisionSourceSerializer implements JsonSerializer<DecisionSource> {
@Override
public JsonElement serialize(DecisionSource source, Type typeOfSrc, JsonSerializationContext context) {
return gson.toJsonTree(source.value);
}
}
}


@Expose @SerializedName("decision_id") private String decisionId;
@Expose @SerializedName("source") private DecisionSource source;
@Expose @SerializedName("analyst") private String analyst;
@Expose @SerializedName("descrion") private String description;
@Expose @SerializedName("time") private Long time;
private String accountId;
private String userId;
private String orderId;

public ApplyDecisionFieldSet() {}

public ApplyDecisionFieldSet setDecisionId(String decisionId) {
this.decisionId = decisionId;
return this;
}

public ApplyDecisionFieldSet setSource(DecisionSource source) {
this.source = source;
return this;
}

public ApplyDecisionFieldSet setDescription(String description) {
this.description = description;
return this;
}

public ApplyDecisionFieldSet setAnalyst(String analyst) {
this.analyst = analyst;
return this;
}

public ApplyDecisionFieldSet setTime(Long time) {
this.time = time;
return this;
}

public String getAccountId() {
return accountId;
}

public ApplyDecisionFieldSet setAccountId(String accountId) {
this.accountId = accountId;
return this;
}

public String getUserId() {
return userId;
}

public ApplyDecisionFieldSet setUserId(String userId) {
this.userId = userId;
return this;
}

public String getOrderId() {
return orderId;
}

public ApplyDecisionFieldSet setOrderId(String orderId) {
this.orderId = orderId;
return this;
}

@Override
public void validate() {
super.validate();
if (DecisionSource.MANUAL_REVIEW.equals(source) && (analyst == null || analyst.isEmpty())) {
throw new MissingFieldException("'analyst' required for decisions " +
"with source type MANUAL_REVIEW");
}
}
}
Loading

0 comments on commit 814ad84

Please sign in to comment.