From a4ebf09a279375866660d89e32b65a0a4c9f774e Mon Sep 17 00:00:00 2001 From: kostyastruga Date: Wed, 8 Jul 2026 17:27:00 +0300 Subject: [PATCH 1/2] Add exchange rate --- pom.xml | 2 +- .../iface/PaymentExchangeContextDao.java | 17 ++++ .../impl/PaymentExchangeContextDaoImpl.java | 82 +++++++++++++++++++ .../factory/cash/flow/CashFlowFactory.java | 6 ++ .../PaymentExchangeContextWrapperHandler.java | 39 +++++++++ ...cePaymentExchangeContextChangedMapper.java | 73 +++++++++++++++++ .../vality/daway/model/PaymentWrapper.java | 1 + .../V52__add_payment_exchange_context.sql | 24 ++++++ .../cash/flow/CashFlowFactoryTest.java | 56 +++++++++++++ ...ymentExchangeContextChangedMapperTest.java | 57 +++++++++++++ .../service/PaymentWrapperServiceTest.java | 47 +++++++++++ .../daway/utils/PaymentWrapperTestUtil.java | 7 ++ 12 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/vality/daway/dao/invoicing/iface/PaymentExchangeContextDao.java create mode 100644 src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java create mode 100644 src/main/java/dev/vality/daway/handler/wrapper/payment/PaymentExchangeContextWrapperHandler.java create mode 100644 src/main/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapper.java create mode 100644 src/main/resources/db/migration/V52__add_payment_exchange_context.sql create mode 100644 src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java create mode 100644 src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java diff --git a/pom.xml b/pom.xml index 1c8e6176..b8887d15 100644 --- a/pom.xml +++ b/pom.xml @@ -177,7 +177,7 @@ dev.vality damsel - 1.692-70b59b9 + 1.695-25e75a5 dev.vality diff --git a/src/main/java/dev/vality/daway/dao/invoicing/iface/PaymentExchangeContextDao.java b/src/main/java/dev/vality/daway/dao/invoicing/iface/PaymentExchangeContextDao.java new file mode 100644 index 00000000..32061aea --- /dev/null +++ b/src/main/java/dev/vality/daway/dao/invoicing/iface/PaymentExchangeContextDao.java @@ -0,0 +1,17 @@ +package dev.vality.daway.dao.invoicing.iface; + +import dev.vality.dao.GenericDao; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; +import dev.vality.daway.exception.DaoException; +import dev.vality.daway.model.InvoicingKey; + +import java.util.List; +import java.util.Set; + +public interface PaymentExchangeContextDao extends GenericDao { + + void saveBatch(List paymentExchangeContexts) throws DaoException; + + void switchCurrent(Set invoicingKeys) throws DaoException; + +} diff --git a/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java b/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java new file mode 100644 index 00000000..f522b425 --- /dev/null +++ b/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java @@ -0,0 +1,82 @@ +package dev.vality.daway.dao.invoicing.impl; + +import dev.vality.dao.impl.AbstractGenericDao; +import dev.vality.daway.dao.invoicing.iface.PaymentExchangeContextDao; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; +import dev.vality.daway.domain.tables.records.PaymentExchangeContextRecord; +import dev.vality.daway.exception.DaoException; +import dev.vality.daway.model.InvoicingKey; +import dev.vality.mapper.RecordRowMapper; +import org.jooq.Query; +import org.jooq.impl.DSL; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static dev.vality.daway.domain.Tables.PAYMENT_EXCHANGE_CONTEXT; + +@Component +public class PaymentExchangeContextDaoImpl extends AbstractGenericDao implements PaymentExchangeContextDao { + + private final RowMapper paymentExchangeContextRowMapper; + + public PaymentExchangeContextDaoImpl(DataSource dataSource) { + super(dataSource); + paymentExchangeContextRowMapper = new RecordRowMapper<>(PAYMENT_EXCHANGE_CONTEXT, PaymentExchangeContext.class); + } + + @Override + public void saveBatch(List paymentExchangeContexts) throws DaoException { + List queries = paymentExchangeContexts.stream() + .map(paymentExchangeContext -> getDslContext().newRecord( + PAYMENT_EXCHANGE_CONTEXT, paymentExchangeContext)) + .map(this::prepareInsertQuery) + .collect(Collectors.toList()); + batchExecute(queries); + } + + private Query prepareInsertQuery(PaymentExchangeContextRecord record) { + return getDslContext().insertInto(PAYMENT_EXCHANGE_CONTEXT) + .set(record) + .onConflict( + PAYMENT_EXCHANGE_CONTEXT.INVOICE_ID, + PAYMENT_EXCHANGE_CONTEXT.PAYMENT_ID, + PAYMENT_EXCHANGE_CONTEXT.SEQUENCE_ID, + PAYMENT_EXCHANGE_CONTEXT.CHANGE_ID + ) + .doNothing(); + } + + @Override + public void switchCurrent(Set invoicingKeys) throws DaoException { + invoicingKeys.forEach(key -> { + setOldPaymentExchangeContextNotCurrent(key); + setLatestPaymentExchangeContextCurrent(key); + }); + } + + private void setOldPaymentExchangeContextNotCurrent(InvoicingKey key) { + execute(getDslContext().update(PAYMENT_EXCHANGE_CONTEXT) + .set(PAYMENT_EXCHANGE_CONTEXT.CURRENT, false) + .where(PAYMENT_EXCHANGE_CONTEXT.INVOICE_ID.eq(key.getInvoiceId()) + .and(PAYMENT_EXCHANGE_CONTEXT.PAYMENT_ID.eq(key.getPaymentId())) + .and(PAYMENT_EXCHANGE_CONTEXT.CURRENT)) + ); + } + + private void setLatestPaymentExchangeContextCurrent(InvoicingKey key) { + execute(getDslContext().update(PAYMENT_EXCHANGE_CONTEXT) + .set(PAYMENT_EXCHANGE_CONTEXT.CURRENT, true) + .where(PAYMENT_EXCHANGE_CONTEXT.ID.eq( + DSL.select(DSL.max(PAYMENT_EXCHANGE_CONTEXT.ID)) + .from(PAYMENT_EXCHANGE_CONTEXT) + .where(PAYMENT_EXCHANGE_CONTEXT.INVOICE_ID.eq(key.getInvoiceId()) + .and(PAYMENT_EXCHANGE_CONTEXT.PAYMENT_ID.eq(key.getPaymentId()))) + )) + ); + } +} diff --git a/src/main/java/dev/vality/daway/factory/cash/flow/CashFlowFactory.java b/src/main/java/dev/vality/daway/factory/cash/flow/CashFlowFactory.java index 6fc56213..e9df3225 100644 --- a/src/main/java/dev/vality/daway/factory/cash/flow/CashFlowFactory.java +++ b/src/main/java/dev/vality/daway/factory/cash/flow/CashFlowFactory.java @@ -37,6 +37,12 @@ public static List build(List cashFlowPostings, pcf.setAmount(cf.getVolume().getAmount()); pcf.setCurrencyCode(cf.getVolume().getCurrency().getSymbolicCode()); pcf.setDetails(cf.getDetails()); + if (cf.isSetExchangeContext()) { + pcf.setExchangeSourceCurrencyCode(cf.getExchangeContext().getSourceCurrency()); + pcf.setExchangeDestinationCurrencyCode(cf.getExchangeContext().getDestinationCurrency()); + pcf.setExchangeRateRationalP(cf.getExchangeContext().getExchangeRate().getP()); + pcf.setExchangeRateRationalQ(cf.getExchangeContext().getExchangeRate().getQ()); + } return pcf; }).collect(Collectors.toList()); } diff --git a/src/main/java/dev/vality/daway/handler/wrapper/payment/PaymentExchangeContextWrapperHandler.java b/src/main/java/dev/vality/daway/handler/wrapper/payment/PaymentExchangeContextWrapperHandler.java new file mode 100644 index 00000000..4b4e44f6 --- /dev/null +++ b/src/main/java/dev/vality/daway/handler/wrapper/payment/PaymentExchangeContextWrapperHandler.java @@ -0,0 +1,39 @@ +package dev.vality.daway.handler.wrapper.payment; + +import dev.vality.daway.dao.invoicing.iface.PaymentExchangeContextDao; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; +import dev.vality.daway.handler.wrapper.WrapperHandler; +import dev.vality.daway.model.PaymentWrapper; +import dev.vality.daway.util.PaymentWrapperUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Component +public class PaymentExchangeContextWrapperHandler implements WrapperHandler { + + private final PaymentExchangeContextDao paymentExchangeContextDao; + + @Override + public boolean accept(List wrappers) { + return wrappers.stream() + .map(PaymentWrapper::getPaymentExchangeContext) + .anyMatch(Objects::nonNull); + } + + @Override + public void saveBatch(List wrappers) { + List processableWrappers = wrappers.stream() + .filter(paymentWrapper -> Objects.nonNull(paymentWrapper.getPaymentExchangeContext())) + .collect(Collectors.toList()); + List paymentExchangeContexts = processableWrappers.stream() + .map(PaymentWrapper::getPaymentExchangeContext) + .collect(Collectors.toList()); + paymentExchangeContextDao.saveBatch(paymentExchangeContexts); + paymentExchangeContextDao.switchCurrent(PaymentWrapperUtil.getInvoicingKeys(processableWrappers)); + } +} diff --git a/src/main/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapper.java b/src/main/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapper.java new file mode 100644 index 00000000..5e2873e4 --- /dev/null +++ b/src/main/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapper.java @@ -0,0 +1,73 @@ +package dev.vality.daway.mapper.payment; + +import dev.vality.damsel.base.Rational; +import dev.vality.damsel.domain.ExchangeContext; +import dev.vality.damsel.payment_processing.InvoiceChange; +import dev.vality.damsel.payment_processing.InvoicePaymentChange; +import dev.vality.damsel.payment_processing.InvoicePaymentExchangeContextChanged; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; +import dev.vality.daway.mapper.Mapper; +import dev.vality.daway.model.InvoicingKey; +import dev.vality.daway.model.PaymentWrapper; +import dev.vality.geck.common.util.TypeUtil; +import dev.vality.geck.filter.Filter; +import dev.vality.geck.filter.PathConditionFilter; +import dev.vality.geck.filter.condition.IsNullCondition; +import dev.vality.geck.filter.rule.PathConditionRule; +import dev.vality.machinegun.eventsink.MachineEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class InvoicePaymentExchangeContextChangedMapper implements Mapper { + + private Filter filter = new PathConditionFilter(new PathConditionRule( + "invoice_payment_change.payload.invoice_payment_exchange_context_changed", + new IsNullCondition().not())); + + @Override + public PaymentWrapper map(InvoiceChange change, MachineEvent event, Integer changeId) { + InvoicePaymentChange invoicePaymentChange = change.getInvoicePaymentChange(); + String invoiceId = event.getSourceId(); + String paymentId = invoicePaymentChange.getId(); + long sequenceId = event.getEventId(); + log.info("Start mapping payment exchange context change, sequenceId='{}', changeId='{}', invoiceId='{}', " + + "paymentId='{}'", + sequenceId, changeId, invoiceId, paymentId); + + InvoicePaymentExchangeContextChanged exchangeContextChanged = + invoicePaymentChange.getPayload().getInvoicePaymentExchangeContextChanged(); + ExchangeContext exchangeContext = exchangeContextChanged.getExchangeContext(); + Rational exchangeRate = exchangeContext.getExchangeRate(); + + PaymentExchangeContext paymentExchangeContext = new PaymentExchangeContext(); + paymentExchangeContext.setWtime(null); + paymentExchangeContext.setId(null); + paymentExchangeContext.setChangeId(changeId); + paymentExchangeContext.setSequenceId(sequenceId); + paymentExchangeContext.setPaymentId(paymentId); + paymentExchangeContext.setInvoiceId(invoiceId); + paymentExchangeContext.setCurrent(true); + paymentExchangeContext.setEventCreatedAt(TypeUtil.stringToLocalDateTime(event.getCreatedAt())); + paymentExchangeContext.setSourceCurrencyCode(exchangeContext.getSourceCurrency()); + paymentExchangeContext.setDestinationCurrencyCode(exchangeContext.getDestinationCurrency()); + paymentExchangeContext.setExchangeRateRationalP(exchangeRate.getP()); + paymentExchangeContext.setExchangeRateRationalQ(exchangeRate.getQ()); + + PaymentWrapper paymentWrapper = new PaymentWrapper(); + paymentWrapper.setKey(InvoicingKey.buildKey(invoiceId, paymentId)); + paymentWrapper.setPaymentExchangeContext(paymentExchangeContext); + log.info("Payment exchange context has been mapped, sequenceId='{}', changeId='{}', invoiceId='{}', " + + "paymentId='{}'", + sequenceId, changeId, invoiceId, paymentId); + return paymentWrapper; + } + + @Override + public Filter getFilter() { + return filter; + } +} diff --git a/src/main/java/dev/vality/daway/model/PaymentWrapper.java b/src/main/java/dev/vality/daway/model/PaymentWrapper.java index 2630d219..80d5502e 100644 --- a/src/main/java/dev/vality/daway/model/PaymentWrapper.java +++ b/src/main/java/dev/vality/daway/model/PaymentWrapper.java @@ -18,6 +18,7 @@ public class PaymentWrapper { private PaymentRiskData paymentRiskData; private PaymentFee paymentFee; private PaymentRoute paymentRoute; + private PaymentExchangeContext paymentExchangeContext; private CashFlowWrapper cashFlowWrapper; private PaymentCashChange paymentCashChange; private InvoicingKey key; diff --git a/src/main/resources/db/migration/V52__add_payment_exchange_context.sql b/src/main/resources/db/migration/V52__add_payment_exchange_context.sql new file mode 100644 index 00000000..96eb08fa --- /dev/null +++ b/src/main/resources/db/migration/V52__add_payment_exchange_context.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS dw.payment_exchange_context +( + id bigserial NOT NULL, + event_created_at timestamp without time zone NOT NULL, + invoice_id character varying NOT NULL, + payment_id character varying NOT NULL, + source_currency_code character varying NOT NULL, + destination_currency_code character varying NOT NULL, + exchange_rate_rational_p bigint NOT NULL, + exchange_rate_rational_q bigint NOT NULL, + current boolean NOT NULL DEFAULT false, + wtime timestamp without time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text), + sequence_id bigint, + change_id integer, + + CONSTRAINT payment_exchange_context_pkey PRIMARY KEY (id), + CONSTRAINT payment_exchange_context_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id) +); + +ALTER TABLE dw.cash_flow + ADD COLUMN IF NOT EXISTS exchange_source_currency_code character varying, + ADD COLUMN IF NOT EXISTS exchange_destination_currency_code character varying, + ADD COLUMN IF NOT EXISTS exchange_rate_rational_p bigint, + ADD COLUMN IF NOT EXISTS exchange_rate_rational_q bigint; diff --git a/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java b/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java new file mode 100644 index 00000000..5cd13fc5 --- /dev/null +++ b/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java @@ -0,0 +1,56 @@ +package dev.vality.daway.factory.cash.flow; + +import dev.vality.damsel.base.Rational; +import dev.vality.damsel.domain.Cash; +import dev.vality.damsel.domain.CashFlowAccount; +import dev.vality.damsel.domain.CurrencyRef; +import dev.vality.damsel.domain.ExchangeContext; +import dev.vality.damsel.domain.FinalCashFlowAccount; +import dev.vality.damsel.domain.FinalCashFlowPosting; +import dev.vality.damsel.domain.MerchantCashFlowAccount; +import dev.vality.damsel.domain.SystemCashFlowAccount; +import dev.vality.daway.domain.enums.PaymentChangeType; +import dev.vality.daway.domain.tables.pojos.CashFlow; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class CashFlowFactoryTest { + + @Test + void buildWithExchangeContextTest() { + FinalCashFlowPosting posting = buildPosting(); + posting.setExchangeContext(new ExchangeContext("RUB", "USD", new Rational(60797502L, 1000000L))); + + CashFlow cashFlow = CashFlowFactory.build(List.of(posting), 1L, PaymentChangeType.payment).get(0); + + assertEquals("RUB", cashFlow.getExchangeSourceCurrencyCode()); + assertEquals("USD", cashFlow.getExchangeDestinationCurrencyCode()); + assertEquals(60797502L, cashFlow.getExchangeRateRationalP()); + assertEquals(1000000L, cashFlow.getExchangeRateRationalQ()); + } + + @Test + void buildWithoutExchangeContextTest() { + CashFlow cashFlow = CashFlowFactory.build(List.of(buildPosting()), 1L, PaymentChangeType.payment).get(0); + + assertNull(cashFlow.getExchangeSourceCurrencyCode()); + assertNull(cashFlow.getExchangeDestinationCurrencyCode()); + assertNull(cashFlow.getExchangeRateRationalP()); + assertNull(cashFlow.getExchangeRateRationalQ()); + } + + private FinalCashFlowPosting buildPosting() { + return new FinalCashFlowPosting() + .setSource(new FinalCashFlowAccount() + .setAccountId(1) + .setAccountType(CashFlowAccount.merchant(MerchantCashFlowAccount.settlement))) + .setDestination(new FinalCashFlowAccount() + .setAccountId(2) + .setAccountType(CashFlowAccount.system(SystemCashFlowAccount.settlement))) + .setVolume(new Cash(1000L, new CurrencyRef("RUB"))); + } +} diff --git a/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java b/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java new file mode 100644 index 00000000..2ccc3a6b --- /dev/null +++ b/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java @@ -0,0 +1,57 @@ +package dev.vality.daway.mapper.payment; + +import dev.vality.damsel.base.Rational; +import dev.vality.damsel.domain.ExchangeContext; +import dev.vality.damsel.payment_processing.InvoiceChange; +import dev.vality.damsel.payment_processing.InvoicePaymentChange; +import dev.vality.damsel.payment_processing.InvoicePaymentChangePayload; +import dev.vality.damsel.payment_processing.InvoicePaymentExchangeContextChanged; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; +import dev.vality.daway.model.PaymentWrapper; +import dev.vality.geck.common.util.TypeUtil; +import dev.vality.machinegun.eventsink.MachineEvent; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class InvoicePaymentExchangeContextChangedMapperTest { + + private final InvoicePaymentExchangeContextChangedMapper mapper = + new InvoicePaymentExchangeContextChangedMapper(); + + @Test + void mapTest() { + LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + MachineEvent event = new MachineEvent() + .setSourceId("invoice_id") + .setEventId(42L) + .setCreatedAt(TypeUtil.temporalToString(createdAt)); + InvoiceChange change = InvoiceChange.invoice_payment_change(new InvoicePaymentChange() + .setId("payment_id") + .setPayload(InvoicePaymentChangePayload.invoice_payment_exchange_context_changed( + new InvoicePaymentExchangeContextChanged(new ExchangeContext( + "RUB", + "USD", + new Rational(60797502L, 1000000L)))))); + + PaymentWrapper wrapper = mapper.map(change, event, 7); + PaymentExchangeContext paymentExchangeContext = wrapper.getPaymentExchangeContext(); + + assertEquals("invoice_id", wrapper.getKey().getInvoiceId()); + assertEquals("payment_id", wrapper.getKey().getPaymentId()); + assertEquals("invoice_id", paymentExchangeContext.getInvoiceId()); + assertEquals("payment_id", paymentExchangeContext.getPaymentId()); + assertEquals(createdAt, paymentExchangeContext.getEventCreatedAt()); + assertEquals(42L, paymentExchangeContext.getSequenceId()); + assertEquals(7, paymentExchangeContext.getChangeId()); + assertTrue(paymentExchangeContext.getCurrent()); + assertEquals("RUB", paymentExchangeContext.getSourceCurrencyCode()); + assertEquals("USD", paymentExchangeContext.getDestinationCurrencyCode()); + assertEquals(60797502L, paymentExchangeContext.getExchangeRateRationalP()); + assertEquals(1000000L, paymentExchangeContext.getExchangeRateRationalQ()); + } +} diff --git a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java index b8cb0646..388d09ee 100644 --- a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java +++ b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java @@ -7,6 +7,7 @@ import dev.vality.daway.domain.enums.PaymentChangeType; import dev.vality.daway.domain.tables.pojos.CashFlow; import dev.vality.daway.domain.tables.pojos.CashFlowLink; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; import dev.vality.daway.domain.tables.pojos.PaymentFee; import dev.vality.daway.model.CashFlowWrapper; import dev.vality.daway.model.InvoicingKey; @@ -95,6 +96,7 @@ private List preparePaymentWrappers() { .map(PaymentWrapper::getPayment) .forEach(payment -> payment.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS))); paymentWrappers.forEach(pw -> { + pw.setPaymentExchangeContext(null); pw.setCashFlowWrapper(new CashFlowWrapper( RandomBeans.random(CashFlowLink.class), RandomBeans.randomListOf(random.nextLong(100), 3, CashFlow.class) @@ -168,4 +170,49 @@ void testPaymentFeeWithEmptyWrapper() { assertDoesNotThrow(() -> paymentWrapperService.save(List.of(wrapperWithFee, emptyWrapper))); } + + @Test + void paymentExchangeContextCurrentSwitchTest() { + String invoiceId = TestData.randomString(); + String paymentId = TestData.randomString(); + + PaymentWrapper firstWrapper = buildPaymentExchangeContextWrapper(invoiceId, paymentId, 1L, 1, 60797502L); + PaymentWrapper secondWrapper = buildPaymentExchangeContextWrapper(invoiceId, paymentId, 2L, 1, 70797502L); + + paymentWrapperService.save(List.of(firstWrapper, secondWrapper)); + + assertEquals(2, countPaymentEntity(jdbcTemplate, "payment_exchange_context", invoiceId, paymentId, false)); + assertEquals(1, countPaymentEntity(jdbcTemplate, "payment_exchange_context", invoiceId, paymentId, true)); + assertEquals(70797502L, jdbcTemplate.queryForObject(""" + SELECT exchange_rate_rational_p + FROM dw.payment_exchange_context + WHERE invoice_id = ? AND payment_id = ? AND current + """, + Long.class, + invoiceId, + paymentId)); + } + + private PaymentWrapper buildPaymentExchangeContextWrapper(String invoiceId, + String paymentId, + Long sequenceId, + Integer changeId, + Long exchangeRateP) { + PaymentExchangeContext paymentExchangeContext = new PaymentExchangeContext(); + paymentExchangeContext.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + paymentExchangeContext.setInvoiceId(invoiceId); + paymentExchangeContext.setPaymentId(paymentId); + paymentExchangeContext.setSourceCurrencyCode("RUB"); + paymentExchangeContext.setDestinationCurrencyCode("USD"); + paymentExchangeContext.setExchangeRateRationalP(exchangeRateP); + paymentExchangeContext.setExchangeRateRationalQ(1000000L); + paymentExchangeContext.setSequenceId(sequenceId); + paymentExchangeContext.setChangeId(changeId); + paymentExchangeContext.setCurrent(true); + + PaymentWrapper paymentWrapper = new PaymentWrapper(); + paymentWrapper.setKey(InvoicingKey.buildKey(invoiceId, paymentId)); + paymentWrapper.setPaymentExchangeContext(paymentExchangeContext); + return paymentWrapper; + } } diff --git a/src/test/java/dev/vality/daway/utils/PaymentWrapperTestUtil.java b/src/test/java/dev/vality/daway/utils/PaymentWrapperTestUtil.java index 7df92daf..bd70afd8 100644 --- a/src/test/java/dev/vality/daway/utils/PaymentWrapperTestUtil.java +++ b/src/test/java/dev/vality/daway/utils/PaymentWrapperTestUtil.java @@ -27,6 +27,9 @@ public static void setCurrent(PaymentWrapper wrapper, boolean current) { if (wrapper.getPaymentRoute() != null) { wrapper.getPaymentRoute().setCurrent(current); } + if (wrapper.getPaymentExchangeContext() != null) { + wrapper.getPaymentExchangeContext().setCurrent(current); + } if (wrapper.getCashFlowWrapper() != null) { wrapper.getCashFlowWrapper().getCashFlowLink().setCurrent(current); } @@ -69,6 +72,10 @@ public static void setInvoiceIdAndPaymentId(PaymentWrapper wrapper, String invoi wrapper.getPaymentRoute().setInvoiceId(invoiceId); wrapper.getPaymentRoute().setPaymentId(paymentId); } + if (wrapper.getPaymentExchangeContext() != null) { + wrapper.getPaymentExchangeContext().setInvoiceId(invoiceId); + wrapper.getPaymentExchangeContext().setPaymentId(paymentId); + } if (wrapper.getCashFlowWrapper() != null) { wrapper.getCashFlowWrapper().getCashFlowLink().setInvoiceId(invoiceId); wrapper.getCashFlowWrapper().getCashFlowLink().setPaymentId(paymentId); From 350d092a4f05ae8eb5cc93b24dfbe4576d6c8a99 Mon Sep 17 00:00:00 2001 From: kostyastruga Date: Thu, 9 Jul 2026 14:03:29 +0300 Subject: [PATCH 2/2] Fix after review --- .../impl/PaymentExchangeContextDaoImpl.java | 2 +- src/test/java/dev/vality/daway/TestData.java | 68 +++++++++++++++++++ .../cash/flow/CashFlowFactoryTest.java | 29 ++------ ...ymentExchangeContextChangedMapperTest.java | 20 +----- .../service/PaymentWrapperServiceTest.java | 30 ++------ 5 files changed, 83 insertions(+), 66 deletions(-) diff --git a/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java b/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java index f522b425..65099d34 100644 --- a/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java +++ b/src/main/java/dev/vality/daway/dao/invoicing/impl/PaymentExchangeContextDaoImpl.java @@ -35,7 +35,7 @@ public void saveBatch(List paymentExchangeContexts) thro .map(paymentExchangeContext -> getDslContext().newRecord( PAYMENT_EXCHANGE_CONTEXT, paymentExchangeContext)) .map(this::prepareInsertQuery) - .collect(Collectors.toList()); + .toList(); batchExecute(queries); } diff --git a/src/test/java/dev/vality/daway/TestData.java b/src/test/java/dev/vality/daway/TestData.java index 173e07f7..105c6135 100644 --- a/src/test/java/dev/vality/daway/TestData.java +++ b/src/test/java/dev/vality/daway/TestData.java @@ -1,5 +1,6 @@ package dev.vality.daway; +import dev.vality.damsel.base.Rational; import dev.vality.damsel.domain.*; import dev.vality.damsel.domain.CashFlowAccount; import dev.vality.damsel.domain.InvoicePaymentChargeback; @@ -14,7 +15,10 @@ import dev.vality.daway.domain.enums.*; import dev.vality.daway.domain.tables.pojos.Chargeback; import dev.vality.daway.domain.tables.pojos.FistfulCashFlow; +import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; import dev.vality.daway.domain.tables.pojos.WithdrawalAdjustment; +import dev.vality.daway.model.InvoicingKey; +import dev.vality.daway.model.PaymentWrapper; import dev.vality.fistful.account.Account; import dev.vality.fistful.base.Realm; import dev.vality.fistful.cashflow.FinalCashFlow; @@ -234,6 +238,70 @@ public static String randomString() { return UUID.randomUUID().toString(); } + public static ExchangeContext createExchangeContext() { + return createExchangeContext("RUB", "USD", 60797502L, 1000000L); + } + + public static ExchangeContext createExchangeContext(String sourceCurrencyCode, + String destinationCurrencyCode, + Long rationalP, + Long rationalQ) { + return new ExchangeContext(sourceCurrencyCode, destinationCurrencyCode, new Rational(rationalP, rationalQ)); + } + + public static FinalCashFlowPosting createPaymentCashFlowPosting() { + return new FinalCashFlowPosting() + .setSource(new FinalCashFlowAccount() + .setAccountId(1) + .setAccountType(CashFlowAccount.merchant(MerchantCashFlowAccount.settlement))) + .setDestination(new FinalCashFlowAccount() + .setAccountId(2) + .setAccountType(CashFlowAccount.system(SystemCashFlowAccount.settlement))) + .setVolume(new Cash(1000L, new CurrencyRef("RUB"))); + } + + public static FinalCashFlowPosting createPaymentCashFlowPostingWithExchangeContext() { + return createPaymentCashFlowPosting() + .setExchangeContext(createExchangeContext()); + } + + public static MachineEvent createInvoiceEvent(String invoiceId, Long sequenceId, LocalDateTime createdAt) { + return new MachineEvent() + .setSourceId(invoiceId) + .setEventId(sequenceId) + .setCreatedAt(TypeUtil.temporalToString(createdAt)); + } + + public static InvoiceChange createInvoicePaymentExchangeContextChanged(String paymentId) { + return InvoiceChange.invoice_payment_change(new InvoicePaymentChange() + .setId(paymentId) + .setPayload(InvoicePaymentChangePayload.invoice_payment_exchange_context_changed( + new InvoicePaymentExchangeContextChanged(createExchangeContext())))); + } + + public static PaymentWrapper createPaymentExchangeContextWrapper(String invoiceId, + String paymentId, + Long sequenceId, + Integer changeId, + Long exchangeRateP) { + PaymentExchangeContext paymentExchangeContext = new PaymentExchangeContext(); + paymentExchangeContext.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + paymentExchangeContext.setInvoiceId(invoiceId); + paymentExchangeContext.setPaymentId(paymentId); + paymentExchangeContext.setSourceCurrencyCode("RUB"); + paymentExchangeContext.setDestinationCurrencyCode("USD"); + paymentExchangeContext.setExchangeRateRationalP(exchangeRateP); + paymentExchangeContext.setExchangeRateRationalQ(1000000L); + paymentExchangeContext.setSequenceId(sequenceId); + paymentExchangeContext.setChangeId(changeId); + paymentExchangeContext.setCurrent(true); + + PaymentWrapper paymentWrapper = new PaymentWrapper(); + paymentWrapper.setKey(InvoicingKey.buildKey(invoiceId, paymentId)); + paymentWrapper.setPaymentExchangeContext(paymentExchangeContext); + return paymentWrapper; + } + public static CountryObject buildCountryObject() { Country country = new Country(); country.setName(randomString()); diff --git a/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java b/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java index 5cd13fc5..38f05691 100644 --- a/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java +++ b/src/test/java/dev/vality/daway/factory/cash/flow/CashFlowFactoryTest.java @@ -1,14 +1,7 @@ package dev.vality.daway.factory.cash.flow; -import dev.vality.damsel.base.Rational; -import dev.vality.damsel.domain.Cash; -import dev.vality.damsel.domain.CashFlowAccount; -import dev.vality.damsel.domain.CurrencyRef; -import dev.vality.damsel.domain.ExchangeContext; -import dev.vality.damsel.domain.FinalCashFlowAccount; import dev.vality.damsel.domain.FinalCashFlowPosting; -import dev.vality.damsel.domain.MerchantCashFlowAccount; -import dev.vality.damsel.domain.SystemCashFlowAccount; +import dev.vality.daway.TestData; import dev.vality.daway.domain.enums.PaymentChangeType; import dev.vality.daway.domain.tables.pojos.CashFlow; import org.junit.jupiter.api.Test; @@ -22,8 +15,7 @@ class CashFlowFactoryTest { @Test void buildWithExchangeContextTest() { - FinalCashFlowPosting posting = buildPosting(); - posting.setExchangeContext(new ExchangeContext("RUB", "USD", new Rational(60797502L, 1000000L))); + FinalCashFlowPosting posting = TestData.createPaymentCashFlowPostingWithExchangeContext(); CashFlow cashFlow = CashFlowFactory.build(List.of(posting), 1L, PaymentChangeType.payment).get(0); @@ -35,22 +27,15 @@ void buildWithExchangeContextTest() { @Test void buildWithoutExchangeContextTest() { - CashFlow cashFlow = CashFlowFactory.build(List.of(buildPosting()), 1L, PaymentChangeType.payment).get(0); + CashFlow cashFlow = CashFlowFactory.build( + List.of(TestData.createPaymentCashFlowPosting()), + 1L, + PaymentChangeType.payment + ).get(0); assertNull(cashFlow.getExchangeSourceCurrencyCode()); assertNull(cashFlow.getExchangeDestinationCurrencyCode()); assertNull(cashFlow.getExchangeRateRationalP()); assertNull(cashFlow.getExchangeRateRationalQ()); } - - private FinalCashFlowPosting buildPosting() { - return new FinalCashFlowPosting() - .setSource(new FinalCashFlowAccount() - .setAccountId(1) - .setAccountType(CashFlowAccount.merchant(MerchantCashFlowAccount.settlement))) - .setDestination(new FinalCashFlowAccount() - .setAccountId(2) - .setAccountType(CashFlowAccount.system(SystemCashFlowAccount.settlement))) - .setVolume(new Cash(1000L, new CurrencyRef("RUB"))); - } } diff --git a/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java b/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java index 2ccc3a6b..b3ec968b 100644 --- a/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java +++ b/src/test/java/dev/vality/daway/mapper/payment/InvoicePaymentExchangeContextChangedMapperTest.java @@ -1,14 +1,9 @@ package dev.vality.daway.mapper.payment; -import dev.vality.damsel.base.Rational; -import dev.vality.damsel.domain.ExchangeContext; import dev.vality.damsel.payment_processing.InvoiceChange; -import dev.vality.damsel.payment_processing.InvoicePaymentChange; -import dev.vality.damsel.payment_processing.InvoicePaymentChangePayload; -import dev.vality.damsel.payment_processing.InvoicePaymentExchangeContextChanged; +import dev.vality.daway.TestData; import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; import dev.vality.daway.model.PaymentWrapper; -import dev.vality.geck.common.util.TypeUtil; import dev.vality.machinegun.eventsink.MachineEvent; import org.junit.jupiter.api.Test; @@ -26,17 +21,8 @@ class InvoicePaymentExchangeContextChangedMapperTest { @Test void mapTest() { LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); - MachineEvent event = new MachineEvent() - .setSourceId("invoice_id") - .setEventId(42L) - .setCreatedAt(TypeUtil.temporalToString(createdAt)); - InvoiceChange change = InvoiceChange.invoice_payment_change(new InvoicePaymentChange() - .setId("payment_id") - .setPayload(InvoicePaymentChangePayload.invoice_payment_exchange_context_changed( - new InvoicePaymentExchangeContextChanged(new ExchangeContext( - "RUB", - "USD", - new Rational(60797502L, 1000000L)))))); + MachineEvent event = TestData.createInvoiceEvent("invoice_id", 42L, createdAt); + InvoiceChange change = TestData.createInvoicePaymentExchangeContextChanged("payment_id"); PaymentWrapper wrapper = mapper.map(change, event, 7); PaymentExchangeContext paymentExchangeContext = wrapper.getPaymentExchangeContext(); diff --git a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java index 388d09ee..5f2e48ff 100644 --- a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java +++ b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java @@ -7,7 +7,6 @@ import dev.vality.daway.domain.enums.PaymentChangeType; import dev.vality.daway.domain.tables.pojos.CashFlow; import dev.vality.daway.domain.tables.pojos.CashFlowLink; -import dev.vality.daway.domain.tables.pojos.PaymentExchangeContext; import dev.vality.daway.domain.tables.pojos.PaymentFee; import dev.vality.daway.model.CashFlowWrapper; import dev.vality.daway.model.InvoicingKey; @@ -176,8 +175,10 @@ void paymentExchangeContextCurrentSwitchTest() { String invoiceId = TestData.randomString(); String paymentId = TestData.randomString(); - PaymentWrapper firstWrapper = buildPaymentExchangeContextWrapper(invoiceId, paymentId, 1L, 1, 60797502L); - PaymentWrapper secondWrapper = buildPaymentExchangeContextWrapper(invoiceId, paymentId, 2L, 1, 70797502L); + PaymentWrapper firstWrapper = + TestData.createPaymentExchangeContextWrapper(invoiceId, paymentId, 1L, 1, 60797502L); + PaymentWrapper secondWrapper = + TestData.createPaymentExchangeContextWrapper(invoiceId, paymentId, 2L, 1, 70797502L); paymentWrapperService.save(List.of(firstWrapper, secondWrapper)); @@ -192,27 +193,4 @@ void paymentExchangeContextCurrentSwitchTest() { invoiceId, paymentId)); } - - private PaymentWrapper buildPaymentExchangeContextWrapper(String invoiceId, - String paymentId, - Long sequenceId, - Integer changeId, - Long exchangeRateP) { - PaymentExchangeContext paymentExchangeContext = new PaymentExchangeContext(); - paymentExchangeContext.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); - paymentExchangeContext.setInvoiceId(invoiceId); - paymentExchangeContext.setPaymentId(paymentId); - paymentExchangeContext.setSourceCurrencyCode("RUB"); - paymentExchangeContext.setDestinationCurrencyCode("USD"); - paymentExchangeContext.setExchangeRateRationalP(exchangeRateP); - paymentExchangeContext.setExchangeRateRationalQ(1000000L); - paymentExchangeContext.setSequenceId(sequenceId); - paymentExchangeContext.setChangeId(changeId); - paymentExchangeContext.setCurrent(true); - - PaymentWrapper paymentWrapper = new PaymentWrapper(); - paymentWrapper.setKey(InvoicingKey.buildKey(invoiceId, paymentId)); - paymentWrapper.setPaymentExchangeContext(paymentExchangeContext); - return paymentWrapper; - } }