feat: persist issuance requests and bank inventory

agent/payment-training-progress
chenyuan 4 weeks ago
parent c17cc29db2
commit 432081d8ce

@ -0,0 +1,36 @@
package com.yau.digitalrmb.issuance.domain.model;
import java.math.BigDecimal;
import java.util.Objects;
public final class IssuanceBankInventory {
private final String bankCode;
private final BigDecimal currentBalance;
private final BigDecimal warningThreshold;
private final BigDecimal suggestedSupplementAmount;
public IssuanceBankInventory(String bankCode, BigDecimal currentBalance, BigDecimal warningThreshold,
BigDecimal suggestedSupplementAmount) {
this.bankCode = Objects.requireNonNull(bankCode, "bankCode must not be null");
this.currentBalance = Objects.requireNonNull(currentBalance, "currentBalance must not be null");
this.warningThreshold = Objects.requireNonNull(warningThreshold, "warningThreshold must not be null");
this.suggestedSupplementAmount = Objects.requireNonNull(suggestedSupplementAmount,
"suggestedSupplementAmount must not be null");
}
public String getBankCode() {
return bankCode;
}
public BigDecimal getCurrentBalance() {
return currentBalance;
}
public BigDecimal getWarningThreshold() {
return warningThreshold;
}
public BigDecimal getSuggestedSupplementAmount() {
return suggestedSupplementAmount;
}
}

@ -5,12 +5,16 @@ import java.math.RoundingMode;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
public class IssuanceRequest {
private final IssuanceApplicationId id;
private final String requestNo;
private final String bankCode;
private final String organizationId;
private BigDecimal totalAmount;
@ -26,9 +30,10 @@ public class IssuanceRequest {
private CentralBankReceiveStatus centralBankReceiveStatus;
private Instant centralBankReceivedAt;
private IssuanceRequest(IssuanceApplicationId id, String bankCode, String organizationId,
private IssuanceRequest(IssuanceApplicationId id, String requestNo, String bankCode, String organizationId,
BigDecimal totalAmount, String currency, List<DenominationItem> denominations) {
this.id = Objects.requireNonNull(id, "id must not be null");
this.requestNo = Objects.requireNonNull(requestNo, "requestNo must not be null");
this.bankCode = Objects.requireNonNull(bankCode, "bankCode must not be null");
this.organizationId = Objects.requireNonNull(organizationId, "organizationId must not be null");
applyDraftValues(totalAmount, currency, denominations);
@ -39,7 +44,29 @@ public class IssuanceRequest {
public static IssuanceRequest create(IssuanceApplicationId id, String bankCode, String organizationId,
BigDecimal totalAmount, String currency,
List<DenominationItem> denominations) {
return new IssuanceRequest(id, bankCode, organizationId, totalAmount, currency, denominations);
return new IssuanceRequest(id, requestNoFor(id), bankCode, organizationId, totalAmount, currency, denominations);
}
public static IssuanceRequest rehydrate(IssuanceApplicationId id, String requestNo, String bankCode, String organizationId,
BigDecimal totalAmount, String currency,
List<DenominationItem> denominations, String requestTimestamp,
String messageText, String digest, String signature, String signingKeyRef,
String payloadJson, IssuanceRequestStatus status,
CentralBankReceiveStatus centralBankReceiveStatus,
Instant centralBankReceivedAt) {
IssuanceRequest request = new IssuanceRequest(id, requestNo, bankCode, organizationId, totalAmount, currency,
denominations);
request.requestTimestamp = requestTimestamp;
request.messageText = messageText;
request.digest = digest;
request.signature = signature;
request.signingKeyRef = signingKeyRef;
request.payloadJson = payloadJson;
request.status = Objects.requireNonNull(status, "status must not be null");
request.centralBankReceiveStatus = Objects.requireNonNull(centralBankReceiveStatus,
"centralBankReceiveStatus must not be null");
request.centralBankReceivedAt = centralBankReceivedAt;
return request;
}
public void updateDraft(BigDecimal totalAmount, String currency, List<DenominationItem> denominations) {
@ -94,6 +121,10 @@ public class IssuanceRequest {
return id;
}
public String getRequestNo() {
return requestNo;
}
public String getBankCode() {
return bankCode;
}
@ -163,6 +194,10 @@ public class IssuanceRequest {
this.denominations = Collections.unmodifiableList(copiedDenominations);
}
private static String requestNoFor(IssuanceApplicationId id) {
return "ISSUE_REQ_" + id.value().toString().replace("-", "").toUpperCase(Locale.ROOT);
}
private BigDecimal normalizeAmount(BigDecimal totalAmount) {
BigDecimal amount = Objects.requireNonNull(totalAmount, "totalAmount must not be null")
.setScale(2, RoundingMode.UNNECESSARY);
@ -178,8 +213,12 @@ public class IssuanceRequest {
if (copiedDenominations.isEmpty()) {
throw new IllegalArgumentException("denominations must not be empty");
}
Set<Integer> faceValues = new HashSet<Integer>();
for (DenominationItem denomination : copiedDenominations) {
Objects.requireNonNull(denomination, "denomination item must not be null");
if (!faceValues.add(denomination.getDenomination())) {
throw new IllegalArgumentException("denomination values must be unique");
}
}
return copiedDenominations;
}

@ -0,0 +1,16 @@
package com.yau.digitalrmb.issuance.domain.repository;
import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId;
import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory;
import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest;
import java.math.BigDecimal;
import java.util.Optional;
public interface IssuanceRequestRepository {
void save(IssuanceRequest request);
Optional<IssuanceRequest> findById(IssuanceApplicationId id);
Optional<IssuanceBankInventory> findInventoryByBankCode(String bankCode);
}

@ -0,0 +1,24 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Getter
@Setter
@TableName("issuance_bank_inventory")
public class IssuanceBankInventoryEntity {
@TableId(value = "bank_code", type = IdType.INPUT)
private String bankCode;
private BigDecimal currentBalance;
private BigDecimal warningThreshold;
private LocalDateTime updatedAt;
}

@ -0,0 +1,20 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@TableName("issuance_request_denomination")
public class IssuanceRequestDenominationEntity {
@TableId(value = "request_id", type = IdType.INPUT)
private String requestId;
private Integer denomination;
private Integer quantity;
}

@ -0,0 +1,51 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Getter
@Setter
@TableName("issuance_request")
public class IssuanceRequestEntity {
@TableId(value = "id", type = IdType.INPUT)
private String id;
private String requestNo;
private String bankCode;
private String organizationId;
private BigDecimal totalAmount;
private String currency;
private String requestTimestamp;
private String messageText;
private String digest;
private String signature;
private String signingKeyRef;
private String payloadJson;
private String status;
private String centralReceiveStatus;
private LocalDateTime centralReceivedAt;
@TableField(value = "created_at", fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
@TableLogic
private Boolean deleted;
}

@ -0,0 +1,9 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceBankInventoryEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IssuanceBankInventoryMapper extends BaseMapper<IssuanceBankInventoryEntity> {
}

@ -0,0 +1,9 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestDenominationEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IssuanceRequestDenominationMapper extends BaseMapper<IssuanceRequestDenominationEntity> {
}

@ -0,0 +1,9 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IssuanceRequestMapper extends BaseMapper<IssuanceRequestEntity> {
}

@ -0,0 +1,130 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yau.digitalrmb.issuance.domain.model.CentralBankReceiveStatus;
import com.yau.digitalrmb.issuance.domain.model.DenominationItem;
import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId;
import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory;
import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest;
import com.yau.digitalrmb.issuance.domain.model.IssuanceRequestStatus;
import com.yau.digitalrmb.issuance.domain.repository.IssuanceRequestRepository;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceBankInventoryEntity;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestDenominationEntity;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity;
import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceBankInventoryMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceRequestDenominationMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceRequestMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository
public class MybatisIssuanceRequestRepository implements IssuanceRequestRepository {
private final IssuanceRequestMapper requestMapper;
private final IssuanceRequestDenominationMapper denominationMapper;
private final IssuanceBankInventoryMapper inventoryMapper;
public MybatisIssuanceRequestRepository(IssuanceRequestMapper requestMapper,
IssuanceRequestDenominationMapper denominationMapper,
IssuanceBankInventoryMapper inventoryMapper) {
this.requestMapper = requestMapper;
this.denominationMapper = denominationMapper;
this.inventoryMapper = inventoryMapper;
}
@Override
@Transactional
public void save(IssuanceRequest request) {
String requestId = request.getId().value().toString();
IssuanceRequestEntity entity = toEntity(request);
if (requestMapper.selectById(requestId) == null) {
requestMapper.insert(entity);
} else {
requestMapper.updateById(entity);
}
denominationMapper.delete(new LambdaQueryWrapper<IssuanceRequestDenominationEntity>()
.eq(IssuanceRequestDenominationEntity::getRequestId, requestId));
for (DenominationItem denomination : request.getDenominations()) {
denominationMapper.insert(toDenominationEntity(requestId, denomination));
}
}
@Override
public Optional<IssuanceRequest> findById(IssuanceApplicationId id) {
String requestId = id.value().toString();
IssuanceRequestEntity requestEntity = requestMapper.selectById(requestId);
if (requestEntity == null) {
return Optional.empty();
}
List<IssuanceRequestDenominationEntity> denominationEntities = denominationMapper.selectList(
new LambdaQueryWrapper<IssuanceRequestDenominationEntity>()
.eq(IssuanceRequestDenominationEntity::getRequestId, requestId)
.orderByDesc(IssuanceRequestDenominationEntity::getDenomination));
return Optional.of(toAggregate(requestEntity, denominationEntities));
}
@Override
public Optional<IssuanceBankInventory> findInventoryByBankCode(String bankCode) {
IssuanceBankInventoryEntity inventory = inventoryMapper.selectById(bankCode);
if (inventory == null) {
return Optional.empty();
}
BigDecimal suggested = inventory.getWarningThreshold().subtract(inventory.getCurrentBalance());
return Optional.of(new IssuanceBankInventory(inventory.getBankCode(), inventory.getCurrentBalance(),
inventory.getWarningThreshold(), suggested.max(BigDecimal.ZERO).setScale(2)));
}
private IssuanceRequestEntity toEntity(IssuanceRequest request) {
IssuanceRequestEntity entity = new IssuanceRequestEntity();
entity.setId(request.getId().value().toString());
entity.setRequestNo(request.getRequestNo());
entity.setBankCode(request.getBankCode());
entity.setOrganizationId(request.getOrganizationId());
entity.setTotalAmount(request.getTotalAmount());
entity.setCurrency(request.getCurrency());
entity.setRequestTimestamp(request.getRequestTimestamp());
entity.setMessageText(request.getMessageText());
entity.setDigest(request.getDigest());
entity.setSignature(request.getSignature());
entity.setSigningKeyRef(request.getSigningKeyRef());
entity.setPayloadJson(request.getPayloadJson());
entity.setStatus(request.getStatus().name());
entity.setCentralReceiveStatus(request.getCentralBankReceiveStatus().name());
if (request.getCentralBankReceivedAt() != null) {
entity.setCentralReceivedAt(request.getCentralBankReceivedAt().atOffset(ZoneOffset.UTC).toLocalDateTime());
}
return entity;
}
private IssuanceRequestDenominationEntity toDenominationEntity(String requestId, DenominationItem denomination) {
IssuanceRequestDenominationEntity entity = new IssuanceRequestDenominationEntity();
entity.setRequestId(requestId);
entity.setDenomination(denomination.getDenomination());
entity.setQuantity(denomination.getQuantity());
return entity;
}
private IssuanceRequest toAggregate(IssuanceRequestEntity requestEntity,
List<IssuanceRequestDenominationEntity> denominationEntities) {
List<DenominationItem> denominations = new ArrayList<DenominationItem>();
for (IssuanceRequestDenominationEntity denomination : denominationEntities) {
denominations.add(new DenominationItem(denomination.getDenomination(), denomination.getQuantity()));
}
return IssuanceRequest.rehydrate(new IssuanceApplicationId(UUID.fromString(requestEntity.getId())),
requestEntity.getRequestNo(), requestEntity.getBankCode(), requestEntity.getOrganizationId(),
requestEntity.getTotalAmount(), requestEntity.getCurrency(), denominations,
requestEntity.getRequestTimestamp(), requestEntity.getMessageText(), requestEntity.getDigest(),
requestEntity.getSignature(), requestEntity.getSigningKeyRef(), requestEntity.getPayloadJson(),
IssuanceRequestStatus.valueOf(requestEntity.getStatus()),
CentralBankReceiveStatus.valueOf(requestEntity.getCentralReceiveStatus()),
requestEntity.getCentralReceivedAt() == null ? null
: requestEntity.getCentralReceivedAt().toInstant(ZoneOffset.UTC));
}
}

@ -67,3 +67,46 @@ ON DUPLICATE KEY UPDATE account = VALUES(account), display_name = VALUES(display
source_updated_at = CURRENT_TIMESTAMP, synced_at = CURRENT_TIMESTAMP;
INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES (487, 1002);
CREATE TABLE IF NOT EXISTS issuance_bank_inventory (
bank_code VARCHAR(32) PRIMARY KEY,
current_balance DECIMAL(20, 2) NOT NULL,
warning_threshold DECIMAL(20, 2) NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS issuance_request (
id CHAR(36) PRIMARY KEY,
request_no VARCHAR(64) NOT NULL UNIQUE,
bank_code VARCHAR(32) NOT NULL,
organization_id VARCHAR(64) NOT NULL,
total_amount DECIMAL(20, 2) NOT NULL,
currency VARCHAR(16) NOT NULL,
request_timestamp VARCHAR(32),
message_text TEXT,
digest CHAR(64),
signature TEXT,
signing_key_ref VARCHAR(64),
payload_json TEXT,
status VARCHAR(32) NOT NULL,
central_receive_status VARCHAR(32) NOT NULL,
central_received_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
created_by VARCHAR(64) NOT NULL,
updated_by VARCHAR(64) NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS issuance_request_denomination (
request_id CHAR(36) NOT NULL,
denomination INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (request_id, denomination)
);
INSERT INTO issuance_bank_inventory (bank_code, current_balance, warning_threshold, updated_at)
VALUES ('BKCHCNBJ00001', 49950000.00, 50000000.00, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE current_balance = VALUES(current_balance),
warning_threshold = VALUES(warning_threshold),
updated_at = CURRENT_TIMESTAMP;

@ -37,6 +37,18 @@ class IssuanceRequestTest {
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void rejectsDuplicateDenominationsThatCannotBeStoredAsDistinctRows() {
assertThatThrownBy(() -> IssuanceRequest.create(
REQUEST_ID,
"BKCHCNBJ00001",
"ORG_3A4B5C6D7E8F",
new BigDecimal("500.00"),
"DC",
Arrays.asList(new DenominationItem(100, 2), new DenominationItem(100, 3))))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void sendsOnlyPackagedRequestAndMarksCentralBankAsReceived() {
IssuanceRequest request = preparedSignedAndPackagedRequest();

@ -0,0 +1,92 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence;
import com.yau.digitalrmb.issuance.domain.model.CentralBankReceiveStatus;
import com.yau.digitalrmb.issuance.domain.model.DenominationItem;
import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory;
import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId;
import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest;
import com.yau.digitalrmb.issuance.domain.model.IssuanceRequestStatus;
import com.yau.digitalrmb.issuance.domain.repository.IssuanceRequestRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Arrays;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@ActiveProfiles("test")
class MybatisIssuanceRequestRepositoryTest {
@Autowired
private IssuanceRequestRepository repository;
@Test
void savesAndRestoresTheRequestWithItsDenominationsAndCentralBankStatus() {
IssuanceRequest sentRequest = sentRequest();
repository.save(sentRequest);
IssuanceRequest restored = repository.findById(sentRequest.getId())
.orElseThrow(() -> new AssertionError("saved request was not restored"));
assertThat(restored.getRequestNo()).isEqualTo(sentRequest.getRequestNo());
assertThat(restored.getBankCode()).isEqualTo("BKCHCNBJ00001");
assertThat(restored.getOrganizationId()).isEqualTo("ORG_3A4B5C6D7E8F");
assertThat(restored.getTotalAmount()).isEqualByComparingTo("50000.00");
assertThat(restored.getCurrency()).isEqualTo("DC");
assertThat(restored.getDenominations())
.containsExactly(new DenominationItem(100, 400), new DenominationItem(50, 200));
assertThat(restored.getRequestTimestamp()).isEqualTo("20260801103218");
assertThat(restored.getMessageText()).isEqualTo("ISSUE|BKCHCNBJ00001|ORG_3A4B5C6D7E8F|50000.00");
assertThat(restored.getDigest()).isEqualTo("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
assertThat(restored.getSignature()).isEqualTo("base64-signature");
assertThat(restored.getSigningKeyRef()).isEqualTo("sm2-key-02");
assertThat(restored.getPayloadJson()).isEqualTo("{\"requestId\":\"example\"}");
assertThat(restored.getStatus()).isEqualTo(IssuanceRequestStatus.SENT);
assertThat(restored.getCentralBankReceiveStatus()).isEqualTo(CentralBankReceiveStatus.RECEIVED);
assertThat(restored.getCentralBankReceivedAt()).isEqualTo(Instant.parse("2026-08-03T12:00:00Z"));
}
@Test
void readsSuggestedAmountAsThresholdMinusCurrentBalance() {
IssuanceBankInventory inventory = repository.findInventoryByBankCode("BKCHCNBJ00001")
.orElseThrow(() -> new AssertionError("seeded bank inventory was not found"));
assertThat(inventory.getCurrentBalance()).isEqualByComparingTo("49950000.00");
assertThat(inventory.getWarningThreshold()).isEqualByComparingTo("50000000.00");
assertThat(inventory.getSuggestedSupplementAmount()).isEqualByComparingTo("50000.00");
}
@Test
void savingChangedDraftReplacesRatherThanAccumulatesDenominations() {
IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(UUID.randomUUID()),
"BKCHCNBJ00001", "ORG_3A4B5C6D7E8F", new BigDecimal("50000.00"), "DC",
Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200)));
repository.save(request);
request.updateDraft(new BigDecimal("50000.00"), "DC", Arrays.asList(new DenominationItem(100, 500)));
repository.save(request);
IssuanceRequest restored = repository.findById(request.getId())
.orElseThrow(() -> new AssertionError("updated request was not restored"));
assertThat(restored.getDenominations()).containsExactly(new DenominationItem(100, 500));
}
private IssuanceRequest sentRequest() {
IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(UUID.randomUUID()),
"BKCHCNBJ00001", "ORG_3A4B5C6D7E8F", new BigDecimal("50000.00"), "DC",
Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200)));
request.prepareMessage("20260801103218", "ISSUE|BKCHCNBJ00001|ORG_3A4B5C6D7E8F|50000.00");
request.recordDigest("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
request.recordSignature("sm2-key-02", "base64-signature");
request.packagePayload("{\"requestId\":\"example\"}");
request.sendToCentralBank(Instant.parse("2026-08-03T12:00:00Z"));
return request;
}
}
Loading…
Cancel
Save