mirror of
https://github.com/valitydev/adapter-common-lib.git
synced 2024-11-06 10:15:23 +00:00
429 lines
20 KiB
Markdown
429 lines
20 KiB
Markdown
|
# Общие данные по проекту при написании адаптера
|
|||
|
|
|||
|
### Оглавление:
|
|||
|
|
|||
|
- [Структура проекта](#Структура-проекта)
|
|||
|
- [Конфигурация](#configuration)
|
|||
|
- [AppConfiguration](#appconfiguration)
|
|||
|
- [HandlerConfiguration](#handlerconfiguration)
|
|||
|
- [ProcessorConfiguration](#processorconfiguration)
|
|||
|
- [Конвертеры](#converter)
|
|||
|
- [entry](#entry)
|
|||
|
- [exit](#exit)
|
|||
|
- [request](#request)
|
|||
|
- [flow](#flow)
|
|||
|
- [handler](#handler)
|
|||
|
- [processor](#processor)
|
|||
|
|
|||
|
|
|||
|
### Структура проекта
|
|||
|
|
|||
|
```
|
|||
|
├── src
|
|||
|
│ ├── main
|
|||
|
│ │ ├── java
|
|||
|
│ │ │ └── com
|
|||
|
│ │ │ └── rbkmoney
|
|||
|
│ │ │ └── adapter
|
|||
|
│ │ │ └── provider
|
|||
|
│ │ │ ├── AdapterProviderApplication.java
|
|||
|
│ │ │ ├── client - клиент для обращения к провайдеру
|
|||
|
│ │ │ │ ├── ProviderClient.java
|
|||
|
│ │ │ │ ├── ProviderClientException.java
|
|||
|
│ │ │ │ ├── constant
|
|||
|
│ │ │ │ └── model
|
|||
|
│ │ │ ├── configuration - конфигурационные файлы
|
|||
|
│ │ │ │ ├── AppConfiguration.java - основные конфигурации приложения
|
|||
|
│ │ │ │ ├── HandlerConfiguration.java - описание handler-ов
|
|||
|
│ │ │ │ ├── ProcessorConfiguration.java - описание processor-ов, они вкладываются друг в друга
|
|||
|
│ │ │ │ ├── RestTemplateConfiguration.java
|
|||
|
│ │ │ │ ├── TomcatEmbeddedConfiguration.java
|
|||
|
│ │ │ │ └── properties - параметры
|
|||
|
│ │ │ │ ├── IPayProperties.java
|
|||
|
│ │ │ │ └── RestTemplateProperties.java
|
|||
|
│ │ │ ├── converter
|
|||
|
│ │ │ │ ├── entry - конвертация входящих объектов в модель общего входа
|
|||
|
│ │ │ │ │ ├── CtxToEntryModelConverter.java
|
|||
|
│ │ │ │ │ └── RecCtxToEntryModelConverter.java
|
|||
|
│ │ │ │ ├── exit - конвертация исходящих объектов в модель общего выхода
|
|||
|
│ │ │ │ │ ├── ExitModelToProxyResultConverter.java
|
|||
|
│ │ │ │ │ └── ExitModelToRecTokenProxyResultConverter.java
|
|||
|
│ │ │ │ └── request - конвертация из модели общего входа в модели запросов к провайдеру
|
|||
|
│ │ │ │ ├── EntryStateToCancelRequestConverter.java
|
|||
|
│ │ │ │ ├── EntryStateToCaptureHandler.java
|
|||
|
│ │ │ │ ├── EntryStateToRefundRequestConverter.java
|
|||
|
│ │ │ │ ├── EntryStateToRegisterPreAuthRequestConverter.java
|
|||
|
│ │ │ │ ├── EntryStateToStatusRequestConverter.java
|
|||
|
│ │ │ ├── flow
|
|||
|
│ │ │ │ └── StepResolverImpl.java - определяет, какой шаг будет на входе и на выходе
|
|||
|
│ │ │ ├── handler - Обработчики шагов. Выполняют роль конвертации общей модели в запрос и его дальнейшее выполнение с обработкой полученного ответа
|
|||
|
│ │ │ │ ├── CancelHandler.java
|
|||
|
│ │ │ │ ├── CapturedHandler.java
|
|||
|
│ │ │ │ ├── PaymentOrderHandler.java
|
|||
|
│ │ │ │ ├── RefundHandler.java
|
|||
|
│ │ │ │ ├── RegisterPreAuthHandler.java
|
|||
|
│ │ │ │ ├── StatusHandler.java
|
|||
|
│ │ │ │ └── StepHandler.java
|
|||
|
│ │ │ ├── processor - процессоры отвечают за подготовку к общей модели выхода, описываются в ProcessorConfiguration
|
|||
|
│ │ │ │ ├── CommonErrorProcessor.java
|
|||
|
│ │ │ │ └── CommonProcessor.java
|
|||
|
│ │ │ ├── service
|
|||
|
│ │ │ │ └── AdapterIPayServerHandler.java - в нем описывается общая логика имплементирующая интерфейс для работы с провайдерами
|
|||
|
│ │ │ ├── servlet
|
|||
|
│ │ │ │ └── IPayAdapterServlet.java
|
|||
|
│ │ │ └── validator - необходимые проверки, например, что в опциях пришли ожидаемые параметры
|
|||
|
│ │ │ ├── PaymentContextValidator.java
|
|||
|
│ │ │ └── RecurrentTokenContextValidator.java
|
|||
|
│ │ └── resources
|
|||
|
│ │ ├── application.yml
|
|||
|
│ │ └── fixture
|
|||
|
│ │ └── errors.json - маппинг ошибок
|
|||
|
```
|
|||
|
|
|||
|
### configuration
|
|||
|
|
|||
|
#### AppConfiguration
|
|||
|
|
|||
|
основные конфигурации приложения, там находятся общие `beans`, например:
|
|||
|
```
|
|||
|
@Bean
|
|||
|
@ConfigurationProperties("time.config")
|
|||
|
public CommonTimerProperties commonTimerProperties() {
|
|||
|
return new CommonTimerProperties();
|
|||
|
}
|
|||
|
|
|||
|
@Bean
|
|||
|
public ExponentialBackOffPollingService<PollingInfo> exponentialBackOffPollingService() {
|
|||
|
return new ExponentialBackOffPollingService<>();
|
|||
|
}
|
|||
|
|
|||
|
@Bean
|
|||
|
public PaymentCallbackHandler paymentCallbackHandler(
|
|||
|
AdapterDeserializer adapterDeserializer,
|
|||
|
AdapterSerializer adapterSerializer,
|
|||
|
CallbackDeserializer callbackDeserializer
|
|||
|
) {
|
|||
|
return new PaymentCallbackHandler(adapterDeserializer, adapterSerializer, callbackDeserializer);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### HandlerConfiguration
|
|||
|
|
|||
|
Конфигурация каждого описанного раннее обработчика.
|
|||
|
В примере ниже описан обработчик для шага PRE_AUTH
|
|||
|
|
|||
|
```
|
|||
|
@Configuration
|
|||
|
@RequiredArgsConstructor
|
|||
|
public class HandlerConfiguration {
|
|||
|
|
|||
|
private final ProviderClient providerClient;
|
|||
|
|
|||
|
private final EntryStateToRegisterPreAuthRequestConverter entryStateToRegisterPreAuthRequestConverter;
|
|||
|
|
|||
|
@Bean
|
|||
|
public RegisterPreAuthHandler registerPreAuthHandler(Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> processor) {
|
|||
|
return new RegisterPreAuthHandler(providerClient, entryStateToRegisterPreAuthRequestConverter, processor);
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
#### ProcessorConfiguration
|
|||
|
|
|||
|
Описывается в цепочку вызовов, матрешечный метод
|
|||
|
|
|||
|
```
|
|||
|
@Configuration
|
|||
|
public class ProcessorConfiguration {
|
|||
|
@Bean
|
|||
|
public Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> commonErrorProcessor(ProviderClient providerClient) {
|
|||
|
Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> commonProcessor = new CommonProcessor(providerClient);
|
|||
|
return new CommonErrorProcessor(commonProcessor);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### Converter
|
|||
|
|
|||
|
#### entry
|
|||
|
|
|||
|
Содержит в себе классы, которые конвертируют входящие модели и структуры в общую модель входа GeneralEntryStateModel с которой далее и работаем.
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
@Component
|
|||
|
@RequiredArgsConstructor
|
|||
|
public class CtxToEntryModelConverter implements Converter<PaymentContext, GeneralEntryStateModel> {
|
|||
|
|
|||
|
private final CdsClientStorage cdsStorage;
|
|||
|
private final AdapterDeserializer adapterDeserializer;
|
|||
|
|
|||
|
@Override
|
|||
|
public GeneralEntryStateModel convert(PaymentContext context) {
|
|||
|
// any code
|
|||
|
return GeneralEntryStateModel.builder()
|
|||
|
.trxId(payment.getTrx() != null ? payment.getTrx().getId() : adapterContext.getTrxId())
|
|||
|
.trxExtra(payment.getTrx() != null ? payment.getTrx().getExtra() : new HashMap<>())
|
|||
|
.orderId(orderId)
|
|||
|
.amount(cash.getAmount())
|
|||
|
.refundAmount(cash.getAmount())
|
|||
|
.currencySymbolCode(payment.getCost().getCurrency().getSymbolicCode())
|
|||
|
.currencyCode(payment.getCost().getCurrency().getNumericCode())
|
|||
|
.createdAt(createdAt)
|
|||
|
.pan(cardData != null ? cardData.getPan() : null)
|
|||
|
.cvv2(CardDataUtils.extractCvv2(sessionData))
|
|||
|
.expYear(cardData != null ? cardData.getExpYear() : null)
|
|||
|
.expMonth(cardData != null ? cardData.getExpMonth() : null)
|
|||
|
.cardHolder(cardData != null ? cardData.getCardholderName() : null)
|
|||
|
.email(PaymentDataConverter.extractEmail(context.getPaymentInfo()))
|
|||
|
.options(paymentContext.getOptions())
|
|||
|
.makeRecurrent(payment.make_recurrent)
|
|||
|
.callbackUrl(redirectUrl)
|
|||
|
.ip(ipAddress)
|
|||
|
.trxExtra(extra)
|
|||
|
.adapterContext(adapterContext)
|
|||
|
.targetStatus(targetStatus)
|
|||
|
.build();
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
#### exit
|
|||
|
|
|||
|
Содержит в себе классы, которые конвертируют из общей модели выхода в PaymentProxyResult, в них производится анализ на основании шага (step), какой intent подготовить
|
|||
|
|
|||
|
```
|
|||
|
@Slf4j
|
|||
|
@Component
|
|||
|
@RequiredArgsConstructor
|
|||
|
public class ExitModelToProxyResultConverter implements Converter<GeneralExitStateModel, PaymentProxyResult> {
|
|||
|
|
|||
|
private final CommonTimerProperties timerProperties;
|
|||
|
private final ErrorMapping errorMapping;
|
|||
|
private final AdapterSerializer adapterSerializer;
|
|||
|
private final ExponentialBackOffPollingService<PollingInfo> exponentialBackOffPollingService;
|
|||
|
|
|||
|
@Override
|
|||
|
public PaymentProxyResult convert(GeneralExitStateModel exitStateModel) {
|
|||
|
log.info("PaymentProxy exit state model: {}", exitStateModel);
|
|||
|
if (!StringUtils.isEmpty(exitStateModel.getErrorCode())) {
|
|||
|
log.info("Exit error code={}, message={}", exitStateModel.getErrorCode(), exitStateModel.getErrorMessage());
|
|||
|
Failure failure = errorMapping.mapFailure(exitStateModel.getErrorCode());
|
|||
|
return createProxyResultFailure(failure);
|
|||
|
}
|
|||
|
|
|||
|
AdapterContext adapterContext = exitStateModel.getAdapterContext();
|
|||
|
Step nextStep = exitStateModel.getAdapterContext().getStep();
|
|||
|
|
|||
|
Intent intent;
|
|||
|
switch (nextStep) {
|
|||
|
case AUTH:
|
|||
|
intent = createIntentWithSleepIntent(0);
|
|||
|
break;
|
|||
|
case FINISH_THREE_DS:
|
|||
|
intent = createIntentWithSuspendIntent(exitStateModel);
|
|||
|
break;
|
|||
|
case CHECK_STATUS:
|
|||
|
case CAPTURE:
|
|||
|
case REFUND:
|
|||
|
case CANCEL:
|
|||
|
case DO_NOTHING:
|
|||
|
intent = createFinishIntentSuccess();
|
|||
|
break;
|
|||
|
default:
|
|||
|
throw new IllegalStateException("Wrong next step: " + nextStep);
|
|||
|
}
|
|||
|
|
|||
|
PaymentProxyResult paymentProxyResult = new PaymentProxyResult(intent).setNextState(adapterSerializer.writeByte(adapterContext));
|
|||
|
paymentProxyResult.setTrx(createTransactionInfo(adapterContext.getTrxId(), exitStateModel.getTrxExtra()));
|
|||
|
log.info("Payment proxy result: {}", paymentProxyResult);
|
|||
|
return paymentProxyResult;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### request
|
|||
|
|
|||
|
Содержит классы, которые из общей модели заполняют структуру для подготовки запроса к провайдеру
|
|||
|
|
|||
|
Например:
|
|||
|
```
|
|||
|
@Component
|
|||
|
public class EntryStateToCancelRequestConverter implements Converter<GeneralEntryStateModel, Request> {
|
|||
|
|
|||
|
@Override
|
|||
|
public Request convert(GeneralEntryStateModel entryStateModel) {
|
|||
|
Map<String, String> options = entryStateModel.getOptions();
|
|||
|
String userName = options.get(OptionField.LOGIN.getField());
|
|||
|
String password = options.get(OptionField.PASSWORD.getField());
|
|||
|
|
|||
|
return Request.builder()
|
|||
|
.userName(userName)
|
|||
|
.password(password)
|
|||
|
.orderId(entryStateModel.getTrxId())
|
|||
|
.build();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
## flow
|
|||
|
|
|||
|
StepResolverImpl - определяет шаг в самом начале и устанавливает шаг на выходе
|
|||
|
|
|||
|
```
|
|||
|
@Slf4j
|
|||
|
@Component
|
|||
|
public class StepResolverImpl implements StepResolver<GeneralEntryStateModel, GeneralExitStateModel> {
|
|||
|
|
|||
|
@Override
|
|||
|
public Step resolveEntry(GeneralEntryStateModel entryStateModel) {
|
|||
|
TargetStatus targetStatus = entryStateModel.getTargetStatus();
|
|||
|
AdapterContext adapterContext = entryStateModel.getAdapterContext();
|
|||
|
Step step = adapterContext.getStep();
|
|||
|
|
|||
|
if (targetStatus == null) {
|
|||
|
return Objects.requireNonNullElse(step, Step.PRE_AUTH);
|
|||
|
}
|
|||
|
|
|||
|
log.info("Entry resolver. Target status: {}. Step: {}", targetStatus, step);
|
|||
|
switch (targetStatus) {
|
|||
|
case PROCESSED:
|
|||
|
return Objects.requireNonNullElse(step, Step.PRE_AUTH);
|
|||
|
case CAPTURED:
|
|||
|
return Step.CAPTURE;
|
|||
|
case CANCELLED:
|
|||
|
return Step.CANCEL;
|
|||
|
case REFUNDED:
|
|||
|
return Step.REFUND;
|
|||
|
default:
|
|||
|
throw new IllegalStateException("Unknown target status: " + targetStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public Step resolveExit(GeneralExitStateModel exitStateModel) {
|
|||
|
GeneralEntryStateModel entryStateModel = exitStateModel.getGeneralEntryStateModel();
|
|||
|
Step step = entryStateModel.getAdapterContext().getStep();
|
|||
|
log.info("Exit resolver. Step: {}", step);
|
|||
|
switch (step) {
|
|||
|
case PRE_AUTH:
|
|||
|
return Step.AUTH;
|
|||
|
case AUTH:
|
|||
|
return Step.FINISH_THREE_DS;
|
|||
|
case CAPTURE:
|
|||
|
return Step.CAPTURE;
|
|||
|
case FINISH_THREE_DS:
|
|||
|
return Step.CHECK_STATUS;
|
|||
|
case CHECK_STATUS:
|
|||
|
return Step.DO_NOTHING;
|
|||
|
default:
|
|||
|
return step;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
#### handler
|
|||
|
|
|||
|
Обработчики шагов.
|
|||
|
|
|||
|
Выполняют роль конвертации общей модели в запрос и его дальнейшее выполнение с обработкой полученного ответа
|
|||
|
Handler'ы описываются в CommonHandler, то есть если не использовать CommonHandler, можно и не использовать processor и converter
|
|||
|
|
|||
|
В примере ниже видно, что данный класс будет использован, если удовлетворит условию `step == Step.CANCEL`,
|
|||
|
т.е. в данном случае будет выбран обработчик для вызова отмены платежа, после того, как будет известен обработчик,
|
|||
|
у него будет вызван метод handle(GeneralEntryStateModel entryStateModel) находящийся в `StepHandler`
|
|||
|
|
|||
|
Передаем в `StepHandler<Request, Response>`(который имплементирует CommonHandler) объекты, которые ожидаем в качестве запроса и ответа от провайдера
|
|||
|
|
|||
|
Видно, что в качестве объекта для запроса используется класс Request, в качестве ожидаемого ответа Response
|
|||
|
|
|||
|
Для обращения к провайдеру используется клиент `ProviderClient` с вызовом метода `reverse`.
|
|||
|
|
|||
|
Конвертор заполнит модель Request данными из модели GeneralEntryStateModel
|
|||
|
|
|||
|
Процессор на основании полученного `Response` и `GeneralEntryStateModel` заполнит `GeneralExitStateModel`
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```
|
|||
|
public class CancelHandler extends StepHandler<Request, Response> {
|
|||
|
|
|||
|
public CancelHandler(
|
|||
|
ProviderClient providerClient,
|
|||
|
Converter<GeneralEntryStateModel, Request> converter,
|
|||
|
Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> processor
|
|||
|
) {
|
|||
|
super(providerClient::reverse, converter, processor);
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public boolean isHandle(GeneralEntryStateModel entryStateModel) {
|
|||
|
Step step = entryStateModel.getAdapterContext().getStep();
|
|||
|
return step == Step.CANCEL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
ps важно помнить, что после добавлении нового handler-а, нужно его прописать в [HandlerConfiguration](#handlerconfiguration),
|
|||
|
но до этого, придется еще написать [entry](#entry) [converter-ы](#converter) для [handler-а](#handler), описать [processor](#processor) и модель ответа, нашем случае это Response
|
|||
|
|
|||
|
|
|||
|
#### Processor
|
|||
|
|
|||
|
Процессоры отвечают за подготовку к общей модели выхода (GeneralExitStateModel), описываются в папке `processor`,
|
|||
|
а матрешочное поведение описывается в конфигурации `ProcessorConfiguration`.
|
|||
|
|
|||
|
Важно помнить, что после создания процессора, его необходимо добавить в [конфигурацию](#processorconfiguration).
|
|||
|
|
|||
|
Пример processor-а:
|
|||
|
```
|
|||
|
@Slf4j
|
|||
|
@RequiredArgsConstructor
|
|||
|
public class CommonErrorProcessor implements Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> {
|
|||
|
|
|||
|
private final Processor<GeneralExitStateModel, Response, GeneralEntryStateModel> nextProcessor;
|
|||
|
|
|||
|
@Override
|
|||
|
public GeneralExitStateModel process(Response response, GeneralEntryStateModel entryStateModel) {
|
|||
|
log.info("Order response: {}", response);
|
|||
|
AdapterContext adapterContext = entryStateModel.getAdapterContext();
|
|||
|
|
|||
|
if(response.getErrorCode() != null) {
|
|||
|
ErrorCode errorCode = ErrorCode.findByCode(response.getErrorCode());
|
|||
|
return GeneralExitStateModel.builder()
|
|||
|
.errorCode(response.getErrorCode())
|
|||
|
.errorMessage(errorCode.getDescription())
|
|||
|
.adapterContext(adapterContext)
|
|||
|
.generalEntryStateModel(entryStateModel)
|
|||
|
.trxExtra(new HashMap<>(entryStateModel.getTrxExtra()))
|
|||
|
.build();
|
|||
|
} else if (nextProcessor != null) {
|
|||
|
return nextProcessor.process(response, entryStateModel);
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Таким образом видно, что, если условие с ошибкой не отработало
|
|||
|
```
|
|||
|
if(response.getErrorCode() != null)
|
|||
|
```
|
|||
|
то, будет вызван следующий processor
|
|||
|
```
|
|||
|
return nextProcessor.process(response, entryStateModel);
|
|||
|
```
|
|||
|
|
|||
|
Что, собственно, и описывается в конфигурации, см. раздел [конфигурации](#processorconfiguration)
|