diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountController.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountController.java new file mode 100644 index 00000000..47aab204 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountController.java @@ -0,0 +1,40 @@ +package org.example.banking.adapter.in.web; + + +import lombok.RequiredArgsConstructor; +import org.example.banking.application.port.in.RegisterBankAccountCommand; +import org.example.banking.application.port.in.RegisterBankAccountUseCase; +import org.example.banking.domain.RegisteredBankAccount; +import org.example.common.WebAdapter; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@WebAdapter +@RestController +@RequiredArgsConstructor +public class RegisterBankAccountController { + + private final RegisterBankAccountUseCase registerBankAccountUseCase; + + @PostMapping("/banking/account/register") + RegisteredBankAccount registeredBankAccount(@RequestBody RegisterBankAccountRequest request) { + + RegisterBankAccountCommand command = RegisterBankAccountCommand.builder() + .membershipId(request.getMembershipId()) + .bankName(request.getBankName()) + .bankAccountNumber(request.getBankAccountNumber()) + .isValid(true) + .build(); + + RegisteredBankAccount registeredBankAccount = registerBankAccountUseCase.registerBankAccount(command); + + if (registeredBankAccount == null) { + + // TODO Error Handling + return null; + } + + return registeredBankAccount; + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountRequest.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountRequest.java new file mode 100644 index 00000000..3498f6bc --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/in/web/RegisterBankAccountRequest.java @@ -0,0 +1,19 @@ +package org.example.banking.adapter.in.web; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.NotEmpty; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RegisterBankAccountRequest { + + private String membershipId; + private String bankName; + private String bankAccountNumber; + private boolean isValid; +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccount.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccount.java new file mode 100644 index 00000000..9a2b0ee3 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccount.java @@ -0,0 +1,13 @@ +package org.example.banking.adapter.out.external.bank; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class BankAccount { + + private String bankName; + private String bankAccountNumber; + private boolean isValid; +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccountAdapter.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccountAdapter.java new file mode 100644 index 00000000..4ffee8fe --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/BankAccountAdapter.java @@ -0,0 +1,15 @@ +package org.example.banking.adapter.out.external.bank; + +import lombok.RequiredArgsConstructor; +import org.example.banking.application.port.out.RequestBankAccountInfoPort; +import org.example.common.ExternalSystemAdapter; + +@ExternalSystemAdapter +@RequiredArgsConstructor +public class BankAccountAdapter implements RequestBankAccountInfoPort { + + @Override + public BankAccount getBankAccountInfo(GetBankAccountRequest request) { + return new BankAccount(request.getBankName(), request.getBankAccountNumber(), true); + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/GetBankAccountRequest.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/GetBankAccountRequest.java new file mode 100644 index 00000000..1932d40e --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/external/bank/GetBankAccountRequest.java @@ -0,0 +1,12 @@ +package org.example.banking.adapter.out.external.bank; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class GetBankAccountRequest { + + private String bankName; + private String bankAccountNumber; +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountJpaEntity.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountJpaEntity.java new file mode 100644 index 00000000..3db8f7c0 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountJpaEntity.java @@ -0,0 +1,30 @@ +package org.example.banking.adapter.out.persistence; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Table(name = "registered_bank_account") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RegisteredBankAccountJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long registeredBankAccountId; + private String membershipId; + private String bankName; + private String bankAccountNumber; + private boolean linkedStatusIsValid; + + public RegisteredBankAccountJpaEntity(String membershipId, String bankName, String bankAccountNumber, boolean linkedStatusIsValid) { + this.membershipId = membershipId; + this.bankName = bankName; + this.bankAccountNumber = bankAccountNumber; + this.linkedStatusIsValid = linkedStatusIsValid; + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountMapper.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountMapper.java new file mode 100644 index 00000000..1766c7bb --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountMapper.java @@ -0,0 +1,18 @@ +package org.example.banking.adapter.out.persistence; + +import org.example.banking.domain.RegisteredBankAccount; +import org.springframework.stereotype.Component; + +@Component +public class RegisteredBankAccountMapper { + + public RegisteredBankAccount mapToDomainEntity(RegisteredBankAccountJpaEntity membershipJpaEntity) { + return RegisteredBankAccount.generateRegisteredBankAccount( + new RegisteredBankAccount.RegisteredBankAccountId(membershipJpaEntity.getRegisteredBankAccountId()+""), + new RegisteredBankAccount.MembershipId(membershipJpaEntity.getMembershipId()), + new RegisteredBankAccount.BankName(membershipJpaEntity.getBankName()), + new RegisteredBankAccount.BankAccountNumber(membershipJpaEntity.getBankAccountNumber()), + new RegisteredBankAccount.LinkedStatusIsValid(membershipJpaEntity.isLinkedStatusIsValid()) + ); + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountPersistenceAdapter.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountPersistenceAdapter.java new file mode 100644 index 00000000..68120054 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/RegisteredBankAccountPersistenceAdapter.java @@ -0,0 +1,25 @@ +package org.example.banking.adapter.out.persistence; + +import lombok.RequiredArgsConstructor; +import org.example.banking.application.port.out.RegisterBankAccountPort; +import org.example.banking.domain.RegisteredBankAccount; +import org.example.common.PersistenceAdapter; + +@PersistenceAdapter +@RequiredArgsConstructor +public class RegisteredBankAccountPersistenceAdapter implements RegisterBankAccountPort { + + private final SpringDataRegisteredBankAccountRepository bankAccountRepository; + + @Override + public RegisteredBankAccountJpaEntity createRegisteredBankAccount(RegisteredBankAccount.MembershipId membershipId, RegisteredBankAccount.BankName bankName, RegisteredBankAccount.BankAccountNumber bankAccountNumber, RegisteredBankAccount.LinkedStatusIsValid linkedStatusIsValid) { + return bankAccountRepository.save( + new RegisteredBankAccountJpaEntity( + membershipId.getMembershipId(), + bankName.getBankName(), + bankAccountNumber.getBankAccountNumber(), + linkedStatusIsValid.isLinkedStatusIsValid() + ) + ); + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/SpringDataRegisteredBankAccountRepository.java b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/SpringDataRegisteredBankAccountRepository.java new file mode 100644 index 00000000..3bf9299c --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/adapter/out/persistence/SpringDataRegisteredBankAccountRepository.java @@ -0,0 +1,6 @@ +package org.example.banking.adapter.out.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SpringDataRegisteredBankAccountRepository extends JpaRepository { +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountCommand.java b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountCommand.java new file mode 100644 index 00000000..9c38efef --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountCommand.java @@ -0,0 +1,36 @@ +package org.example.banking.application.port.in; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.example.common.SelfValidating; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.NotEmpty; + +@Builder +@Data +@EqualsAndHashCode(callSuper = false) +public class RegisterBankAccountCommand extends SelfValidating { + + @NotEmpty + private final String membershipId; + + @NotEmpty + private final String bankName; + + @NotEmpty + private final String bankAccountNumber; + + @AssertTrue + private final boolean isValid; + + public RegisterBankAccountCommand(String membershipId, String bankName, String bankAccountNumber, boolean isValid) { + this.membershipId = membershipId; + this.bankName = bankName; + this.bankAccountNumber = bankAccountNumber; + this.isValid = isValid; + + this.validateSelf(); + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountUseCase.java b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountUseCase.java new file mode 100644 index 00000000..c9645193 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/in/RegisterBankAccountUseCase.java @@ -0,0 +1,8 @@ +package org.example.banking.application.port.in; + +import org.example.banking.domain.RegisteredBankAccount; + +public interface RegisterBankAccountUseCase { + + RegisteredBankAccount registerBankAccount(RegisterBankAccountCommand command); +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RegisterBankAccountPort.java b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RegisterBankAccountPort.java new file mode 100644 index 00000000..7ca9269d --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RegisterBankAccountPort.java @@ -0,0 +1,14 @@ +package org.example.banking.application.port.out; + +import org.example.banking.adapter.out.persistence.RegisteredBankAccountJpaEntity; +import org.example.banking.domain.RegisteredBankAccount; + +public interface RegisterBankAccountPort { + + RegisteredBankAccountJpaEntity createRegisteredBankAccount( + RegisteredBankAccount.MembershipId membershipId, + RegisteredBankAccount.BankName bankName, + RegisteredBankAccount.BankAccountNumber bankAccountNumber, + RegisteredBankAccount.LinkedStatusIsValid linkedStatusIsValid + ); +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RequestBankAccountInfoPort.java b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RequestBankAccountInfoPort.java new file mode 100644 index 00000000..dfeae33a --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/application/port/out/RequestBankAccountInfoPort.java @@ -0,0 +1,9 @@ +package org.example.banking.application.port.out; + +import org.example.banking.adapter.out.external.bank.BankAccount; +import org.example.banking.adapter.out.external.bank.GetBankAccountRequest; + +public interface RequestBankAccountInfoPort { + + BankAccount getBankAccountInfo(GetBankAccountRequest request); +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/application/service/RegisterBankAccountService.java b/bobby-pay/banking-service/src/main/java/org/example/banking/application/service/RegisterBankAccountService.java new file mode 100644 index 00000000..9d7944a1 --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/application/service/RegisterBankAccountService.java @@ -0,0 +1,47 @@ +package org.example.banking.application.service; + +import lombok.RequiredArgsConstructor; +import org.example.banking.adapter.out.external.bank.BankAccount; +import org.example.banking.adapter.out.external.bank.GetBankAccountRequest; +import org.example.banking.adapter.out.persistence.RegisteredBankAccountJpaEntity; +import org.example.banking.adapter.out.persistence.RegisteredBankAccountMapper; +import org.example.banking.application.port.in.RegisterBankAccountCommand; +import org.example.banking.application.port.in.RegisterBankAccountUseCase; +import org.example.banking.application.port.out.RegisterBankAccountPort; +import org.example.banking.application.port.out.RequestBankAccountInfoPort; +import org.example.banking.domain.RegisteredBankAccount; +import org.example.common.UseCase; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@Transactional +@RequiredArgsConstructor +public class RegisterBankAccountService implements RegisterBankAccountUseCase { + + private final RegisterBankAccountPort registerBankAccountPort; + private final RegisteredBankAccountMapper mapper; + private final RequestBankAccountInfoPort requestBankAccountInfoPort; + + @Override + public RegisteredBankAccount registerBankAccount(RegisterBankAccountCommand command) { + + BankAccount accountInfo = requestBankAccountInfoPort.getBankAccountInfo(new GetBankAccountRequest(command.getBankName(), command.getBankAccountNumber())); + + boolean accountIsValid = accountInfo.isValid(); + + if (accountIsValid) { + + RegisteredBankAccountJpaEntity savedAccountInfo = registerBankAccountPort.createRegisteredBankAccount( + new RegisteredBankAccount.MembershipId(command.getMembershipId() + ""), + new RegisteredBankAccount.BankName(accountInfo.getBankName()), + new RegisteredBankAccount.BankAccountNumber(accountInfo.getBankAccountNumber()), + new RegisteredBankAccount.LinkedStatusIsValid(true) + ); + + return mapper.mapToDomainEntity(savedAccountInfo); + } else { + return null; + } + + } +} diff --git a/bobby-pay/banking-service/src/main/java/org/example/banking/domain/RegisteredBankAccount.java b/bobby-pay/banking-service/src/main/java/org/example/banking/domain/RegisteredBankAccount.java new file mode 100644 index 00000000..1810127d --- /dev/null +++ b/bobby-pay/banking-service/src/main/java/org/example/banking/domain/RegisteredBankAccount.java @@ -0,0 +1,84 @@ +package org.example.banking.domain; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Value; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RegisteredBankAccount { + + private final String registeredBankAccountId; + private final String membershipId; + private final String bankName; + private final String bankAccountNumber; + private final boolean linkedStatusIsValid; + + + public static RegisteredBankAccount generateRegisteredBankAccount( + RegisteredBankAccountId registeredBankAccountId, + MembershipId membershipId, + BankName bankName, + BankAccountNumber bankAccountNumber, + LinkedStatusIsValid linkedStatusIsValid + ) { + return new RegisteredBankAccount( + registeredBankAccountId.registeredBankAccountId, + membershipId.membershipId, + bankName.bankName, + bankAccountNumber.bankAccountNumber, + linkedStatusIsValid.linkedStatusIsValid + ); + } + + @Value + public static class RegisteredBankAccountId { + + String registeredBankAccountId; + + public RegisteredBankAccountId(String value) { + this.registeredBankAccountId = value; + } + } + + @Value + public static class MembershipId { + + String membershipId; + + public MembershipId(String value) { + this.membershipId = value; + } + } + + @Value + public static class BankName { + + String bankName; + + public BankName(String value) { + this.bankName = value; + } + } + + @Value + public static class BankAccountNumber { + + String bankAccountNumber; + + public BankAccountNumber(String value) { + this.bankAccountNumber = value; + } + } + + @Value + public static class LinkedStatusIsValid { + + boolean linkedStatusIsValid; + + public LinkedStatusIsValid(boolean value) { + this.linkedStatusIsValid = value; + } + } +} diff --git a/bobby-pay/banking-service/src/test/java/org/example/banking/adapter/in/web/RegisterBankAccountControllerTest.java b/bobby-pay/banking-service/src/test/java/org/example/banking/adapter/in/web/RegisterBankAccountControllerTest.java new file mode 100644 index 00000000..28b4b02a --- /dev/null +++ b/bobby-pay/banking-service/src/test/java/org/example/banking/adapter/in/web/RegisterBankAccountControllerTest.java @@ -0,0 +1,63 @@ +package org.example.banking.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.example.banking.domain.RegisteredBankAccount; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +@SpringBootTest +@AutoConfigureMockMvc +class RegisterBankAccountControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private WebApplicationContext ctx; + + @Autowired + private ObjectMapper mapper; + + @BeforeEach + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx) + .addFilters(new CharacterEncodingFilter("UTF-8", true)) + .alwaysDo(print()) + .build(); + } + + @Test + void registerBankAccountTest() throws Exception { + RegisterBankAccountRequest request = + new RegisterBankAccountRequest("1", "국민은행", "1234567890", true); + + RegisteredBankAccount bankAccount = RegisteredBankAccount.generateRegisteredBankAccount( + new RegisteredBankAccount.RegisteredBankAccountId("1"), + new RegisteredBankAccount.MembershipId("1"), + new RegisteredBankAccount.BankName("국민은행"), + new RegisteredBankAccount.BankAccountNumber("1234567890"), + new RegisteredBankAccount.LinkedStatusIsValid(true) + ); + + mockMvc.perform( + MockMvcRequestBuilders.post("/banking/account/register") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(request)) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string(mapper.writeValueAsString(bankAccount))) + ; + } +} \ No newline at end of file diff --git a/bobby-pay/common/src/main/java/org/example/common/ExternalSystemAdapter.java b/bobby-pay/common/src/main/java/org/example/common/ExternalSystemAdapter.java new file mode 100644 index 00000000..07c7fcd2 --- /dev/null +++ b/bobby-pay/common/src/main/java/org/example/common/ExternalSystemAdapter.java @@ -0,0 +1,16 @@ +package org.example.common; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ExternalSystemAdapter { + + @AliasFor(annotation = Component.class) + String value() default ""; +} diff --git a/bobby-pay/membership-service/build.gradle b/bobby-pay/membership-service/build.gradle index ee2068a8..6ca0aa6c 100644 --- a/bobby-pay/membership-service/build.gradle +++ b/bobby-pay/membership-service/build.gradle @@ -3,7 +3,7 @@ plugins { } group 'org.example.membership' -version '1.0.1' +version '1.0.0' repositories { mavenCentral() diff --git a/bobby-pay/settings.gradle b/bobby-pay/settings.gradle index bc58b27f..0e567f56 100644 --- a/bobby-pay/settings.gradle +++ b/bobby-pay/settings.gradle @@ -1,4 +1,5 @@ rootProject.name = 'bobby-pay' include 'membership-service' include 'common' +include 'banking-service'