Finish payment workflow
This commit is contained in:
@@ -49,7 +49,8 @@ public class AccountController {
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}")
|
||||
public ResponseEntity getAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(getAccountResource(id))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(this::getAccountResource)
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
@@ -91,57 +92,48 @@ public class AccountController {
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/commands/confirm")
|
||||
public ResponseEntity confirm(@PathVariable Long id) {
|
||||
return Optional.ofNullable(getAccountResource(accountService.get(id)
|
||||
.confirm()))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(Account::confirm)
|
||||
.map(this::getAccountResource)
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/commands/activate")
|
||||
public ResponseEntity activate(@PathVariable Long id) {
|
||||
return Optional.ofNullable(getAccountResource(accountService.get(id)
|
||||
.activate()))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(Account::activate)
|
||||
.map(this::getAccountResource)
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/commands/suspend")
|
||||
public ResponseEntity suspend(@PathVariable Long id) {
|
||||
return Optional.ofNullable(getAccountResource(accountService.get(id)
|
||||
.suspend()))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(Account::suspend)
|
||||
.map(this::getAccountResource)
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/commands/archive")
|
||||
public ResponseEntity archive(@PathVariable Long id) {
|
||||
return Optional.ofNullable(getAccountResource(accountService.get(id)
|
||||
.archive()))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(Account::archive)
|
||||
.map(this::getAccountResource)
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/commands/postOrder", method = RequestMethod.POST)
|
||||
public ResponseEntity postOrder(@PathVariable Long id, @RequestBody Order order) {
|
||||
return Optional.ofNullable(accountService.get(id)
|
||||
.postOrder(order))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(a -> a.postOrder(order))
|
||||
.map(o -> new ResponseEntity<>(o, HttpStatus.CREATED))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a hypermedia resource for {@link Account} with the specified identifier.
|
||||
*
|
||||
* @param id is the unique identifier for looking up the {@link Account} entity
|
||||
* @return a hypermedia resource for the fetched {@link Account}
|
||||
*/
|
||||
private Resource<Account> getAccountResource(Long id) {
|
||||
// Get the account for the provided id
|
||||
Account account = accountService.get(id);
|
||||
|
||||
return getAccountResource(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Account} entity and persists the result to the repository.
|
||||
*
|
||||
@@ -247,17 +239,17 @@ public class AccountController {
|
||||
private Resource<Account> getAccountResource(Account account) {
|
||||
Assert.notNull(account, "Account must not be null");
|
||||
|
||||
if(account.getLink("commands") == null) {
|
||||
if (!account.hasLink("commands")) {
|
||||
// Add command link
|
||||
account.add(linkBuilder("getCommands", account.getIdentity()).withRel("commands"));
|
||||
}
|
||||
|
||||
if(account.getLink("events") == null) {
|
||||
if (!account.hasLink("events")) {
|
||||
// Add get events link
|
||||
account.add(linkBuilder("getAccountEvents", account.getIdentity()).withRel("events"));
|
||||
}
|
||||
|
||||
if(account.getLink("orders") == null) {
|
||||
if (!account.hasLink("orders")) {
|
||||
// Add orders link
|
||||
account.add(linkBuilder("getAccountOrders", account.getIdentity()).withRel("orders"));
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>${json-path.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package demo.order.action;
|
||||
|
||||
import demo.domain.Action;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.order.domain.OrderService;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects an {@link Order} to an Account.
|
||||
@@ -18,19 +20,33 @@ import java.util.function.BiConsumer;
|
||||
*/
|
||||
@Service
|
||||
public class ConnectAccount extends Action<Order> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiConsumer<Order, Long> getConsumer() {
|
||||
public BiFunction<Order, Long, Order> getFunction() {
|
||||
return (order, accountId) -> {
|
||||
OrderService orderService = order.getModule(OrderModule.class)
|
||||
.getDefaultService();
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.ORDER_CREATED, "Order must be in a created state");
|
||||
|
||||
Order result;
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Connect the account
|
||||
order.setAccountId(accountId);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
// Trigger the account connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ACCOUNT_CONNECTED, order));
|
||||
result = order.sendEvent(new OrderEvent(OrderEventType.ACCOUNT_CONNECTED, order)).getEntity();
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect order to account", ex);
|
||||
order.setAccountId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
orderService.update(order);
|
||||
throw new IllegalStateException("Could not connect order to account", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package demo.order.action;
|
||||
|
||||
import demo.domain.Action;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.order.domain.OrderService;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.payment.domain.Payment;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects a {@link Payment} to an {@link Order}.
|
||||
@@ -19,19 +21,33 @@ import java.util.function.BiConsumer;
|
||||
*/
|
||||
@Service
|
||||
public class ConnectPayment extends Action<Order> {
|
||||
public BiConsumer<Order, Long> getConsumer() {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Order, Long, Order> getFunction() {
|
||||
return (order, paymentId) -> {
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.PAYMENT_CREATED, "Order must be in a payment created state");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class)
|
||||
.getDefaultService();
|
||||
Order result;
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Connect the account
|
||||
// Connect the payment
|
||||
order.setPaymentId(paymentId);
|
||||
order.setStatus(OrderStatus.PAYMENT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
// Trigger the account connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.PAYMENT_CONNECTED, order));
|
||||
try {
|
||||
// Trigger the payment connected event
|
||||
result = order.sendEvent(new OrderEvent(OrderEventType.PAYMENT_CONNECTED, order)).getEntity();
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect payment to order", ex);
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
orderService.update(order);
|
||||
throw new IllegalStateException("Could not connect payment to order", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
package demo.order.action;
|
||||
|
||||
import demo.domain.Action;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.order.domain.OrderService;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.payment.domain.Payment;
|
||||
import demo.payment.domain.PaymentMethod;
|
||||
import demo.payment.domain.PaymentService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Creates a {@link Payment} for an {@link Order}.
|
||||
@@ -29,54 +24,54 @@ import java.util.function.Consumer;
|
||||
@Service
|
||||
public class CreatePayment extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(CreatePayment.class);
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final PaymentService paymentService;
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public CreatePayment(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
public CreatePayment(PaymentService paymentService) {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Consumer<Order> getConsumer() {
|
||||
public Function<Order, Order> getFunction() {
|
||||
return order -> {
|
||||
Assert.isTrue(order.getPaymentId() == null, "Payment has already been created");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "Account must be connected first");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class)
|
||||
.getDefaultService();
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
Order result;
|
||||
|
||||
Payment payment = new Payment();
|
||||
|
||||
// Calculate payment amount
|
||||
payment.setAmount(order.calculateTotal());
|
||||
|
||||
// Set payment method
|
||||
payment.setPaymentMethod(PaymentMethod.CREDIT_CARD);
|
||||
payment = paymentService.create(payment);
|
||||
|
||||
// Create a new request entity
|
||||
RequestEntity<Resource<Payment>> requestEntity = RequestEntity.post(
|
||||
URI.create("http://payment-web/v1/payments"))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaTypes.HAL_JSON)
|
||||
.body(new Resource<>(payment), Resource.class);
|
||||
log.info(payment);
|
||||
|
||||
// Update the order entity's status
|
||||
Resource paymentResource = restTemplate
|
||||
.exchange(requestEntity, Resource.class)
|
||||
.getBody();
|
||||
|
||||
log.info(paymentResource);
|
||||
|
||||
// Update the status
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_CREATED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_CREATED, order);
|
||||
event.add(paymentResource.getLink("self")
|
||||
.withRel("payment"));
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger the payment created
|
||||
order.sendAsyncEvent(event);
|
||||
// Trigger payment created event
|
||||
result = order.sendEvent(event).getEntity();
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be created", ex);
|
||||
|
||||
// Rollback the payment creation
|
||||
if (payment.getIdentity() != null)
|
||||
paymentService.delete(payment.getIdentity());
|
||||
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
orderService.update(order);
|
||||
|
||||
throw new IllegalStateException("Payment creation failed", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import demo.domain.Action;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.payment.domain.Payment;
|
||||
import demo.payment.domain.PaymentService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -17,19 +18,18 @@ import java.util.function.Consumer;
|
||||
@Service
|
||||
public class DeleteOrder extends Action<Order> {
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final PaymentService paymentService;
|
||||
|
||||
public DeleteOrder(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
public DeleteOrder(PaymentService paymentService) {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Consumer<Order> getConsumer() {
|
||||
return (order) -> {
|
||||
// Delete payment
|
||||
if (order.getPaymentId() != null) {
|
||||
String href = "http://payment-web/v1/payments/" + order.getPaymentId();
|
||||
restTemplate.delete(href);
|
||||
}
|
||||
if (order.getPaymentId() != null)
|
||||
paymentService.delete(order.getPaymentId());
|
||||
|
||||
// Delete order
|
||||
order.getModule(OrderModule.class)
|
||||
|
||||
@@ -2,10 +2,21 @@ package demo.order.action;
|
||||
|
||||
import demo.domain.Action;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.order.domain.OrderService;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.payment.domain.Payment;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Processes a {@link Payment} for an {@link Order}.
|
||||
@@ -14,7 +25,51 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
@Service
|
||||
public class ProcessPayment extends Action<Order> {
|
||||
public Consumer<Order> getConsumer() {
|
||||
return (order) -> {};
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return order -> {
|
||||
Assert.isTrue(!Arrays
|
||||
.asList(OrderStatus.PAYMENT_SUCCEEDED, OrderStatus.PAYMENT_PENDING, OrderStatus.PAYMENT_FAILED)
|
||||
.contains(order.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.PAYMENT_CONNECTED, "Payment must be connected to an order");
|
||||
|
||||
Order result = order;
|
||||
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Get the payment
|
||||
Payment payment = order.getPayment();
|
||||
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_PENDING);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
// Create traverson for the new order
|
||||
Traverson traverson = new Traverson(URI.create(payment.getLink("self").getHref()), MediaTypes.HAL_JSON);
|
||||
payment = traverson.follow("commands", "processPayment").toObject(Payment.class);
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be processed", ex);
|
||||
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_FAILED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger payment failed event
|
||||
result = order.sendEvent(event).getEntity();
|
||||
} finally {
|
||||
if (result.getStatus() != OrderStatus.PAYMENT_FAILED) {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_SUCCEEDED, result);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger payment created event
|
||||
result = order.sendEvent(event).getEntity();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,21 +214,25 @@ public class OrderController {
|
||||
private Resource<Order> getOrderResource(Order order) {
|
||||
if (order == null) return null;
|
||||
|
||||
if (order.getLink("commands") == null) {
|
||||
// Add command link
|
||||
order.add(linkBuilder("getCommands", order.getIdentity()).withRel("commands"));
|
||||
}
|
||||
|
||||
if (order.getLink("events") == null) {
|
||||
// Add get events link
|
||||
order.add(linkBuilder("getOrderEvents", order.getIdentity()).withRel("events"));
|
||||
}
|
||||
|
||||
// Add remote account link
|
||||
if (order.getAccountId() != null) {
|
||||
if (order.getAccountId() != null && order.getLink("account") == null) {
|
||||
Link result = getRemoteLink("account-web", "/v1/accounts/{id}", order.getAccountId(), "account");
|
||||
if (result != null)
|
||||
order.add(result);
|
||||
}
|
||||
|
||||
// Add remote payment link
|
||||
if (order.getPaymentId() != null) {
|
||||
if (order.getPaymentId() != null && order.getLink("payment") == null) {
|
||||
Link result = getRemoteLink("payment-web", "/v1/payments/{id}", order.getPaymentId(), "payment");
|
||||
if (result != null)
|
||||
order.add(result);
|
||||
|
||||
@@ -3,10 +3,13 @@ package demo.order.domain;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.AbstractEntity;
|
||||
import demo.domain.Aggregate;
|
||||
import demo.domain.Command;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.domain.Module;
|
||||
import demo.order.action.*;
|
||||
import demo.order.controller.OrderController;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.payment.domain.Payment;
|
||||
import org.springframework.hateoas.Link;
|
||||
|
||||
import javax.persistence.*;
|
||||
@@ -97,34 +100,30 @@ public class Order extends AbstractEntity<OrderEvent, Long> {
|
||||
|
||||
@Command(method = "connectAccount", controller = OrderController.class)
|
||||
public Order connectAccount(Long accountId) {
|
||||
getAction(ConnectAccount.class)
|
||||
.getConsumer()
|
||||
.accept(this, accountId);
|
||||
return this;
|
||||
return getAction(ConnectAccount.class)
|
||||
.getFunction()
|
||||
.apply(this, accountId);
|
||||
}
|
||||
|
||||
@Command(method = "connectPayment", controller = OrderController.class)
|
||||
public Order connectPayment(Long paymentId) {
|
||||
getAction(ConnectPayment.class)
|
||||
.getConsumer()
|
||||
.accept(this, paymentId);
|
||||
return this;
|
||||
return getAction(ConnectPayment.class)
|
||||
.getFunction()
|
||||
.apply(this, paymentId);
|
||||
}
|
||||
|
||||
@Command(method = "createPayment", controller = OrderController.class)
|
||||
public Order createPayment() {
|
||||
getAction(CreatePayment.class)
|
||||
.getConsumer()
|
||||
.accept(this);
|
||||
return this;
|
||||
return getAction(CreatePayment.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "processPayment", controller = OrderController.class)
|
||||
public Order processPayment() {
|
||||
getAction(ProcessPayment.class)
|
||||
.getConsumer()
|
||||
.accept(this);
|
||||
return this;
|
||||
return getAction(ProcessPayment.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "reserveInventory", controller = OrderController.class)
|
||||
@@ -151,6 +150,24 @@ public class Order extends AbstractEntity<OrderEvent, Long> {
|
||||
.sum();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Payment getPayment() {
|
||||
Payment result = null;
|
||||
|
||||
if (paymentId != null)
|
||||
result = getModule(OrderModule.class).getPaymentService().get(paymentId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Module<A>, A extends Aggregate<OrderEvent, Long>> T getModule() throws
|
||||
IllegalArgumentException {
|
||||
OrderModule orderModule = getModule(OrderModule.class);
|
||||
return (T) orderModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||
*/
|
||||
|
||||
@@ -3,15 +3,19 @@ package demo.order.domain;
|
||||
import demo.domain.Module;
|
||||
import demo.event.EventService;
|
||||
import demo.order.event.OrderEvent;
|
||||
import demo.payment.domain.PaymentService;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
public class OrderModule extends Module<Order> {
|
||||
|
||||
private final OrderService orderService;
|
||||
private final PaymentService paymentService;
|
||||
private final EventService<OrderEvent, Long> eventService;
|
||||
|
||||
public OrderModule(OrderService orderService, EventService<OrderEvent, Long> eventService) {
|
||||
public OrderModule(OrderService orderService, PaymentService paymentService, EventService<OrderEvent, Long>
|
||||
eventService) {
|
||||
this.orderService = orderService;
|
||||
this.paymentService = paymentService;
|
||||
this.eventService = eventService;
|
||||
}
|
||||
|
||||
@@ -32,4 +36,8 @@ public class OrderModule extends Module<Order> {
|
||||
public EventService<OrderEvent, Long> getDefaultEventService() {
|
||||
return eventService;
|
||||
}
|
||||
|
||||
public PaymentService getPaymentService() {
|
||||
return paymentService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
public class Payment {
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.Aggregate;
|
||||
import demo.domain.Module;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Payment extends Aggregate<PaymentEvent, Long> {
|
||||
|
||||
private Long id;
|
||||
private List<PaymentEvent> events = new ArrayList<>();
|
||||
private Double amount;
|
||||
private PaymentMethod paymentMethod;
|
||||
private PaymentStatus status;
|
||||
@@ -38,10 +50,47 @@ public class Payment {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
@JsonProperty("paymentId")
|
||||
@Override
|
||||
public Long getIdentity() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setIdentity(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaymentEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||
*/
|
||||
@Override
|
||||
public Link getId() {
|
||||
return new Link(new UriTemplate("http://payment-web/v1/payments/{id}")
|
||||
.with("id", TemplateVariable.VariableType
|
||||
.PATH_VARIABLE)
|
||||
.expand(getIdentity())
|
||||
.toString()).withSelfRel();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Module<A>, A extends Aggregate<PaymentEvent, Long>> T getModule() throws
|
||||
IllegalArgumentException {
|
||||
PaymentModule paymentModule = getModule(PaymentModule.class);
|
||||
return (T) paymentModule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Payment{" +
|
||||
"amount=" + amount +
|
||||
"id=" + id +
|
||||
", events=" + events +
|
||||
", amount=" + amount +
|
||||
", paymentMethod=" + paymentMethod +
|
||||
", status=" + status +
|
||||
"} " + super.toString();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
import demo.event.Event;
|
||||
|
||||
/**
|
||||
* The domain event {@link PaymentEvent} tracks the type and state of events as applied to the {@link Payment} domain
|
||||
* object. This event resource can be used to event source the aggregate state of {@link Payment}.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public class PaymentEvent extends Event<Payment, PaymentEventType, Long> {
|
||||
|
||||
private Long eventId;
|
||||
private PaymentEventType type;
|
||||
private Payment payment;
|
||||
private Long createdAt;
|
||||
private Long lastModified;
|
||||
|
||||
public PaymentEvent() {
|
||||
}
|
||||
|
||||
public PaymentEvent(PaymentEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public PaymentEvent(PaymentEventType type, Payment payment) {
|
||||
this.type = type;
|
||||
this.payment = payment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEventId(Long id) {
|
||||
eventId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentEventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(PaymentEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payment getEntity() {
|
||||
return payment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntity(Payment entity) {
|
||||
this.payment = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedAt(Long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModified(Long lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
/**
|
||||
* The {@link PaymentEventType} represents a collection of possible events that describe state transitions of
|
||||
* {@link PaymentStatus} on the {@link Payment} aggregate.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum PaymentEventType {
|
||||
PAYMENT_CREATED,
|
||||
ORDER_CONNECTED,
|
||||
PAYMENT_PENDING,
|
||||
PAYMENT_PROCESSED,
|
||||
PAYMENT_FAILED,
|
||||
PAYMENT_SUCCEEDED
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
import demo.domain.Module;
|
||||
import demo.event.EventService;
|
||||
import demo.order.event.OrderEvent;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
public class PaymentModule extends Module<Payment> {
|
||||
|
||||
private final PaymentService paymentService;
|
||||
private final EventService<OrderEvent, Long> eventService;
|
||||
|
||||
public PaymentModule(PaymentService paymentService, EventService<OrderEvent, Long> eventService) {
|
||||
this.paymentService = paymentService;
|
||||
this.eventService = eventService;
|
||||
}
|
||||
|
||||
public PaymentService getPaymentService() {
|
||||
return paymentService;
|
||||
}
|
||||
|
||||
public EventService<OrderEvent, Long> getEventService() {
|
||||
return eventService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentService getDefaultService() {
|
||||
return paymentService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventService<OrderEvent, Long> getDefaultEventService() {
|
||||
return eventService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
import demo.domain.Service;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
public class PaymentService extends Service<Payment, Long> {
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public PaymentService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payment get(Long paymentId) {
|
||||
return restTemplate.getForObject(new UriTemplate("http://payment-web/v1/payments/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(paymentId), Payment.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payment create(Payment payment) {
|
||||
return restTemplate.postForObject(new UriTemplate("http://payment-web/v1/payments").expand(),
|
||||
payment, Payment.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payment update(Payment payment) {
|
||||
return restTemplate.exchange(new RequestEntity<>(payment, HttpMethod.PUT, new UriTemplate
|
||||
("http://payment-web/v1/payments/{id}").with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(payment.getIdentity())), Payment.class)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(Long paymentId) {
|
||||
restTemplate.delete(new UriTemplate("http://payment-web/v1/payments/{id}").with("id", TemplateVariable
|
||||
.VariableType.PATH_VARIABLE)
|
||||
.expand(paymentId));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package demo.payment.domain;
|
||||
|
||||
public enum PaymentStatus {
|
||||
PAYMENT_CREATED,
|
||||
ORDER_CONNECTED,
|
||||
PAYMENT_PENDING,
|
||||
PAYMENT_PROCESSED,
|
||||
PAYMENT_FAILED,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package demo.payment.domain;
|
||||
|
||||
import demo.order.domain.Order;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
public class Payments extends Resources<Order> {
|
||||
|
||||
/**
|
||||
* Creates an empty {@link Resources} instance.
|
||||
*/
|
||||
public Payments() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Resources} instance with the given content and {@link Link}s (optional).
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param links the links to be added to the {@link Resources}.
|
||||
*/
|
||||
public Payments(Iterable<Order> content, Link... links) {
|
||||
super(content, links);
|
||||
}
|
||||
}
|
||||
@@ -135,17 +135,11 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(OrderStatus.PAYMENT_CREATED)
|
||||
.target(OrderStatus.PAYMENT_CONNECTED)
|
||||
.target(OrderStatus.PAYMENT_PENDING)
|
||||
.event(OrderEventType.PAYMENT_CONNECTED)
|
||||
.action(paymentConnected())
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(OrderStatus.PAYMENT_CONNECTED)
|
||||
.target(OrderStatus.PAYMENT_PENDING)
|
||||
.event(OrderEventType.PAYMENT_PENDING)
|
||||
.action(paymentPending())
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(OrderStatus.PAYMENT_PENDING)
|
||||
.target(OrderStatus.PAYMENT_SUCCEEDED)
|
||||
.event(OrderEventType.PAYMENT_SUCCEEDED)
|
||||
@@ -166,7 +160,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new OrderCreated(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -183,7 +177,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new PaymentPending(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -200,7 +194,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new ReservationPending(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -217,7 +211,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new PaymentFailed(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -231,19 +225,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
|
||||
@Bean
|
||||
public Action<OrderStatus, OrderEventType> paymentSucceeded() {
|
||||
return context -> applyEvent(context,
|
||||
new PaymentSucceeded(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
return traverson.follow("self")
|
||||
.toEntity(Order.class)
|
||||
.getBody();
|
||||
}));
|
||||
return context -> applyEvent(context, new PaymentSucceeded(context));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -251,15 +233,16 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new PaymentConnected(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
|
||||
// Create a traverson for the root order
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
return traverson.follow("self")
|
||||
.toEntity(Order.class)
|
||||
.getBody();
|
||||
// Traverse to the process payment link
|
||||
return traverson.follow("self", "commands", "processPayment")
|
||||
.toObject(Order.class);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -268,7 +251,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new PaymentCreated(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson paymentResource = new Traverson(
|
||||
URI.create(event.getLink("payment").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -307,7 +290,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new ReservationSucceeded(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -324,7 +307,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new ReservationSucceeded(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
@@ -341,7 +324,7 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
return context -> applyEvent(context,
|
||||
new ReservationFailed(context, event -> {
|
||||
log.info(event.getType() + ": " + event.getLink("order").getHref());
|
||||
// Get the account resource for the event
|
||||
// Get the order resource for the event
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
|
||||
@@ -5,14 +5,23 @@ import demo.order.event.OrderEventType;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class PaymentSucceeded extends OrderFunction {
|
||||
|
||||
final private Logger log = Logger.getLogger(PaymentSucceeded.class);
|
||||
|
||||
public PaymentSucceeded(StateContext<OrderStatus, OrderEventType> context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PaymentSucceeded(StateContext<OrderStatus, OrderEventType> context, Function<OrderEvent, Order> lambda) {
|
||||
super(context, lambda);
|
||||
}
|
||||
@@ -25,7 +34,50 @@ public class PaymentSucceeded extends OrderFunction {
|
||||
*/
|
||||
@Override
|
||||
public Order apply(OrderEvent event) {
|
||||
Order order;
|
||||
|
||||
log.info("Executing workflow for payment succeeded...");
|
||||
return super.apply(event);
|
||||
|
||||
// Create a traverson for the root order
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
// Get the order resource attached to the event
|
||||
order = traverson.follow("self")
|
||||
.toEntity(Order.class)
|
||||
.getBody();
|
||||
|
||||
// Set the order to a pending state
|
||||
order = setOrderPaymentSucceededStatus(event, order);
|
||||
|
||||
context.getExtendedState().getVariables().put("order", order);
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Order} resource to a payment succeeded state.
|
||||
*
|
||||
* @param event is the {@link OrderEvent} for this context
|
||||
* @param order is the {@link Order} attached to the {@link OrderEvent} resource
|
||||
* @return an {@link Order} with its updated state set to pending
|
||||
*/
|
||||
private Order setOrderPaymentSucceededStatus(OrderEvent event, Order order) {
|
||||
// Set the account status to pending
|
||||
order.setStatus(OrderStatus.PAYMENT_SUCCEEDED);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// Create a new request entity
|
||||
RequestEntity<Order> requestEntity = RequestEntity.put(
|
||||
URI.create(event.getLink("order").getHref()))
|
||||
.contentType(MediaTypes.HAL_JSON)
|
||||
.body(order);
|
||||
|
||||
// Update the account entity's status
|
||||
order = restTemplate.exchange(requestEntity, Order.class).getBody();
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ public class Order extends AbstractEntity {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long accountId;
|
||||
|
||||
private Long paymentId;
|
||||
|
||||
private OrderStatus status;
|
||||
|
||||
private Set<OrderEvent> events = new HashSet<>();
|
||||
@@ -68,6 +72,22 @@ public class Order extends AbstractEntity {
|
||||
lineItems.add(lineItem);
|
||||
}
|
||||
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setAccountId(Long accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public Long getPaymentId() {
|
||||
return paymentId;
|
||||
}
|
||||
|
||||
public void setPaymentId(Long paymentId) {
|
||||
this.paymentId = paymentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||
*/
|
||||
|
||||
@@ -8,13 +8,19 @@ import demo.payment.domain.PaymentStatus;
|
||||
import demo.payment.event.PaymentEvent;
|
||||
import demo.payment.event.PaymentEventType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Service
|
||||
public class ConnectOrder extends Action<Payment> {
|
||||
public BiConsumer<Payment, Long> getConsumer() {
|
||||
public BiFunction<Payment, Long, Payment> getFunction() {
|
||||
return (payment, orderId) -> {
|
||||
Assert.isTrue(payment
|
||||
.getStatus() == PaymentStatus.PAYMENT_CREATED, "Payment has already been connected to an order");
|
||||
|
||||
Payment result;
|
||||
|
||||
PaymentService paymentService = payment.getModule(PaymentModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
@@ -23,8 +29,17 @@ public class ConnectOrder extends Action<Payment> {
|
||||
payment.setStatus(PaymentStatus.ORDER_CONNECTED);
|
||||
payment = paymentService.update(payment);
|
||||
|
||||
try {
|
||||
// Trigger the payment connected
|
||||
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.ORDER_CONNECTED, payment));
|
||||
result = payment.sendEvent(new PaymentEvent(PaymentEventType.ORDER_CONNECTED, payment)).getEntity();
|
||||
} catch (IllegalStateException ex) {
|
||||
payment.setStatus(PaymentStatus.PAYMENT_CREATED);
|
||||
payment.setOrderId(null);
|
||||
paymentService.update(payment);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,31 @@ package demo.payment.action;
|
||||
|
||||
import demo.domain.Action;
|
||||
import demo.payment.domain.Payment;
|
||||
import demo.payment.domain.PaymentModule;
|
||||
import demo.payment.domain.PaymentService;
|
||||
import demo.payment.domain.PaymentStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Service
|
||||
public class ProcessPayment extends Action<Payment> {
|
||||
public Consumer<Payment> getConsumer() {
|
||||
return payment -> payment.setStatus(PaymentStatus.PAYMENT_PROCESSED);
|
||||
return payment -> {
|
||||
// Validations
|
||||
Assert.isTrue(!Arrays.asList(PaymentStatus.PAYMENT_SUCCEEDED,
|
||||
PaymentStatus.PAYMENT_PENDING,
|
||||
PaymentStatus.PAYMENT_FAILED).contains(payment.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(payment.getStatus() == PaymentStatus.ORDER_CONNECTED,
|
||||
"Payment must be connected to an order");
|
||||
|
||||
PaymentService paymentService = payment.getModule(PaymentModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
payment.setStatus(PaymentStatus.PAYMENT_PROCESSED);
|
||||
paymentService.update(payment);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ public class PaymentController {
|
||||
}
|
||||
|
||||
// Add remote payment link
|
||||
if (payment.getOrderId() != null) {
|
||||
if (payment.getOrderId() != null && !payment.hasLink("order")) {
|
||||
Link result = getRemoteLink("order-web", "/v1/orders/{id}", payment.getOrderId(), "order");
|
||||
if (result != null)
|
||||
payment.add(result);
|
||||
|
||||
@@ -90,11 +90,9 @@ public class Payment extends AbstractEntity<PaymentEvent, Long> {
|
||||
|
||||
@Command(method = "connectOrder", controller = PaymentController.class)
|
||||
public Payment connectOrder(Long orderId) {
|
||||
getAction(ConnectOrder.class)
|
||||
.getConsumer()
|
||||
.accept(this, orderId);
|
||||
|
||||
return this;
|
||||
return getAction(ConnectOrder.class)
|
||||
.getFunction()
|
||||
.apply(this, orderId);
|
||||
}
|
||||
|
||||
@Command(method = "processPayment", controller = PaymentController.class)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package demo.domain;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An {@link Action} is a reference of a method. A function contains an address to the location of a method. A function
|
||||
* may contain meta-data that describes the inputs and outputs of a method. An action invokes a method annotated with
|
||||
@@ -14,10 +18,19 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
public abstract class Action<A extends Aggregate> implements ApplicationContextAware {
|
||||
private final Logger log = Logger.getLogger(Action.class);
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
protected Consumer<A> onSuccess(Map<String, Object> context) {
|
||||
return a -> {};
|
||||
}
|
||||
|
||||
protected Consumer<A> onError(Map<String, Object> context) {
|
||||
return a -> {};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user