mirror of
https://github.com/valitydev/adapter-bank-payout-spring-boot-starter.git
synced 2024-11-06 00:45:21 +00:00
parent
1d0f8a8c05
commit
77343b02c8
8
pom.xml
8
pom.xml
@ -71,7 +71,7 @@
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>adapter-common-lib</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>0.0.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
@ -79,6 +79,12 @@
|
||||
<version>${error-mapping.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.geck</groupId>
|
||||
<artifactId>serializer</artifactId>
|
||||
<version>0.6.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>damsel</artifactId>
|
||||
|
@ -1,8 +1,6 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.common.mapper.SimpleErrorMapping;
|
||||
import com.rbkmoney.adapter.common.mapper.SimpleObjectMapper;
|
||||
import com.rbkmoney.error.mapping.ErrorMapping;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -23,6 +21,6 @@ public class ErrorMappingProperties {
|
||||
|
||||
@Bean
|
||||
public ErrorMapping errorMapping() throws IOException {
|
||||
return new SimpleErrorMapping(errorMappingFilePath, errorMappingPattern).getErrorMapping();
|
||||
return new SimpleErrorMapping(errorMappingFilePath, errorMappingPattern).createErrorMapping();
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.Step;
|
||||
|
||||
public interface StepResolver<T extends EntryStateModel, X extends ExitStateModel> {
|
||||
Step resolveEntry(T entryStateModel);
|
||||
|
||||
Step resolveExit(X exitStateModel);
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.ExitStateModel
|
||||
|
||||
public interface CommonHandler<T extends EntryStateModel, X extends ExitStateModel> {
|
||||
boolean isHandle(T entryStateModel);
|
||||
|
||||
X handle(T entryStateModel);
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.rbkmoney.adapter.common.model.PollingInfo;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AdapterState {
|
||||
|
||||
private Step step;
|
||||
// TODO: backward compatibility
|
||||
private Long maxTimePoolingMillis;
|
||||
|
||||
private TransactionInfo trxInfo;
|
||||
|
||||
@JsonUnwrapped
|
||||
private PollingInfo pollingInfo;
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.serializer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.AdapterState;
|
||||
import com.rbkmoney.adapter.common.serializer.StateSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AdapterStateSerializer extends StateSerializer<AdapterState> {
|
||||
|
||||
public AdapterStateSerializer(ObjectMapper mapper) {
|
||||
super(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdapterState read(byte[] data) {
|
||||
if (data == null) {
|
||||
return new AdapterState();
|
||||
}
|
||||
try {
|
||||
return getMapper().readValue(data, AdapterState.class);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,18 @@ import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.EntryStateMode
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.ExitStateModel;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.Intent;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public interface IntentService {
|
||||
Intent getFailureByCode(ExitStateModel exitStateModel);
|
||||
|
||||
Intent getFailureByCodeAndDesc(ExitStateModel exitStateModel);
|
||||
|
||||
Intent getSuccess(ExitStateModel exitStateModel);
|
||||
|
||||
Intent getSleep(ExitStateModel exitStateModel);
|
||||
Long getMaxDateTimeInstant(EntryStateModel entryStateModel);
|
||||
|
||||
Long getMaxDateTimeInstantMillis(EntryStateModel entryStateModel);
|
||||
|
||||
Instant extractMaxDateTimeInstant(EntryStateModel entryStateModel);
|
||||
}
|
||||
|
@ -3,11 +3,15 @@ package com.rbkmoney.adapter.bank.payout.spring.boot.starter.service;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties.TimerProperties;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.EntryStateModel;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.ExitStateModel;
|
||||
import com.rbkmoney.damsel.base.Timer;
|
||||
import com.rbkmoney.adapter.common.model.PollingInfo;
|
||||
import com.rbkmoney.adapter.common.utils.times.ExponentialBackOffPollingService;
|
||||
import com.rbkmoney.damsel.domain.TransactionInfo;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.*;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.FinishIntent;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.FinishStatus;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.Intent;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.Success;
|
||||
import com.rbkmoney.error.mapping.ErrorMapping;
|
||||
import com.rbkmoney.java.damsel.utils.extractors.OptionsExtractors;
|
||||
import com.rbkmoney.java.damsel.utils.creators.WithdrawalsProviderAdapterPackageCreators;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
@ -37,21 +41,38 @@ public class IntentServiceImpl implements IntentService {
|
||||
}
|
||||
|
||||
public Intent getSleep(ExitStateModel exitStateModel) {
|
||||
if (exitStateModel.getNextState().getMaxTimePoolingMillis() == null) {
|
||||
throw new IllegalArgumentException("Need to specify 'maxTimePoolingMillis' before sleep");
|
||||
Instant maxDateTimePolling = exitStateModel.getNextState().getPollingInfo().getMaxDateTimePolling();
|
||||
if (maxDateTimePolling == null) {
|
||||
throw new IllegalArgumentException("Need to specify 'maxDateTimePolling' before sleep");
|
||||
}
|
||||
if (exitStateModel.getNextState().getMaxTimePoolingMillis() < Instant.now().toEpochMilli()) {
|
||||
if (maxDateTimePolling.toEpochMilli() < Instant.now().toEpochMilli()) {
|
||||
return prepareFailureIntent();
|
||||
}
|
||||
int timerPollingDelay = computePollingInterval(exitStateModel);
|
||||
return WithdrawalsProviderAdapterPackageCreators.createIntentWithSleepIntent(timerPollingDelay);
|
||||
}
|
||||
|
||||
private Intent prepareFailureIntent() {
|
||||
String code = "Sleep timeout";
|
||||
String reason = "Max time pool limit reached";
|
||||
return Intent.finish(new FinishIntent(FinishStatus.failure(errorMapping.mapFailure(code, reason))));
|
||||
}
|
||||
|
||||
int timerPollingDelay = OptionsExtractors.extractPollingDelay(exitStateModel.getEntryStateModel().getOptions(), timerProperties.getPollingDelay());
|
||||
return Intent.sleep(new SleepIntent(new Timer(Timer.timeout(timerPollingDelay))));
|
||||
private int computePollingInterval(ExitStateModel exitStateModel) {
|
||||
ExponentialBackOffPollingService<PollingInfo> pollingService = new ExponentialBackOffPollingService<>();
|
||||
return pollingService.prepareNextPollingInterval(
|
||||
exitStateModel.getNextState().getPollingInfo(),
|
||||
exitStateModel.getEntryStateModel().getOptions()
|
||||
);
|
||||
}
|
||||
|
||||
public Long getMaxDateTimeInstant(EntryStateModel entryStateModel) {
|
||||
public Long getMaxDateTimeInstantMillis(EntryStateModel entryStateModel) {
|
||||
int maxTimePolling = extractMaxTimePolling(entryStateModel.getOptions(), timerProperties.getMaxTimePolling());
|
||||
return Instant.now().plus(maxTimePolling, ChronoUnit.MINUTES).toEpochMilli();
|
||||
}
|
||||
|
||||
public Instant extractMaxDateTimeInstant(EntryStateModel entryStateModel) {
|
||||
int maxTimePolling = extractMaxTimePolling(entryStateModel.getOptions(), timerProperties.getMaxTimePolling());
|
||||
return Instant.now().plus(maxTimePolling, ChronoUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.state.deserializer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.AdapterState;
|
||||
import com.rbkmoney.adapter.common.state.deserializer.DeserializationException;
|
||||
import com.rbkmoney.adapter.common.state.deserializer.Deserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class AdapterStateDeserializer implements Deserializer<AdapterState> {
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public AdapterState read(byte[] data) {
|
||||
if (data == null) {
|
||||
return new AdapterState();
|
||||
} else {
|
||||
try {
|
||||
return this.getMapper().readValue(data, AdapterState.class);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AdapterState read(String data) {
|
||||
throw new DeserializationException("Deserialization not supported");
|
||||
}
|
||||
|
||||
public ObjectMapper getMapper() {
|
||||
return this.mapper;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.state.serializer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.AdapterState;
|
||||
import com.rbkmoney.adapter.common.state.serializer.StateSerializer;
|
||||
|
||||
public class AdapterStateSerializer extends StateSerializer<AdapterState> {
|
||||
public AdapterStateSerializer(ObjectMapper mapper) {
|
||||
super(mapper);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.state.utils;
|
||||
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.AdapterState;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.state.deserializer.AdapterStateDeserializer;
|
||||
import com.rbkmoney.damsel.proxy_provider.PaymentContext;
|
||||
import com.rbkmoney.damsel.proxy_provider.RecurrentTokenContext;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class AdapterStateUtils {
|
||||
|
||||
public static AdapterState getAdapterState(Object context, AdapterStateDeserializer adapterDeserializer) {
|
||||
AdapterState adapterState = new AdapterState();
|
||||
byte[] state = getState(context);
|
||||
if (state != null && state.length > 0) {
|
||||
return adapterDeserializer.read(state);
|
||||
}
|
||||
return adapterState;
|
||||
}
|
||||
|
||||
private static byte[] getState(Object context) {
|
||||
if (context instanceof RecurrentTokenContext) {
|
||||
if (((RecurrentTokenContext) context).getSession() == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
return ((RecurrentTokenContext) context).getSession().getState();
|
||||
}
|
||||
return ((PaymentContext) context).getSession().getState();
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
[
|
||||
{
|
||||
"code":"unknown",
|
||||
"description":"unknown",
|
||||
"regexp":".*",
|
||||
"mapping":"authorization_failed:unknown"
|
||||
"codeRegex": "unknown",
|
||||
"descriptionRegex": "unknown",
|
||||
"mapping": "ResultUnknown"
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,44 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.common.mapper.SimpleObjectMapper;
|
||||
import com.rbkmoney.adapter.common.model.PollingInfo;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class AdapterStateTest {
|
||||
|
||||
@Test
|
||||
public void testUnwrappedPollingInfo() throws IOException {
|
||||
ObjectMapper om = new SimpleObjectMapper().createSimpleObjectMapperFactory();
|
||||
AdapterState as = new AdapterState();
|
||||
as.setStep(Step.CHECK);
|
||||
PollingInfo pollingInfo = new PollingInfo();
|
||||
pollingInfo.setMaxDateTimePolling(Instant.now());
|
||||
as.setPollingInfo(pollingInfo);
|
||||
String str = om.writeValueAsString(as);
|
||||
assertTrue(str.startsWith("{\"step\":\"CHECK\",\"max_date_time_polling\":"));
|
||||
AdapterState acRestored = om.readValue(str, AdapterState.class);
|
||||
assertEquals(as.getStep(), acRestored.getStep());
|
||||
assertNotNull(acRestored.getPollingInfo());
|
||||
assertEquals(as.getPollingInfo().getMaxDateTimePolling(), acRestored.getPollingInfo().getMaxDateTimePolling());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnwrappedPollingInfoIsNull() throws IOException {
|
||||
ObjectMapper om = new SimpleObjectMapper().createSimpleObjectMapperFactory();
|
||||
AdapterState as = new AdapterState();
|
||||
as.setStep(Step.CHECK);
|
||||
as.setPollingInfo(null);
|
||||
String str = om.writeValueAsString(as);
|
||||
assertTrue(str.startsWith("{\"step\":\"CHECK\""));
|
||||
AdapterState acRestored = om.readValue(str, AdapterState.class);
|
||||
assertEquals(as.getStep(), acRestored.getStep());
|
||||
assertNotNull(acRestored.getPollingInfo());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.rbkmoney.adapter.bank.payout.spring.boot.starter.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties.TimerProperties;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.AdapterState;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.EntryStateModel;
|
||||
import com.rbkmoney.adapter.bank.payout.spring.boot.starter.model.ExitStateModel;
|
||||
import com.rbkmoney.adapter.common.mapper.SimpleObjectMapper;
|
||||
import com.rbkmoney.adapter.common.model.PollingInfo;
|
||||
import com.rbkmoney.damsel.withdrawals.provider_adapter.Intent;
|
||||
import com.rbkmoney.error.mapping.ErrorMapping;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IntentServiceImplTest {
|
||||
|
||||
private static final String ERROR_MAPPING_FILE_PATH = "src/test/resources/fixture/errors.json";
|
||||
private static final String ERROR_MAPPING_PATTERN = "'%s' - '%s'";
|
||||
|
||||
private IntentServiceImpl intentService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
intentService = new IntentServiceImpl(prepareErrorMapping(), prepareTimerProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSleepIntentSuccess() {
|
||||
ExitStateModel exitStateModel = new ExitStateModel();
|
||||
EntryStateModel entryStateModel = new EntryStateModel();
|
||||
entryStateModel.setOptions(new HashMap<>());
|
||||
exitStateModel.setEntryStateModel(entryStateModel);
|
||||
AdapterState adapterState = new AdapterState();
|
||||
PollingInfo pollingInfo = new PollingInfo();
|
||||
pollingInfo.setMaxDateTimePolling(Instant.now().plus(10000L, ChronoUnit.MINUTES));
|
||||
adapterState.setPollingInfo(pollingInfo);
|
||||
exitStateModel.setNextState(adapterState);
|
||||
Intent intent = intentService.getSleep(exitStateModel);
|
||||
assertTrue(intent.isSetSleep());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSleepIntentFailure() {
|
||||
ExitStateModel exitStateModel = new ExitStateModel();
|
||||
AdapterState adapterState = new AdapterState();
|
||||
PollingInfo pollingInfo = new PollingInfo();
|
||||
pollingInfo.setMaxDateTimePolling(Instant.now().minus(1000L, ChronoUnit.MINUTES));
|
||||
adapterState.setPollingInfo(pollingInfo);
|
||||
exitStateModel.setNextState(adapterState);
|
||||
Intent intent = intentService.getSleep(exitStateModel);
|
||||
assertTrue(intent.getFinish().getStatus().isSetFailure());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getSleepException() {
|
||||
ExitStateModel exitStateModel = new ExitStateModel();
|
||||
AdapterState adapterState = new AdapterState();
|
||||
adapterState.setPollingInfo(new PollingInfo());
|
||||
exitStateModel.setNextState(adapterState);
|
||||
intentService.getSleep(exitStateModel);
|
||||
}
|
||||
|
||||
private ErrorMapping prepareErrorMapping() throws IOException {
|
||||
ObjectMapper mapper = new SimpleObjectMapper().createSimpleObjectMapperFactory();
|
||||
File file = new File(ERROR_MAPPING_FILE_PATH);
|
||||
InputStream is = new FileInputStream(file);
|
||||
ErrorMapping errorMapping = new ErrorMapping(is, ERROR_MAPPING_PATTERN, mapper);
|
||||
errorMapping.validateMapping();
|
||||
return errorMapping;
|
||||
}
|
||||
|
||||
private TimerProperties prepareTimerProperties() {
|
||||
TimerProperties timerProperties = new TimerProperties();
|
||||
timerProperties.setMaxTimePolling(600);
|
||||
timerProperties.setPollingDelay(10);
|
||||
return timerProperties;
|
||||
}
|
||||
|
||||
}
|
12
src/test/resources/fixture/errors.json
Normal file
12
src/test/resources/fixture/errors.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"codeRegex": "Sleep timeout",
|
||||
"descriptionRegex": "Max time pool limit reached",
|
||||
"mapping": "authorization_failed:operation_blocked"
|
||||
},
|
||||
{
|
||||
"codeRegex": "unknown",
|
||||
"descriptionRegex": "unknown",
|
||||
"mapping": "ResultUnknown"
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user