From 6f653a31cd24d1463f9240b0607664f8d5e926bf Mon Sep 17 00:00:00 2001 From: vitorsob Date: Sat, 4 Feb 2023 12:30:38 +0000 Subject: [PATCH 1/2] PAYM-95 - Create Invoice --- .../invoice/InvoiceApiController.java | 95 ++++++++++++++ .../invoice/InvoiceApiControllerImpl.java | 78 +++++++++++ .../InvoiceLinkCreationException.java | 8 ++ .../exception/InvoiceProcessorException.java | 5 + .../exception/InvoiceRequestNotFound.java | 5 + .../payment/mapper/InvoiceRequestMapper.java | 34 +++++ .../payment/mapper/TransactionMapper.java | 16 +++ .../payment/model/InvoiceConfirmationDTO.java | 24 ++++ .../model/InvoiceConfirmationRequest.java | 43 +++++++ .../chain/payment/model/InvoiceRequest.java | 109 ++++++++++++++++ .../payment/model/InvoiceRequestDTO.java | 46 +++++++ .../chain/payment/model/InvoiceResponse.java | 12 ++ .../payment/model/ProductInvoiceRequest.java | 28 ++++ .../payment/model/enums/InvoiceStatus.java | 21 +++ .../payment/model/enums/InvoiceType.java | 16 +++ .../persistence/InvoiceRequestDbMapper.java | 28 ++++ .../persistence/InvoiceRequestDbService.java | 14 ++ .../InvoiceRequestDbServiceImpl.java | 32 +++++ .../persistence/InvoiceRequestEntity.java | 49 +++++++ .../persistence/InvoiceRequestRepository.java | 15 +++ .../invoice/InvoiceLinkServiceImpl.java | 121 ++++++++++++++++++ .../invoice/InvoiceProcessorService.java | 26 ++++ .../invoice/InvoiceProcessorServiceImpl.java | 84 ++++++++++++ .../service/invoice/InvoiceService.java | 20 +++ src/main/resources/application.yml | 3 + 25 files changed, 932 insertions(+) create mode 100644 src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java create mode 100644 src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java create mode 100644 src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceResponse.java create mode 100644 src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java create mode 100644 src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java diff --git a/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java new file mode 100644 index 0000000..508de0b --- /dev/null +++ b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java @@ -0,0 +1,95 @@ +package com.cross.chain.payment.controller.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; +import com.cross.chain.payment.model.enums.InvoiceType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + + +@Tag(name = "invoices", description = "Invoice Operations") +public interface InvoiceApiController { + + String INVOICE = "/invoice"; + String INVOICE_HASH = "/invoice/{invoiceHash}"; + String INVOICE_HASH_TRANSACTION = "/invoice/{invoiceHash}/transaction"; + String INVOICE_HASH_CONFIRMATION = "/invoice/{invoiceHash}/confirmation"; + String INVOICE_HASH_CANCELLATION = "/invoice/{invoiceHash}/cancellation"; + String INVOICE_FIND_BY_USER_ADDRESS = "/invoice/findByUserAddress"; + + @Operation(summary = "create a invoice", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity createInvoice(@Parameter(in = ParameterIn.DEFAULT, description = "Created invoice object", schema=@Schema()) @RequestBody InvoiceRequest body); + + + @Operation(summary = "get all invoices with filter", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied")}) + ResponseEntity invoiceByHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "get all transaction with filter", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied")}) + ResponseEntity> transactionByInvoiceHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "Update invoice given a hash", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "Invoice not found") }) + ResponseEntity updateInvoiceByHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema()) @PathVariable(value = "invoiceHash") String invoiceHash, @RequestBody InvoiceRequest body) throws InvoiceRequestNotFound; + + @Operation(summary = "Invoice confirmation", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity invoiceConfirmation(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash, + @Parameter(in = ParameterIn.DEFAULT, description = "Created invoice object", schema=@Schema()) @RequestBody InvoiceConfirmationRequest body) throws InvoiceRequestNotFound; + + @Operation(summary = "Invoice cancellation", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity invoiceCancellation(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "get all invoices with filter", description = "(INTERNAL USAGE ONLY)", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity getAllInvoice(@Parameter(in = ParameterIn.QUERY, description = "accountId that belongs the invoices" ,schema=@Schema())@RequestParam(value = "accountId", required = false) String accountId, @Parameter(in = ParameterIn.QUERY, description = "Invoice type that need to be considered for filter" ,schema=@Schema()) InvoiceType invoiceType); + + + @Operation(summary = "get a invoice by User Address", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity> getAllInvoiceGivenUserAddressAndFilters(@Parameter(in = ParameterIn.QUERY, description = "User address that owns the invoices" ,required=true,schema=@Schema()) String accountId, @Parameter(in = ParameterIn.QUERY, description = "Invoice type that need to be considered for filter" ,schema=@Schema()) InvoiceType invoiceType); + +} + diff --git a/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java new file mode 100644 index 0000000..ef9f47f --- /dev/null +++ b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java @@ -0,0 +1,78 @@ +package com.cross.chain.payment.controller.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.service.invoice.InvoiceProcessorService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@RequestMapping(value = "/api/v1") +public class InvoiceApiControllerImpl implements InvoiceApiController { + + @Autowired + private InvoiceProcessorService invoiceProcessorService; + @Override + @PostMapping(value = INVOICE, produces = { APPLICATION_JSON_VALUE }, consumes = { APPLICATION_JSON_VALUE }) + public ResponseEntity createInvoice(@Valid @RequestBody InvoiceRequest body) { + return ResponseEntity.ok(invoiceProcessorService.processInvoiceRequest(body)); + } + + @Override + @GetMapping(value = INVOICE_HASH, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity invoiceByHash(@PathVariable("invoiceHash") String invoiceHash) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.retrieveInvoiceRequest(invoiceHash)); + } + + @Override + @GetMapping(value = INVOICE_HASH_TRANSACTION, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity> transactionByInvoiceHash(String invoiceHash) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.retrieveInvoiceTransactions(invoiceHash)); + } + + @Override + @PatchMapping(value = INVOICE_HASH, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity updateInvoiceByHash(@PathVariable("invoiceHash") String invoiceHash, @Valid @RequestBody InvoiceRequest body) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.updateInvoiceRequest(invoiceHash, body)); + } + + @Override + @PostMapping(value = INVOICE_HASH_CONFIRMATION, consumes = { APPLICATION_JSON_VALUE }) + public ResponseEntity invoiceConfirmation(@PathVariable("invoiceHash") String invoiceHash, @Valid @RequestBody InvoiceConfirmationRequest body) throws InvoiceRequestNotFound { + invoiceProcessorService.invoiceConfirmation(invoiceHash, body); + return ResponseEntity.noContent().build(); + } + + @Override + @PostMapping(value = INVOICE_HASH_CANCELLATION) + public ResponseEntity invoiceCancellation(@PathVariable("invoiceHash") String invoiceHash) throws InvoiceRequestNotFound { + invoiceProcessorService.invoiceCancellation(invoiceHash); + return ResponseEntity.noContent().build(); + } + + @Override + @GetMapping(value = INVOICE, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity getAllInvoice(@Valid @RequestParam(value = "accountId", required = false) String accountId, @Valid @RequestParam(value = "invoiceType", required = false) InvoiceType invoiceType) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + @Override + @GetMapping(value = INVOICE_FIND_BY_USER_ADDRESS, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity> getAllInvoiceGivenUserAddressAndFilters(@NotNull @Valid @RequestParam(value = "address") String address, @Valid @RequestParam(value = "invoiceType", required = false) InvoiceType invoiceType) { + return ResponseEntity.ok(invoiceProcessorService.retrieveByUserAddress(address)); + } + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java b/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java new file mode 100644 index 0000000..b99f9ce --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java @@ -0,0 +1,8 @@ +package com.cross.chain.payment.exception; + +public class InvoiceLinkCreationException extends RuntimeException { + public InvoiceLinkCreationException(Exception exception){ + super(exception); + } + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java b/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java new file mode 100644 index 0000000..2d5d53b --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java @@ -0,0 +1,5 @@ +package com.cross.chain.payment.exception; + +public class InvoiceProcessorException extends RuntimeException{ + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java b/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java new file mode 100644 index 0000000..72bd6bd --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java @@ -0,0 +1,5 @@ +package com.cross.chain.payment.exception; + +public class InvoiceRequestNotFound extends Exception { + +} diff --git a/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java b/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java new file mode 100644 index 0000000..7d1f8cb --- /dev/null +++ b/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java @@ -0,0 +1,34 @@ +package com.cross.chain.payment.mapper; + +import com.cross.chain.payment.model.InvoiceConfirmationDTO; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceRequestDTO; +import org.bson.BsonBinarySubType; +import org.bson.types.Binary; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface InvoiceRequestMapper { + + InvoiceRequestDTO map(InvoiceRequest invoiceRequest); + @Mapping(source = "createdAt", target = "createdAt", dateFormat = "dd/MM/yyyy HH:mm:ss") + InvoiceRequest map(InvoiceRequestDTO invoiceRequest); + + InvoiceConfirmationDTO map(InvoiceConfirmationRequest invoiceConfirmationDTO); + + default Binary map(byte[] value){ + if(value == null){ + return null; + } + return new Binary(BsonBinarySubType.BINARY, value); + } + default byte[] map(Binary value){ + if(value == null){ + return null; + } + return value.getData(); + } + +} diff --git a/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java b/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java index e112970..89b2ca0 100644 --- a/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java +++ b/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java @@ -1,5 +1,6 @@ package com.cross.chain.payment.mapper; +import com.cross.chain.payment.model.InvoiceConfirmationDTO; import com.cross.chain.payment.model.PaymentConfirmationDTO; import com.cross.chain.payment.model.Transaction; import com.cross.chain.payment.model.TransactionDTO; @@ -27,6 +28,21 @@ public interface TransactionMapper { }) TransactionDTO map(PaymentConfirmationDTO paymentConfirmationDTO); + @Mappings({ + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.transactionHash", target = "transactionHash"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.blockHash", target = "blockHash"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.blockNumber", target = "blockNumber"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.gasUsed", target = "gasUsed"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.toAddress", target = "toAddress"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.fromAddress", target = "fromAddress"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.confirmations", target = "confirmations"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.name", target = "customerInfo.name"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.email", target = "customerInfo.email"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.phoneNumber", target = "customerInfo.phoneNumber"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.shippingAddress", target = "customerInfo.shippingAddress") + }) + TransactionDTO map(InvoiceConfirmationDTO invoiceConfirmationDTO); + @Mappings({ @Mapping(source = "transaction.customerInfo.name", target = "customerInfo.name"), @Mapping(source = "transaction.customerInfo.email", target = "customerInfo.email"), diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java new file mode 100644 index 0000000..7c16284 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java @@ -0,0 +1,24 @@ +package com.cross.chain.payment.model; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.List; + +/** + * PaymentConfirmation + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class InvoiceConfirmationDTO { + private TransactionDetailsDTO transactionDetails; + private BigDecimal amount; + private List products; + private CustomerInfoDTO customerInfoDTO; +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java new file mode 100644 index 0000000..7df4b4a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java @@ -0,0 +1,43 @@ +package com.cross.chain.payment.model; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import java.math.BigDecimal; +import java.util.List; + +/** + * PaymentConfirmation + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Validated +public class InvoiceConfirmationRequest { + + @Valid + @JsonProperty("transactionDetails") + private TransactionDetails transactionDetails; + + @Valid + @JsonProperty("amountPaid") + private BigDecimal amount; + + @Valid + @JsonProperty("products") + private List products; + + @Valid + @JsonProperty("customerInfo") + private CustomerInfoDTO customerInfoDTO; + +} diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java b/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java new file mode 100644 index 0000000..5d5eeaf --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java @@ -0,0 +1,109 @@ +package com.cross.chain.payment.model; + +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +/** + * InvoiceRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Validated +public class InvoiceRequest { + + @Schema(example = "507f191e810c19729de860ea") + @JsonProperty("id") + private String id; + + @Schema(example = "Name of what is being offered") + @JsonProperty("title") + private String title; + + @Schema(example = "Invoice memo") + @JsonProperty("memo") + private String memo; + + @Schema(example = "Due date") + @JsonProperty("dueDate") + private String dueDate; + + @Schema(example = "Customer") + @JsonProperty("customer") + private Customer customer; + + @JsonProperty("hash") + private String hash; + @JsonProperty("uuid") + private UUID uuid; + + @JsonProperty("createdAt") + private String createdAt; + + @Valid + @Schema(example = "55.34", description = "") + @JsonProperty("amount") + private BigDecimal amount; + + @Valid + @JsonProperty("cryptocurrency") + private Cryptocurrency cryptocurrency; + + @Schema(example = "false", description = "If true minAmount must be filled") + @JsonProperty("minInvoice") + private Boolean minInvoice; + + @Schema(example = "true", description = "If true minAmount and maxAmount must be filled") + @JsonProperty("invoiceLimits") + private Boolean invoiceLimits; + + @Valid + @Schema(example = "0.5", description = "") + @JsonProperty("minAmount") + private BigDecimal minAmount; + + @Valid + @Schema(example = "10") + @JsonProperty("maxAmount") + private BigDecimal maxAmount; + + @Valid + @JsonProperty("products") + private List products; + + @Valid + @Schema(example = "0x4279953514f0009c5cb371df4d530f6fee0ede17") + @JsonProperty("creditAddress") + private String creditAddress; + + @Valid + @Schema(example = "0x4279953514f0009c5cb371df4d530f6fee0ede17") + @JsonProperty("userAddress") + private String userAddress; + + @JsonProperty("user") //TODO: review what information should be returned in here, cause is used on the invoicelink + private UserRequest user; + + @JsonProperty("invoiceStatus") + @Schema(hidden = true) + private InvoiceStatus invoiceStatus; + + @JsonProperty("invoiceLink") + @Schema(example = "https://buy.crosspay.com/test_28oaGzarrdlx6Pe288", description = "Required if invoice") + private String invoiceLink; + +} diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java b/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java new file mode 100644 index 0000000..c529174 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java @@ -0,0 +1,46 @@ +package com.cross.chain.payment.model; + +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * InvoiceRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class InvoiceRequestDTO { + private String id; + private String title; + private String memo; + private String dueDate; + private Customer customer; + private InvoiceType invoiceType; + private String hash; + private UUID uuid; + private LocalDateTime createdAt; + private BigDecimal amount; + private CryptocurrencyDTO cryptocurrency; + private Boolean minInvoice; + private Boolean invoiceLimits; + private BigDecimal minAmount; + private BigDecimal maxAmount; + private List products; + private TransactionDTO transaction; + private boolean adjustableQuantity; + private String creditAddress; + private String userAddress; + private UserDTO user; + private InvoiceStatus invoiceStatus; + private String invoiceLink; +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java b/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java new file mode 100644 index 0000000..07d110a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java @@ -0,0 +1,12 @@ +package com.cross.chain.payment.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class InvoiceResponse { + + private String paymentLink; + +} diff --git a/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java b/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java new file mode 100644 index 0000000..7ceddf2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java @@ -0,0 +1,28 @@ +package com.cross.chain.payment.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; + +/** + * ProductPaymentRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Validated +public class ProductInvoiceRequest { + + @Valid + @JsonProperty("item") + private ProductResponse product; + + private int quantity; + +} diff --git a/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java b/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java new file mode 100644 index 0000000..6ca742c --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java @@ -0,0 +1,21 @@ +package com.cross.chain.payment.model.enums; + +import java.util.Arrays; + +public enum InvoiceStatus { + + AWAITING_PAYMENT("AWAITING_PAYMENT"), + PAID("PAID"), + DEACTIVATED("DEACTIVATED"); + + private String value; + + InvoiceStatus(String value) { + this.value = value; + } + + public boolean isFinalStatus(){ + return Arrays.asList(PAID, DEACTIVATED).contains(valueOf(value)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java b/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java new file mode 100644 index 0000000..a926cb8 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java @@ -0,0 +1,16 @@ +package com.cross.chain.payment.model.enums; + +/** + * Payment types + */ +public enum InvoiceType { + + INVOICE("INVOICE"); + + private String value; + + InvoiceType(String value) { + this.value = value; + } + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java new file mode 100644 index 0000000..9f430dc --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java @@ -0,0 +1,28 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.model.InvoiceRequestDTO; +import org.bson.BsonBinarySubType; +import org.bson.types.Binary; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface InvoiceRequestDbMapper { + + InvoiceRequestEntity map(InvoiceRequestDTO invoiceRequest); + + InvoiceRequestDTO map(InvoiceRequestEntity invoiceRequestEntity); + + default Binary map(byte[] value){ + if(value == null){ + return null; + } + return new Binary(BsonBinarySubType.BINARY, value); + } + default byte[] map(Binary value){ + if(value == null){ + return null; + } + return value.getData(); + } + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java new file mode 100644 index 0000000..d3689e2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java @@ -0,0 +1,14 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceRequestDTO; + +import java.util.List; + +public interface InvoiceRequestDbService { + InvoiceRequestDTO findByHash(String invoiceHash) throws InvoiceRequestNotFound; + + InvoiceRequestDTO save(InvoiceRequestDTO invoiceRequestFound); + + List findByUserAddress(String address); +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java new file mode 100644 index 0000000..9d289ff --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java @@ -0,0 +1,32 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceRequestDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class InvoiceRequestDbServiceImpl implements InvoiceRequestDbService { + + private final InvoiceRequestRepository repository; + private final InvoiceRequestDbMapper mapper; + @Override + public InvoiceRequestDTO findByHash(String invoiceHash) throws InvoiceRequestNotFound { + return mapper.map(repository.findByHash(invoiceHash).orElseThrow(InvoiceRequestNotFound::new)); + } + + @Override + public InvoiceRequestDTO save(InvoiceRequestDTO invoiceRequest) { + InvoiceRequestEntity invoiceRequestEntity = mapper.map(invoiceRequest); + return mapper.map(repository.save(invoiceRequestEntity)); + } + + @Override + public List findByUserAddress(String address) { + return repository.findByUserAddress(address).stream().map(mapper::map).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java new file mode 100644 index 0000000..8d8fd50 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java @@ -0,0 +1,49 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.model.Customer; +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Data +@Document(collection = "invoice-request") +class InvoiceRequestEntity { + + @Id + private String id; + private String memo; + private String dueDate; + private Customer customer; + private String creditAddress; + private String userAddress; + private String hash; + private UUID uuid; + private String invoiceLink; + @Indexed + @CreatedDate + private LocalDateTime createdAt; + private BigDecimal amount; + @DBRef + private UserEntity user; + @DBRef + private CryptocurrencyEntity cryptocurrency; + private InvoiceType invoiceType; + private InvoiceStatus invoiceStatus; + private List products = new ArrayList<>(); + @DBRef + private TransactionEntity transaction; + + private String title; + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java new file mode 100644 index 0000000..c19fc8d --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java @@ -0,0 +1,15 @@ +package com.cross.chain.payment.persistence; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +interface InvoiceRequestRepository extends MongoRepository { + + Optional findByHash(String hash); + List findByUserAddress(String address); + +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java new file mode 100644 index 0000000..68a83f9 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java @@ -0,0 +1,121 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceLinkCreationException; +import com.cross.chain.payment.exception.ProductNotFoundException; +import com.cross.chain.payment.exception.UserNotFoundException; +import com.cross.chain.payment.mapper.InvoiceRequestMapper; +import com.cross.chain.payment.mapper.ProductMapper; +import com.cross.chain.payment.mapper.TransactionMapper; +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.persistence.InvoiceRequestDbService; +import com.cross.chain.payment.persistence.ProductDbService; +import com.cross.chain.payment.persistence.TransactionDbService; +import com.cross.chain.payment.persistence.UserDbService; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class InvoiceLinkServiceImpl implements InvoiceService { + + @Value("${invoice.hash-length}") + private int hashLength; + + @Value("${invoice.url}") + private String url; + + private final InvoiceRequestMapper invoiceRequestMapper; + + private final ProductMapper productMapper; + + private final TransactionMapper transactionMapper; + + private final InvoiceRequestDbService invoiceRequestDbService; + + private final UserDbService userDbService; + + private final TransactionDbService transactionDbService; + private final ProductDbService productDbService; + + @Override + public InvoiceResponse create(InvoiceRequest invoiceRequest) { + validateInvoice(invoiceRequest); //TODO: move this to a bean validator? + InvoiceRequestDTO invoiceRequestDTO = invoiceRequestMapper.map(invoiceRequest); + try { + invoiceRequestDTO = createLink(invoiceRequestDTO); + } catch (UserNotFoundException e) { + throw new InvoiceLinkCreationException(e); + } + return InvoiceResponse.builder() + .paymentLink(invoiceRequestDTO.getInvoiceLink()) + .build(); + } + + @Override + public InvoiceRequestDTO confirm(InvoiceRequestDTO invoiceRequest, InvoiceConfirmationDTO invoiceConfirmationDTO) { + TransactionDTO transaction = transactionMapper.map(invoiceConfirmationDTO); + transaction.setCryptocurrency(invoiceRequest.getCryptocurrency()); + updateProductDetails(transaction.getProducts()); + invoiceRequest.setTransaction(transactionDbService.save(transaction)); + invoiceRequest.setInvoiceStatus(InvoiceStatus.PAID);; + return invoiceRequestDbService.save(invoiceRequest); + } + + @Override + public InvoiceRequestDTO cancel(InvoiceRequestDTO invoiceRequest) { + //TODO: create a transaction history using the invoiceConfirmation + if(invoiceRequest.getInvoiceStatus().isFinalStatus()){ + throw new RuntimeException(); //TODO: change exception + } + //TODO: increase the total supply since the invoice was cancelled. + invoiceRequest.setInvoiceStatus(InvoiceStatus.DEACTIVATED); + return invoiceRequestDbService.save(invoiceRequest); + } + + @Override + public void validateInvoice(InvoiceRequest invoiceRequest) { + //TODO: validate if contains all the required information to create the link + Assert.notNull(invoiceRequest.getAmount(), String.format("Amount is required for an Invoice")); + } + + @Override + public InvoiceType typeInvoice() { + return InvoiceType.INVOICE; + } + + + private InvoiceRequestDTO createLink(InvoiceRequestDTO invoiceRequestDTO) throws UserNotFoundException { + UserDTO user = userDbService.findBySignerAddress(invoiceRequestDTO.getUserAddress()); + invoiceRequestDTO.setUser(user); + invoiceRequestDTO.setHash(RandomStringUtils.randomAlphabetic(hashLength)); + invoiceRequestDTO.setInvoiceLink(url.concat("/").concat(invoiceRequestDTO.getHash())); + invoiceRequestDTO.setInvoiceStatus(InvoiceStatus.AWAITING_PAYMENT); + invoiceRequestDTO.setUuid(UUID.randomUUID()); + return invoiceRequestDbService.save(invoiceRequestDTO); + } + + private void updateProductDetails(List products) { + products.forEach(item-> { + ProductDTO product; + try { + product = productDbService.findById(item.getProduct().getId()); + } catch (ProductNotFoundException e) { + throw new RuntimeException(e); //TODO: throw an payment Execution error - Product not found + } + if(item.getQuantity() > product.getTotalSupply()){ + throw new RuntimeException(); //TODO: throw an payment Execution error - Amount selected not valid + } + product.setTotalSupply(product.getTotalSupply() - item.getQuantity()); + productDbService.save(product); + }); + } + +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java new file mode 100644 index 0000000..be60a94 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java @@ -0,0 +1,26 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; + +import java.util.List; + +public interface InvoiceProcessorService { + + InvoiceResponse processInvoiceRequest(InvoiceRequest invoiceRequest); + + InvoiceRequest retrieveInvoiceRequest(String invoiceHash) throws InvoiceRequestNotFound; + + List retrieveInvoiceTransactions(String invoiceHash) throws InvoiceRequestNotFound; + + InvoiceRequest updateInvoiceRequest(String invoiceHash, InvoiceRequest invoiceRequest) throws InvoiceRequestNotFound; + + List retrieveByUserAddress(String address); + + void invoiceConfirmation(String invoiceHash, InvoiceConfirmationRequest invoiceConfirmation) throws InvoiceRequestNotFound; + + void invoiceCancellation(String invoiceHash) throws InvoiceRequestNotFound; +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java new file mode 100644 index 0000000..b5f5f0a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java @@ -0,0 +1,84 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceProcessorException; +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.mapper.InvoiceRequestMapper; +import com.cross.chain.payment.mapper.TransactionMapper; +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.persistence.InvoiceRequestDbService; +import com.cross.chain.payment.service.product.ProductService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class InvoiceProcessorServiceImpl implements InvoiceProcessorService { + + private final List invoiceServices; + private final ProductService productService; + private final InvoiceRequestMapper invoiceRequestMapper; + + private final TransactionMapper transactionMapper; + private final InvoiceRequestDbService invoiceRequestDbService; + + @Override + public InvoiceResponse processInvoiceRequest(InvoiceRequest invoiceRequest) { + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + return invoiceService.create(invoiceRequest); + } + + @Override + public InvoiceRequest retrieveInvoiceRequest(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + return invoiceRequestMapper.map(invoiceRequest); + } + + @Override + public List retrieveInvoiceTransactions(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + return Collections.singletonList(transactionMapper.map(invoiceRequest.getTransaction())); + } + + @Override + public InvoiceRequest updateInvoiceRequest(String invoiceHash, InvoiceRequest invoiceRequest) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequestFound = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceRequestDTO invoiceUpdated = invoiceRequestMapper.map(invoiceRequest); + invoiceRequestFound.setCreditAddress(invoiceUpdated.getCreditAddress()); + invoiceRequestFound.setMemo(invoiceUpdated.getMemo()); + invoiceRequestFound.setAmount(invoiceUpdated.getAmount()); + invoiceRequestFound.setProducts(invoiceUpdated.getProducts()); + invoiceRequestDbService.save(invoiceRequestFound); + return invoiceRequestMapper.map(invoiceRequestFound); + } + + @Override + public List retrieveByUserAddress(String address) { + return invoiceRequestDbService.findByUserAddress(address).stream().map(invoiceRequestMapper::map).collect(Collectors.toList()); + } + + @Override + public void invoiceConfirmation(String invoiceHash, InvoiceConfirmationRequest invoiceConfirmationRequest) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + invoiceService.confirm(invoiceRequest, invoiceRequestMapper.map(invoiceConfirmationRequest)); + } + + @Override + public void invoiceCancellation(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + invoiceService.cancel(invoiceRequest); + } + + private InvoiceService getInvoiceService(InvoiceType invoiceType) { + return invoiceServices.stream() + .filter(p -> p.applies(invoiceType)) + .findFirst() + .orElseThrow(InvoiceProcessorException::new); + } +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java new file mode 100644 index 0000000..a06eae2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java @@ -0,0 +1,20 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceType; + +public interface InvoiceService { + + InvoiceResponse create(InvoiceRequest invoiceRequest); + + InvoiceRequestDTO confirm(InvoiceRequestDTO invoiceRequest, InvoiceConfirmationDTO invoiceConfirmationDTO); + + InvoiceRequestDTO cancel(InvoiceRequestDTO invoiceRequest); + + void validateInvoice(InvoiceRequest invoiceRequest); + default boolean applies(InvoiceType type){ + return typeInvoice().equals(type); + } + InvoiceType typeInvoice(); + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c04f2ba..c583c51 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,9 @@ spring: payment: hash-length: 50 url: localhost:3000/crypto-payment +invoice: + hash-length: 50 + url: localhost:3000/crypto-invoice springfox: documentation: From 130b6637e4a26b368c02c324d0ecbbb6585346b0 Mon Sep 17 00:00:00 2001 From: vitorsob Date: Sat, 4 Feb 2023 12:30:38 +0000 Subject: [PATCH 2/2] PAYM-95 - Create Invoice --- .../invoice/InvoiceApiController.java | 95 ++++++++++++++ .../invoice/InvoiceApiControllerImpl.java | 78 +++++++++++ .../InvoiceLinkCreationException.java | 8 ++ .../exception/InvoiceProcessorException.java | 5 + .../exception/InvoiceRequestNotFound.java | 5 + .../payment/mapper/InvoiceRequestMapper.java | 34 +++++ .../payment/mapper/TransactionMapper.java | 16 +++ .../payment/model/InvoiceConfirmationDTO.java | 24 ++++ .../model/InvoiceConfirmationRequest.java | 43 +++++++ .../chain/payment/model/InvoiceRequest.java | 109 ++++++++++++++++ .../payment/model/InvoiceRequestDTO.java | 46 +++++++ .../chain/payment/model/InvoiceResponse.java | 12 ++ .../payment/model/ProductInvoiceRequest.java | 28 ++++ .../payment/model/enums/InvoiceStatus.java | 21 +++ .../payment/model/enums/InvoiceType.java | 16 +++ .../persistence/InvoiceRequestDbMapper.java | 28 ++++ .../persistence/InvoiceRequestDbService.java | 14 ++ .../InvoiceRequestDbServiceImpl.java | 32 +++++ .../persistence/InvoiceRequestEntity.java | 49 +++++++ .../persistence/InvoiceRequestRepository.java | 15 +++ .../invoice/InvoiceLinkServiceImpl.java | 121 ++++++++++++++++++ .../invoice/InvoiceProcessorService.java | 26 ++++ .../invoice/InvoiceProcessorServiceImpl.java | 84 ++++++++++++ .../service/invoice/InvoiceService.java | 20 +++ src/main/resources/application.yml | 3 + 25 files changed, 932 insertions(+) create mode 100644 src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java create mode 100644 src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java create mode 100644 src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java create mode 100644 src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java create mode 100644 src/main/java/com/cross/chain/payment/model/InvoiceResponse.java create mode 100644 src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java create mode 100644 src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java create mode 100644 src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java create mode 100644 src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java create mode 100644 src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java diff --git a/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java new file mode 100644 index 0000000..508de0b --- /dev/null +++ b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiController.java @@ -0,0 +1,95 @@ +package com.cross.chain.payment.controller.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; +import com.cross.chain.payment.model.enums.InvoiceType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + + +@Tag(name = "invoices", description = "Invoice Operations") +public interface InvoiceApiController { + + String INVOICE = "/invoice"; + String INVOICE_HASH = "/invoice/{invoiceHash}"; + String INVOICE_HASH_TRANSACTION = "/invoice/{invoiceHash}/transaction"; + String INVOICE_HASH_CONFIRMATION = "/invoice/{invoiceHash}/confirmation"; + String INVOICE_HASH_CANCELLATION = "/invoice/{invoiceHash}/cancellation"; + String INVOICE_FIND_BY_USER_ADDRESS = "/invoice/findByUserAddress"; + + @Operation(summary = "create a invoice", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity createInvoice(@Parameter(in = ParameterIn.DEFAULT, description = "Created invoice object", schema=@Schema()) @RequestBody InvoiceRequest body); + + + @Operation(summary = "get all invoices with filter", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied")}) + ResponseEntity invoiceByHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "get all transaction with filter", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied")}) + ResponseEntity> transactionByInvoiceHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "Update invoice given a hash", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "Invoice not found") }) + ResponseEntity updateInvoiceByHash(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema()) @PathVariable(value = "invoiceHash") String invoiceHash, @RequestBody InvoiceRequest body) throws InvoiceRequestNotFound; + + @Operation(summary = "Invoice confirmation", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity invoiceConfirmation(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash, + @Parameter(in = ParameterIn.DEFAULT, description = "Created invoice object", schema=@Schema()) @RequestBody InvoiceConfirmationRequest body) throws InvoiceRequestNotFound; + + @Operation(summary = "Invoice cancellation", description = "", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity invoiceCancellation(@Parameter(in = ParameterIn.PATH, description = "hash that identify the invoice" ,schema=@Schema())@PathVariable(value = "invoiceHash") String invoiceHash) throws InvoiceRequestNotFound; + + @Operation(summary = "get all invoices with filter", description = "(INTERNAL USAGE ONLY)", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity getAllInvoice(@Parameter(in = ParameterIn.QUERY, description = "accountId that belongs the invoices" ,schema=@Schema())@RequestParam(value = "accountId", required = false) String accountId, @Parameter(in = ParameterIn.QUERY, description = "Invoice type that need to be considered for filter" ,schema=@Schema()) InvoiceType invoiceType); + + + @Operation(summary = "get a invoice by User Address", tags={ "invoices" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = InvoiceRequest.class))), + @ApiResponse(responseCode = "400", description = "Invalid data supplied"), + @ApiResponse(responseCode = "404", description = "User not found") }) + ResponseEntity> getAllInvoiceGivenUserAddressAndFilters(@Parameter(in = ParameterIn.QUERY, description = "User address that owns the invoices" ,required=true,schema=@Schema()) String accountId, @Parameter(in = ParameterIn.QUERY, description = "Invoice type that need to be considered for filter" ,schema=@Schema()) InvoiceType invoiceType); + +} + diff --git a/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java new file mode 100644 index 0000000..ef9f47f --- /dev/null +++ b/src/main/java/com/cross/chain/payment/controller/invoice/InvoiceApiControllerImpl.java @@ -0,0 +1,78 @@ +package com.cross.chain.payment.controller.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.service.invoice.InvoiceProcessorService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Slf4j +@RestController +@RequestMapping(value = "/api/v1") +public class InvoiceApiControllerImpl implements InvoiceApiController { + + @Autowired + private InvoiceProcessorService invoiceProcessorService; + @Override + @PostMapping(value = INVOICE, produces = { APPLICATION_JSON_VALUE }, consumes = { APPLICATION_JSON_VALUE }) + public ResponseEntity createInvoice(@Valid @RequestBody InvoiceRequest body) { + return ResponseEntity.ok(invoiceProcessorService.processInvoiceRequest(body)); + } + + @Override + @GetMapping(value = INVOICE_HASH, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity invoiceByHash(@PathVariable("invoiceHash") String invoiceHash) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.retrieveInvoiceRequest(invoiceHash)); + } + + @Override + @GetMapping(value = INVOICE_HASH_TRANSACTION, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity> transactionByInvoiceHash(String invoiceHash) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.retrieveInvoiceTransactions(invoiceHash)); + } + + @Override + @PatchMapping(value = INVOICE_HASH, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity updateInvoiceByHash(@PathVariable("invoiceHash") String invoiceHash, @Valid @RequestBody InvoiceRequest body) throws InvoiceRequestNotFound { + return ResponseEntity.ok(invoiceProcessorService.updateInvoiceRequest(invoiceHash, body)); + } + + @Override + @PostMapping(value = INVOICE_HASH_CONFIRMATION, consumes = { APPLICATION_JSON_VALUE }) + public ResponseEntity invoiceConfirmation(@PathVariable("invoiceHash") String invoiceHash, @Valid @RequestBody InvoiceConfirmationRequest body) throws InvoiceRequestNotFound { + invoiceProcessorService.invoiceConfirmation(invoiceHash, body); + return ResponseEntity.noContent().build(); + } + + @Override + @PostMapping(value = INVOICE_HASH_CANCELLATION) + public ResponseEntity invoiceCancellation(@PathVariable("invoiceHash") String invoiceHash) throws InvoiceRequestNotFound { + invoiceProcessorService.invoiceCancellation(invoiceHash); + return ResponseEntity.noContent().build(); + } + + @Override + @GetMapping(value = INVOICE, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity getAllInvoice(@Valid @RequestParam(value = "accountId", required = false) String accountId, @Valid @RequestParam(value = "invoiceType", required = false) InvoiceType invoiceType) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + @Override + @GetMapping(value = INVOICE_FIND_BY_USER_ADDRESS, produces = { APPLICATION_JSON_VALUE }) + public ResponseEntity> getAllInvoiceGivenUserAddressAndFilters(@NotNull @Valid @RequestParam(value = "address") String address, @Valid @RequestParam(value = "invoiceType", required = false) InvoiceType invoiceType) { + return ResponseEntity.ok(invoiceProcessorService.retrieveByUserAddress(address)); + } + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java b/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java new file mode 100644 index 0000000..b99f9ce --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceLinkCreationException.java @@ -0,0 +1,8 @@ +package com.cross.chain.payment.exception; + +public class InvoiceLinkCreationException extends RuntimeException { + public InvoiceLinkCreationException(Exception exception){ + super(exception); + } + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java b/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java new file mode 100644 index 0000000..2d5d53b --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceProcessorException.java @@ -0,0 +1,5 @@ +package com.cross.chain.payment.exception; + +public class InvoiceProcessorException extends RuntimeException{ + +} diff --git a/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java b/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java new file mode 100644 index 0000000..72bd6bd --- /dev/null +++ b/src/main/java/com/cross/chain/payment/exception/InvoiceRequestNotFound.java @@ -0,0 +1,5 @@ +package com.cross.chain.payment.exception; + +public class InvoiceRequestNotFound extends Exception { + +} diff --git a/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java b/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java new file mode 100644 index 0000000..7d1f8cb --- /dev/null +++ b/src/main/java/com/cross/chain/payment/mapper/InvoiceRequestMapper.java @@ -0,0 +1,34 @@ +package com.cross.chain.payment.mapper; + +import com.cross.chain.payment.model.InvoiceConfirmationDTO; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceRequestDTO; +import org.bson.BsonBinarySubType; +import org.bson.types.Binary; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface InvoiceRequestMapper { + + InvoiceRequestDTO map(InvoiceRequest invoiceRequest); + @Mapping(source = "createdAt", target = "createdAt", dateFormat = "dd/MM/yyyy HH:mm:ss") + InvoiceRequest map(InvoiceRequestDTO invoiceRequest); + + InvoiceConfirmationDTO map(InvoiceConfirmationRequest invoiceConfirmationDTO); + + default Binary map(byte[] value){ + if(value == null){ + return null; + } + return new Binary(BsonBinarySubType.BINARY, value); + } + default byte[] map(Binary value){ + if(value == null){ + return null; + } + return value.getData(); + } + +} diff --git a/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java b/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java index e112970..89b2ca0 100644 --- a/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java +++ b/src/main/java/com/cross/chain/payment/mapper/TransactionMapper.java @@ -1,5 +1,6 @@ package com.cross.chain.payment.mapper; +import com.cross.chain.payment.model.InvoiceConfirmationDTO; import com.cross.chain.payment.model.PaymentConfirmationDTO; import com.cross.chain.payment.model.Transaction; import com.cross.chain.payment.model.TransactionDTO; @@ -27,6 +28,21 @@ public interface TransactionMapper { }) TransactionDTO map(PaymentConfirmationDTO paymentConfirmationDTO); + @Mappings({ + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.transactionHash", target = "transactionHash"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.blockHash", target = "blockHash"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.blockNumber", target = "blockNumber"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.gasUsed", target = "gasUsed"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.toAddress", target = "toAddress"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.fromAddress", target = "fromAddress"), + @Mapping(source = "invoiceConfirmationDTO.transactionDetails.confirmations", target = "confirmations"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.name", target = "customerInfo.name"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.email", target = "customerInfo.email"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.phoneNumber", target = "customerInfo.phoneNumber"), + @Mapping(source = "invoiceConfirmationDTO.customerInfoDTO.shippingAddress", target = "customerInfo.shippingAddress") + }) + TransactionDTO map(InvoiceConfirmationDTO invoiceConfirmationDTO); + @Mappings({ @Mapping(source = "transaction.customerInfo.name", target = "customerInfo.name"), @Mapping(source = "transaction.customerInfo.email", target = "customerInfo.email"), diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java new file mode 100644 index 0000000..7c16284 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationDTO.java @@ -0,0 +1,24 @@ +package com.cross.chain.payment.model; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.List; + +/** + * PaymentConfirmation + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class InvoiceConfirmationDTO { + private TransactionDetailsDTO transactionDetails; + private BigDecimal amount; + private List products; + private CustomerInfoDTO customerInfoDTO; +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java new file mode 100644 index 0000000..7df4b4a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceConfirmationRequest.java @@ -0,0 +1,43 @@ +package com.cross.chain.payment.model; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import java.math.BigDecimal; +import java.util.List; + +/** + * PaymentConfirmation + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Validated +public class InvoiceConfirmationRequest { + + @Valid + @JsonProperty("transactionDetails") + private TransactionDetails transactionDetails; + + @Valid + @JsonProperty("amountPaid") + private BigDecimal amount; + + @Valid + @JsonProperty("products") + private List products; + + @Valid + @JsonProperty("customerInfo") + private CustomerInfoDTO customerInfoDTO; + +} diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java b/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java new file mode 100644 index 0000000..5d5eeaf --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceRequest.java @@ -0,0 +1,109 @@ +package com.cross.chain.payment.model; + +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +/** + * InvoiceRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Validated +public class InvoiceRequest { + + @Schema(example = "507f191e810c19729de860ea") + @JsonProperty("id") + private String id; + + @Schema(example = "Name of what is being offered") + @JsonProperty("title") + private String title; + + @Schema(example = "Invoice memo") + @JsonProperty("memo") + private String memo; + + @Schema(example = "Due date") + @JsonProperty("dueDate") + private String dueDate; + + @Schema(example = "Customer") + @JsonProperty("customer") + private Customer customer; + + @JsonProperty("hash") + private String hash; + @JsonProperty("uuid") + private UUID uuid; + + @JsonProperty("createdAt") + private String createdAt; + + @Valid + @Schema(example = "55.34", description = "") + @JsonProperty("amount") + private BigDecimal amount; + + @Valid + @JsonProperty("cryptocurrency") + private Cryptocurrency cryptocurrency; + + @Schema(example = "false", description = "If true minAmount must be filled") + @JsonProperty("minInvoice") + private Boolean minInvoice; + + @Schema(example = "true", description = "If true minAmount and maxAmount must be filled") + @JsonProperty("invoiceLimits") + private Boolean invoiceLimits; + + @Valid + @Schema(example = "0.5", description = "") + @JsonProperty("minAmount") + private BigDecimal minAmount; + + @Valid + @Schema(example = "10") + @JsonProperty("maxAmount") + private BigDecimal maxAmount; + + @Valid + @JsonProperty("products") + private List products; + + @Valid + @Schema(example = "0x4279953514f0009c5cb371df4d530f6fee0ede17") + @JsonProperty("creditAddress") + private String creditAddress; + + @Valid + @Schema(example = "0x4279953514f0009c5cb371df4d530f6fee0ede17") + @JsonProperty("userAddress") + private String userAddress; + + @JsonProperty("user") //TODO: review what information should be returned in here, cause is used on the invoicelink + private UserRequest user; + + @JsonProperty("invoiceStatus") + @Schema(hidden = true) + private InvoiceStatus invoiceStatus; + + @JsonProperty("invoiceLink") + @Schema(example = "https://buy.crosspay.com/test_28oaGzarrdlx6Pe288", description = "Required if invoice") + private String invoiceLink; + +} diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java b/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java new file mode 100644 index 0000000..c529174 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceRequestDTO.java @@ -0,0 +1,46 @@ +package com.cross.chain.payment.model; + +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * InvoiceRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class InvoiceRequestDTO { + private String id; + private String title; + private String memo; + private String dueDate; + private Customer customer; + private InvoiceType invoiceType; + private String hash; + private UUID uuid; + private LocalDateTime createdAt; + private BigDecimal amount; + private CryptocurrencyDTO cryptocurrency; + private Boolean minInvoice; + private Boolean invoiceLimits; + private BigDecimal minAmount; + private BigDecimal maxAmount; + private List products; + private TransactionDTO transaction; + private boolean adjustableQuantity; + private String creditAddress; + private String userAddress; + private UserDTO user; + private InvoiceStatus invoiceStatus; + private String invoiceLink; +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java b/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java new file mode 100644 index 0000000..07d110a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/InvoiceResponse.java @@ -0,0 +1,12 @@ +package com.cross.chain.payment.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class InvoiceResponse { + + private String paymentLink; + +} diff --git a/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java b/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java new file mode 100644 index 0000000..7ceddf2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/ProductInvoiceRequest.java @@ -0,0 +1,28 @@ +package com.cross.chain.payment.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; + +/** + * ProductPaymentRequest + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Validated +public class ProductInvoiceRequest { + + @Valid + @JsonProperty("item") + private ProductResponse product; + + private int quantity; + +} diff --git a/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java b/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java new file mode 100644 index 0000000..6ca742c --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/enums/InvoiceStatus.java @@ -0,0 +1,21 @@ +package com.cross.chain.payment.model.enums; + +import java.util.Arrays; + +public enum InvoiceStatus { + + AWAITING_PAYMENT("AWAITING_PAYMENT"), + PAID("PAID"), + DEACTIVATED("DEACTIVATED"); + + private String value; + + InvoiceStatus(String value) { + this.value = value; + } + + public boolean isFinalStatus(){ + return Arrays.asList(PAID, DEACTIVATED).contains(valueOf(value)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java b/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java new file mode 100644 index 0000000..a926cb8 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/model/enums/InvoiceType.java @@ -0,0 +1,16 @@ +package com.cross.chain.payment.model.enums; + +/** + * Payment types + */ +public enum InvoiceType { + + INVOICE("INVOICE"); + + private String value; + + InvoiceType(String value) { + this.value = value; + } + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java new file mode 100644 index 0000000..9f430dc --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbMapper.java @@ -0,0 +1,28 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.model.InvoiceRequestDTO; +import org.bson.BsonBinarySubType; +import org.bson.types.Binary; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface InvoiceRequestDbMapper { + + InvoiceRequestEntity map(InvoiceRequestDTO invoiceRequest); + + InvoiceRequestDTO map(InvoiceRequestEntity invoiceRequestEntity); + + default Binary map(byte[] value){ + if(value == null){ + return null; + } + return new Binary(BsonBinarySubType.BINARY, value); + } + default byte[] map(Binary value){ + if(value == null){ + return null; + } + return value.getData(); + } + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java new file mode 100644 index 0000000..d3689e2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbService.java @@ -0,0 +1,14 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceRequestDTO; + +import java.util.List; + +public interface InvoiceRequestDbService { + InvoiceRequestDTO findByHash(String invoiceHash) throws InvoiceRequestNotFound; + + InvoiceRequestDTO save(InvoiceRequestDTO invoiceRequestFound); + + List findByUserAddress(String address); +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java new file mode 100644 index 0000000..9d289ff --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestDbServiceImpl.java @@ -0,0 +1,32 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceRequestDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class InvoiceRequestDbServiceImpl implements InvoiceRequestDbService { + + private final InvoiceRequestRepository repository; + private final InvoiceRequestDbMapper mapper; + @Override + public InvoiceRequestDTO findByHash(String invoiceHash) throws InvoiceRequestNotFound { + return mapper.map(repository.findByHash(invoiceHash).orElseThrow(InvoiceRequestNotFound::new)); + } + + @Override + public InvoiceRequestDTO save(InvoiceRequestDTO invoiceRequest) { + InvoiceRequestEntity invoiceRequestEntity = mapper.map(invoiceRequest); + return mapper.map(repository.save(invoiceRequestEntity)); + } + + @Override + public List findByUserAddress(String address) { + return repository.findByUserAddress(address).stream().map(mapper::map).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java new file mode 100644 index 0000000..8d8fd50 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestEntity.java @@ -0,0 +1,49 @@ +package com.cross.chain.payment.persistence; + +import com.cross.chain.payment.model.Customer; +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Data +@Document(collection = "invoice-request") +class InvoiceRequestEntity { + + @Id + private String id; + private String memo; + private String dueDate; + private Customer customer; + private String creditAddress; + private String userAddress; + private String hash; + private UUID uuid; + private String invoiceLink; + @Indexed + @CreatedDate + private LocalDateTime createdAt; + private BigDecimal amount; + @DBRef + private UserEntity user; + @DBRef + private CryptocurrencyEntity cryptocurrency; + private InvoiceType invoiceType; + private InvoiceStatus invoiceStatus; + private List products = new ArrayList<>(); + @DBRef + private TransactionEntity transaction; + + private String title; + +} diff --git a/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java new file mode 100644 index 0000000..c19fc8d --- /dev/null +++ b/src/main/java/com/cross/chain/payment/persistence/InvoiceRequestRepository.java @@ -0,0 +1,15 @@ +package com.cross.chain.payment.persistence; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +interface InvoiceRequestRepository extends MongoRepository { + + Optional findByHash(String hash); + List findByUserAddress(String address); + +} \ No newline at end of file diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java new file mode 100644 index 0000000..68a83f9 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceLinkServiceImpl.java @@ -0,0 +1,121 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceLinkCreationException; +import com.cross.chain.payment.exception.ProductNotFoundException; +import com.cross.chain.payment.exception.UserNotFoundException; +import com.cross.chain.payment.mapper.InvoiceRequestMapper; +import com.cross.chain.payment.mapper.ProductMapper; +import com.cross.chain.payment.mapper.TransactionMapper; +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceStatus; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.persistence.InvoiceRequestDbService; +import com.cross.chain.payment.persistence.ProductDbService; +import com.cross.chain.payment.persistence.TransactionDbService; +import com.cross.chain.payment.persistence.UserDbService; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class InvoiceLinkServiceImpl implements InvoiceService { + + @Value("${invoice.hash-length}") + private int hashLength; + + @Value("${invoice.url}") + private String url; + + private final InvoiceRequestMapper invoiceRequestMapper; + + private final ProductMapper productMapper; + + private final TransactionMapper transactionMapper; + + private final InvoiceRequestDbService invoiceRequestDbService; + + private final UserDbService userDbService; + + private final TransactionDbService transactionDbService; + private final ProductDbService productDbService; + + @Override + public InvoiceResponse create(InvoiceRequest invoiceRequest) { + validateInvoice(invoiceRequest); //TODO: move this to a bean validator? + InvoiceRequestDTO invoiceRequestDTO = invoiceRequestMapper.map(invoiceRequest); + try { + invoiceRequestDTO = createLink(invoiceRequestDTO); + } catch (UserNotFoundException e) { + throw new InvoiceLinkCreationException(e); + } + return InvoiceResponse.builder() + .paymentLink(invoiceRequestDTO.getInvoiceLink()) + .build(); + } + + @Override + public InvoiceRequestDTO confirm(InvoiceRequestDTO invoiceRequest, InvoiceConfirmationDTO invoiceConfirmationDTO) { + TransactionDTO transaction = transactionMapper.map(invoiceConfirmationDTO); + transaction.setCryptocurrency(invoiceRequest.getCryptocurrency()); + updateProductDetails(transaction.getProducts()); + invoiceRequest.setTransaction(transactionDbService.save(transaction)); + invoiceRequest.setInvoiceStatus(InvoiceStatus.PAID);; + return invoiceRequestDbService.save(invoiceRequest); + } + + @Override + public InvoiceRequestDTO cancel(InvoiceRequestDTO invoiceRequest) { + //TODO: create a transaction history using the invoiceConfirmation + if(invoiceRequest.getInvoiceStatus().isFinalStatus()){ + throw new RuntimeException(); //TODO: change exception + } + //TODO: increase the total supply since the invoice was cancelled. + invoiceRequest.setInvoiceStatus(InvoiceStatus.DEACTIVATED); + return invoiceRequestDbService.save(invoiceRequest); + } + + @Override + public void validateInvoice(InvoiceRequest invoiceRequest) { + //TODO: validate if contains all the required information to create the link + Assert.notNull(invoiceRequest.getAmount(), String.format("Amount is required for an Invoice")); + } + + @Override + public InvoiceType typeInvoice() { + return InvoiceType.INVOICE; + } + + + private InvoiceRequestDTO createLink(InvoiceRequestDTO invoiceRequestDTO) throws UserNotFoundException { + UserDTO user = userDbService.findBySignerAddress(invoiceRequestDTO.getUserAddress()); + invoiceRequestDTO.setUser(user); + invoiceRequestDTO.setHash(RandomStringUtils.randomAlphabetic(hashLength)); + invoiceRequestDTO.setInvoiceLink(url.concat("/").concat(invoiceRequestDTO.getHash())); + invoiceRequestDTO.setInvoiceStatus(InvoiceStatus.AWAITING_PAYMENT); + invoiceRequestDTO.setUuid(UUID.randomUUID()); + return invoiceRequestDbService.save(invoiceRequestDTO); + } + + private void updateProductDetails(List products) { + products.forEach(item-> { + ProductDTO product; + try { + product = productDbService.findById(item.getProduct().getId()); + } catch (ProductNotFoundException e) { + throw new RuntimeException(e); //TODO: throw an payment Execution error - Product not found + } + if(item.getQuantity() > product.getTotalSupply()){ + throw new RuntimeException(); //TODO: throw an payment Execution error - Amount selected not valid + } + product.setTotalSupply(product.getTotalSupply() - item.getQuantity()); + productDbService.save(product); + }); + } + +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java new file mode 100644 index 0000000..be60a94 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorService.java @@ -0,0 +1,26 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.model.InvoiceConfirmationRequest; +import com.cross.chain.payment.model.InvoiceRequest; +import com.cross.chain.payment.model.InvoiceResponse; +import com.cross.chain.payment.model.Transaction; + +import java.util.List; + +public interface InvoiceProcessorService { + + InvoiceResponse processInvoiceRequest(InvoiceRequest invoiceRequest); + + InvoiceRequest retrieveInvoiceRequest(String invoiceHash) throws InvoiceRequestNotFound; + + List retrieveInvoiceTransactions(String invoiceHash) throws InvoiceRequestNotFound; + + InvoiceRequest updateInvoiceRequest(String invoiceHash, InvoiceRequest invoiceRequest) throws InvoiceRequestNotFound; + + List retrieveByUserAddress(String address); + + void invoiceConfirmation(String invoiceHash, InvoiceConfirmationRequest invoiceConfirmation) throws InvoiceRequestNotFound; + + void invoiceCancellation(String invoiceHash) throws InvoiceRequestNotFound; +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java new file mode 100644 index 0000000..b5f5f0a --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceProcessorServiceImpl.java @@ -0,0 +1,84 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.exception.InvoiceProcessorException; +import com.cross.chain.payment.exception.InvoiceRequestNotFound; +import com.cross.chain.payment.mapper.InvoiceRequestMapper; +import com.cross.chain.payment.mapper.TransactionMapper; +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceType; +import com.cross.chain.payment.persistence.InvoiceRequestDbService; +import com.cross.chain.payment.service.product.ProductService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class InvoiceProcessorServiceImpl implements InvoiceProcessorService { + + private final List invoiceServices; + private final ProductService productService; + private final InvoiceRequestMapper invoiceRequestMapper; + + private final TransactionMapper transactionMapper; + private final InvoiceRequestDbService invoiceRequestDbService; + + @Override + public InvoiceResponse processInvoiceRequest(InvoiceRequest invoiceRequest) { + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + return invoiceService.create(invoiceRequest); + } + + @Override + public InvoiceRequest retrieveInvoiceRequest(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + return invoiceRequestMapper.map(invoiceRequest); + } + + @Override + public List retrieveInvoiceTransactions(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + return Collections.singletonList(transactionMapper.map(invoiceRequest.getTransaction())); + } + + @Override + public InvoiceRequest updateInvoiceRequest(String invoiceHash, InvoiceRequest invoiceRequest) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequestFound = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceRequestDTO invoiceUpdated = invoiceRequestMapper.map(invoiceRequest); + invoiceRequestFound.setCreditAddress(invoiceUpdated.getCreditAddress()); + invoiceRequestFound.setMemo(invoiceUpdated.getMemo()); + invoiceRequestFound.setAmount(invoiceUpdated.getAmount()); + invoiceRequestFound.setProducts(invoiceUpdated.getProducts()); + invoiceRequestDbService.save(invoiceRequestFound); + return invoiceRequestMapper.map(invoiceRequestFound); + } + + @Override + public List retrieveByUserAddress(String address) { + return invoiceRequestDbService.findByUserAddress(address).stream().map(invoiceRequestMapper::map).collect(Collectors.toList()); + } + + @Override + public void invoiceConfirmation(String invoiceHash, InvoiceConfirmationRequest invoiceConfirmationRequest) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + invoiceService.confirm(invoiceRequest, invoiceRequestMapper.map(invoiceConfirmationRequest)); + } + + @Override + public void invoiceCancellation(String invoiceHash) throws InvoiceRequestNotFound { + InvoiceRequestDTO invoiceRequest = invoiceRequestDbService.findByHash(invoiceHash); + InvoiceService invoiceService = getInvoiceService(InvoiceType.INVOICE); + invoiceService.cancel(invoiceRequest); + } + + private InvoiceService getInvoiceService(InvoiceType invoiceType) { + return invoiceServices.stream() + .filter(p -> p.applies(invoiceType)) + .findFirst() + .orElseThrow(InvoiceProcessorException::new); + } +} diff --git a/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java new file mode 100644 index 0000000..a06eae2 --- /dev/null +++ b/src/main/java/com/cross/chain/payment/service/invoice/InvoiceService.java @@ -0,0 +1,20 @@ +package com.cross.chain.payment.service.invoice; + +import com.cross.chain.payment.model.*; +import com.cross.chain.payment.model.enums.InvoiceType; + +public interface InvoiceService { + + InvoiceResponse create(InvoiceRequest invoiceRequest); + + InvoiceRequestDTO confirm(InvoiceRequestDTO invoiceRequest, InvoiceConfirmationDTO invoiceConfirmationDTO); + + InvoiceRequestDTO cancel(InvoiceRequestDTO invoiceRequest); + + void validateInvoice(InvoiceRequest invoiceRequest); + default boolean applies(InvoiceType type){ + return typeInvoice().equals(type); + } + InvoiceType typeInvoice(); + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c04f2ba..c583c51 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,9 @@ spring: payment: hash-length: 50 url: localhost:3000/crypto-payment +invoice: + hash-length: 50 + url: localhost:3000/crypto-invoice springfox: documentation: