From 0d0b51f3c81bb0390b7dfec689e66ec88c5c8b42 Mon Sep 17 00:00:00 2001 From: Kostya Date: Thu, 28 May 2020 11:32:12 +0300 Subject: [PATCH] Add chargebacks and refunds (#25) --- src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 | 20 +++++++++++ .../fraudo/aggregator/CountAggregator.java | 4 +++ .../fraudo/aggregator/SumAggregator.java | 4 +++ .../key/generator/CountKeyGenerator.java | 18 ++++++++++ .../utils/key/generator/SumKeyGenerator.java | 18 ++++++++++ .../rbkmoney/fraudo/visitor/CountVisitor.java | 4 +++ .../rbkmoney/fraudo/visitor/SumVisitor.java | 4 +++ .../fraudo/visitor/impl/CountVisitorImpl.java | 22 ++++++++++++ .../visitor/impl/FirstFindVisitorImpl.java | 36 +++++++++++++++++++ .../fraudo/visitor/impl/SumVisitorImpl.java | 22 ++++++++++++ .../java/com/rbkmoney/fraudo/CountTest.java | 13 +++++++ .../java/com/rbkmoney/fraudo/SumTest.java | 10 ++++++ src/test/resources/rules/amount.frd | 2 +- .../rules/count_chargeback_refund.frd | 2 ++ .../resources/rules/sum_chargeback_refund.frd | 2 ++ 15 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/rules/count_chargeback_refund.frd create mode 100644 src/test/resources/rules/sum_chargeback_refund.frd diff --git a/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 b/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 index adca360..aa8eb0a 100644 --- a/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 +++ b/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 @@ -17,9 +17,13 @@ expression | count #countExpression | count_success #countSuccessExpression | count_error #countErrorExpression + | count_chargeback #countChargebackExpression + | count_refund #countRefundExpression | sum #sumExpression | sum_success #sumSuccessExpression | sum_error #sumErrorExpression + | sum_chargeback #sumChargebackExpression + | sum_refund #sumRefundExpression | unique #uniqueExpression | in #inFunctionExpression | in_white_list #inWhiteListExpression @@ -68,6 +72,14 @@ count_error : 'countError' LPAREN STRING time_window DELIMETER STRING (group_by)? RPAREN ; +count_chargeback + : 'countChargeback' LPAREN STRING time_window (group_by)? RPAREN + ; + +count_refund + : 'countRefund' LPAREN STRING time_window (group_by)? RPAREN + ; + sum : 'sum' LPAREN STRING time_window (group_by)? RPAREN ; @@ -80,6 +92,14 @@ sum_error : 'sumError' LPAREN STRING time_window DELIMETER STRING (group_by)? RPAREN ; +sum_chargeback + : 'sumChargeback' LPAREN STRING time_window (group_by)? RPAREN + ; + +sum_refund + : 'sumRefund' LPAREN STRING time_window (group_by)? RPAREN + ; + unique : 'unique' LPAREN STRING DELIMETER STRING time_window (group_by)? RPAREN ; diff --git a/src/main/java/com/rbkmoney/fraudo/aggregator/CountAggregator.java b/src/main/java/com/rbkmoney/fraudo/aggregator/CountAggregator.java index acac36c..eff1455 100644 --- a/src/main/java/com/rbkmoney/fraudo/aggregator/CountAggregator.java +++ b/src/main/java/com/rbkmoney/fraudo/aggregator/CountAggregator.java @@ -12,4 +12,8 @@ public interface CountAggregator { Integer countError(U checkedField, T model, TimeWindow timeWindow, String errorCode, List fields); + Integer countChargeback(U checkedField, T model, TimeWindow timeWindow, List fields); + + Integer countRefund(U checkedField, T model, TimeWindow timeWindow, List fields); + } diff --git a/src/main/java/com/rbkmoney/fraudo/aggregator/SumAggregator.java b/src/main/java/com/rbkmoney/fraudo/aggregator/SumAggregator.java index d3d6a08..b69eeb2 100644 --- a/src/main/java/com/rbkmoney/fraudo/aggregator/SumAggregator.java +++ b/src/main/java/com/rbkmoney/fraudo/aggregator/SumAggregator.java @@ -12,4 +12,8 @@ public interface SumAggregator { Double sumError(U checkedField, T model, TimeWindow timeWindow, String errorCode, List fields); + Double sumChargeback(U checkedField, T model, TimeWindow timeWindow, List fields); + + Double sumRefund(U checkedField, T model, TimeWindow timeWindow, List fields); + } diff --git a/src/main/java/com/rbkmoney/fraudo/utils/key/generator/CountKeyGenerator.java b/src/main/java/com/rbkmoney/fraudo/utils/key/generator/CountKeyGenerator.java index 2d6a406..c9a0e6b 100644 --- a/src/main/java/com/rbkmoney/fraudo/utils/key/generator/CountKeyGenerator.java +++ b/src/main/java/com/rbkmoney/fraudo/utils/key/generator/CountKeyGenerator.java @@ -38,4 +38,22 @@ public class CountKeyGenerator { resolve); } + public static String generateChargebackKey(ParserRuleContext context, Function resolve) { + FraudoParser.Count_chargebackContext ctx = (FraudoParser.Count_chargebackContext) context; + return CommonKeyGenerator.generateKeyGroupedFunction(ctx.STRING(), + ctx.children.get(0), + ctx.time_window(), + ctx.group_by(), + resolve); + } + + public static String generateRefundKey(ParserRuleContext context, Function resolve) { + FraudoParser.Count_refundContext ctx = (FraudoParser.Count_refundContext) context; + return CommonKeyGenerator.generateKeyGroupedFunction(ctx.STRING(), + ctx.children.get(0), + ctx.time_window(), + ctx.group_by(), + resolve); + } + } diff --git a/src/main/java/com/rbkmoney/fraudo/utils/key/generator/SumKeyGenerator.java b/src/main/java/com/rbkmoney/fraudo/utils/key/generator/SumKeyGenerator.java index 8fdec5f..129e08f 100644 --- a/src/main/java/com/rbkmoney/fraudo/utils/key/generator/SumKeyGenerator.java +++ b/src/main/java/com/rbkmoney/fraudo/utils/key/generator/SumKeyGenerator.java @@ -38,4 +38,22 @@ public class SumKeyGenerator { resolve); } + public static String generateChargebackKey(ParserRuleContext context, Function resolve) { + FraudoParser.Sum_chargebackContext ctx = (FraudoParser.Sum_chargebackContext) context; + return CommonKeyGenerator.generateKeyGroupedFunction(ctx.STRING(), + ctx.children.get(0), + ctx.time_window(), + ctx.group_by(), + resolve); + } + + public static String generateRefundKey(ParserRuleContext context, Function resolve) { + FraudoParser.Sum_refundContext ctx = (FraudoParser.Sum_refundContext) context; + return CommonKeyGenerator.generateKeyGroupedFunction(ctx.STRING(), + ctx.children.get(0), + ctx.time_window(), + ctx.group_by(), + resolve); + } + } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/CountVisitor.java b/src/main/java/com/rbkmoney/fraudo/visitor/CountVisitor.java index 5fd624e..2d0b633 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/CountVisitor.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/CountVisitor.java @@ -10,4 +10,8 @@ public interface CountVisitor { Integer visitCountError(FraudoParser.Count_errorContext ctx, T model); + Integer visitCountChargeback(FraudoParser.Count_chargebackContext ctx, T model); + + Integer visitCountRefund(FraudoParser.Count_refundContext ctx, T model); + } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/SumVisitor.java b/src/main/java/com/rbkmoney/fraudo/visitor/SumVisitor.java index fc291eb..3433a64 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/SumVisitor.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/SumVisitor.java @@ -10,4 +10,8 @@ public interface SumVisitor { Double visitSumError(FraudoParser.Sum_errorContext ctx, T model); + Double visitSumChargeback(FraudoParser.Sum_chargebackContext ctx, T model); + + Double visitSumRefund(FraudoParser.Sum_refundContext ctx, T model); + } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/impl/CountVisitorImpl.java b/src/main/java/com/rbkmoney/fraudo/visitor/impl/CountVisitorImpl.java index 2e65f10..46029c3 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/impl/CountVisitorImpl.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/impl/CountVisitorImpl.java @@ -51,4 +51,26 @@ public class CountVisitorImpl implements CountVisitor { ); } + @Override + public Integer visitCountChargeback(FraudoParser.Count_chargebackContext ctx, T model) { + String countTarget = TextUtil.safeGetText(ctx.STRING()); + return countAggregator.countChargeback( + fieldResolver.resolveName(countTarget), + model, + TimeWindowResolver.resolve(ctx.time_window()), + groupFieldsResolver.resolve(ctx.group_by()) + ); + } + + @Override + public Integer visitCountRefund(FraudoParser.Count_refundContext ctx, T model) { + String countTarget = TextUtil.safeGetText(ctx.STRING()); + return countAggregator.countRefund( + fieldResolver.resolveName(countTarget), + model, + TimeWindowResolver.resolve(ctx.time_window()), + groupFieldsResolver.resolve(ctx.group_by()) + ); + } + } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/impl/FirstFindVisitorImpl.java b/src/main/java/com/rbkmoney/fraudo/visitor/impl/FirstFindVisitorImpl.java index 9945edd..aabe578 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/impl/FirstFindVisitorImpl.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/impl/FirstFindVisitorImpl.java @@ -174,6 +174,24 @@ public class FirstFindVisitorImpl extends FraudoBaseVisi ); } + @Override + public Object visitCount_chargeback(FraudoParser.Count_chargebackContext ctx) { + String key = CountKeyGenerator.generateChargebackKey(ctx, fieldResolver::resolveName); + return localFuncCache.get().computeIfAbsent( + key, + s -> Double.valueOf(countVisitor.visitCountChargeback(ctx, threadLocalModel.get())) + ); + } + + @Override + public Object visitCount_refund(FraudoParser.Count_refundContext ctx) { + String key = CountKeyGenerator.generateRefundKey(ctx, fieldResolver::resolveName); + return localFuncCache.get().computeIfAbsent( + key, + s -> Double.valueOf(countVisitor.visitCountRefund(ctx, threadLocalModel.get())) + ); + } + @Override public Object visitSum(FraudoParser.SumContext ctx) { String key = SumKeyGenerator.generate(ctx, fieldResolver::resolveName); @@ -201,6 +219,24 @@ public class FirstFindVisitorImpl extends FraudoBaseVisi ); } + @Override + public Object visitSum_chargeback(FraudoParser.Sum_chargebackContext ctx) { + String key = SumKeyGenerator.generateChargebackKey(ctx, fieldResolver::resolveName); + return localFuncCache.get().computeIfAbsent( + key, + s -> sumVisitor.visitSumChargeback(ctx, threadLocalModel.get()) + ); + } + + @Override + public Object visitSum_refund(FraudoParser.Sum_refundContext ctx) { + String key = SumKeyGenerator.generateRefundKey(ctx, fieldResolver::resolveName); + return localFuncCache.get().computeIfAbsent( + key, + s -> sumVisitor.visitSumRefund(ctx, threadLocalModel.get()) + ); + } + @Override public Object visitCountry_by(FraudoParser.Country_byContext ctx) { String key = CountryKeyGenerator.generate(ctx); diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/impl/SumVisitorImpl.java b/src/main/java/com/rbkmoney/fraudo/visitor/impl/SumVisitorImpl.java index a60d40e..0ddb485 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/impl/SumVisitorImpl.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/impl/SumVisitorImpl.java @@ -49,4 +49,26 @@ public class SumVisitorImpl implements SumVisitor { groupByModelResolver.resolve(ctx.group_by())); } + @Override + public Double visitSumChargeback(FraudoParser.Sum_chargebackContext ctx, T model) { + String countTarget = TextUtil.safeGetText(ctx.STRING()); + return sumAggregator.sumChargeback( + fieldResolver.resolveName(countTarget), + model, + TimeWindowResolver.resolve(ctx.time_window()), + groupByModelResolver.resolve(ctx.group_by()) + ); + } + + @Override + public Double visitSumRefund(FraudoParser.Sum_refundContext ctx, T model) { + String countTarget = TextUtil.safeGetText(ctx.STRING()); + return sumAggregator.sumRefund( + fieldResolver.resolveName(countTarget), + model, + TimeWindowResolver.resolve(ctx.time_window()), + groupByModelResolver.resolve(ctx.group_by()) + ); + } + } diff --git a/src/test/java/com/rbkmoney/fraudo/CountTest.java b/src/test/java/com/rbkmoney/fraudo/CountTest.java index fbf492d..7229cdc 100644 --- a/src/test/java/com/rbkmoney/fraudo/CountTest.java +++ b/src/test/java/com/rbkmoney/fraudo/CountTest.java @@ -25,6 +25,8 @@ public class CountTest extends AbstractPaymentTest { when(countAggregator.count(anyObject(), any(), any(), any())).thenReturn(10); when(countAggregator.countError(anyObject(), any(), any(), anyString(), any())).thenReturn(6); when(countAggregator.countSuccess(anyObject(), any(), any(), any())).thenReturn(4); + when(countAggregator.countChargeback(anyObject(), any(), any(), any())).thenReturn(0); + when(countAggregator.countRefund(anyObject(), any(), any(), any())).thenReturn(1); com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream); ResultModel result = invokeParse(parseContext); assertEquals(ResultStatus.DECLINE, result.getResultStatus()); @@ -38,6 +40,17 @@ public class CountTest extends AbstractPaymentTest { assertEquals(ResultStatus.DECLINE, result.getResultStatus()); } + @Test + public void countCargeRefundTest() throws Exception { + InputStream resourceAsStream = CountTest.class.getResourceAsStream("/rules/count_chargeback_refund.frd"); + when(countAggregator.countChargeback(anyObject(), any(), any(), any())).thenReturn(3); + when(countAggregator.countRefund(anyObject(), any(), any(), any())).thenReturn(5); + com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream); + ResultModel result = invokeParse(parseContext); + assertEquals(ResultStatus.DECLINE, result.getResultStatus()); + assertEquals("1", result.getRuleChecked()); + } + @Test public void countGroupByTest() throws Exception { InputStream resourceAsStream = CountTest.class.getResourceAsStream("/rules/countGroupBy.frd"); diff --git a/src/test/java/com/rbkmoney/fraudo/SumTest.java b/src/test/java/com/rbkmoney/fraudo/SumTest.java index b34cf40..fcc0417 100644 --- a/src/test/java/com/rbkmoney/fraudo/SumTest.java +++ b/src/test/java/com/rbkmoney/fraudo/SumTest.java @@ -39,6 +39,16 @@ public class SumTest extends AbstractPaymentTest { Assert.assertEquals(0, result.getNotificationsRule().size()); } + @Test + public void sumChargeRefundTest() throws Exception { + InputStream resourceAsStream = SumTest.class.getResourceAsStream("/rules/sum_chargeback_refund.frd"); + Mockito.when(sumAggregator.sumChargeback(anyObject(), any(), any(), any())).thenReturn(10000.60); + Mockito.when(sumAggregator.sumRefund(anyObject(), any(), any(), any())).thenReturn(10000.60); + com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream); + ResultModel result = invokeParse(parseContext); + Assert.assertEquals(ResultStatus.ACCEPT, result.getResultStatus()); + } + @Test public void sumGroupByTest() throws Exception { InputStream resourceAsStream = SumTest.class.getResourceAsStream("/rules/sumGroupBy.frd"); diff --git a/src/test/resources/rules/amount.frd b/src/test/resources/rules/amount.frd index 35d7d1d..d2e71e8 100644 --- a/src/test/resources/rules/amount.frd +++ b/src/test/resources/rules/amount.frd @@ -1,2 +1,2 @@ -rule: amount() < 100 AND currency() == "RUB" +rule: amount() < 100 AND currency() = "RUB" -> accept; \ No newline at end of file diff --git a/src/test/resources/rules/count_chargeback_refund.frd b/src/test/resources/rules/count_chargeback_refund.frd new file mode 100644 index 0000000..2de5ba7 --- /dev/null +++ b/src/test/resources/rules/count_chargeback_refund.frd @@ -0,0 +1,2 @@ +rule: countChargeback("ip", 1444) > 3 OR countRefund("ip", 1444) > 1 +-> decline; \ No newline at end of file diff --git a/src/test/resources/rules/sum_chargeback_refund.frd b/src/test/resources/rules/sum_chargeback_refund.frd new file mode 100644 index 0000000..d7fdd94 --- /dev/null +++ b/src/test/resources/rules/sum_chargeback_refund.frd @@ -0,0 +1,2 @@ +rule: sumChargeback("ip", 1444) < 10500.50 AND sumRefund("ip", 1444) < 10500.50 +-> accept; \ No newline at end of file