diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/order/OrderService.java b/backend/app/src/main/java/eu/viandeendirect/domains/order/OrderService.java index 57a51ed..7f3ad0e 100644 --- a/backend/app/src/main/java/eu/viandeendirect/domains/order/OrderService.java +++ b/backend/app/src/main/java/eu/viandeendirect/domains/order/OrderService.java @@ -1,6 +1,7 @@ package eu.viandeendirect.domains.order; import eu.viandeendirect.api.OrdersApiDelegate; +import eu.viandeendirect.domains.payment.StripePaymentRepository; import eu.viandeendirect.domains.payment.StripeService; import eu.viandeendirect.domains.user.CustomerRepository; import eu.viandeendirect.model.*; @@ -16,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; @Service @@ -33,17 +35,56 @@ public class OrderService implements OrdersApiDelegate { @Autowired private StripeService stripeService; + @Autowired private CustomerRepository customerRepository; + @Autowired + private StripePaymentRepository stripePaymentRepository; + + @Override + public ResponseEntity getOrder(Integer orderId) { + Order order = orderRepository.findById(orderId).get(); + List items = orderItemRepository.findByOrder(order); + order.setItems(items); + return new ResponseEntity<>(order, HttpStatus.OK); + } + @Override public ResponseEntity createOrder(Order order) { + loadCustomer(order); + order.setStatus(OrderStatus.ITEMS_SELECTED); + Order orderCreated = orderRepository.save(order); + updateQuantitiesSold(order); + return new ResponseEntity<>(orderCreated, CREATED); + } + + @Override + public ResponseEntity createOrderPayment(Order order) { + try { + loadCustomer(order); + order.setStatus(OrderStatus.ITEMS_SELECTED); + orderRepository.save(order); + updateQuantitiesSold(order); + StripePayment payment = stripeService.createPayment(order); + stripePaymentRepository.save(payment); + order.setPayment(payment); + orderRepository.save(order); + return new ResponseEntity<>(order, CREATED); + } catch (Exception e) { + LOGGER.error("An error occurred when creating Stripe payment using Stripe API", e); + throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Une erreur s'est produite à la création du paiement Stripe", e); + } + } + + private void loadCustomer(Order order) { if (order.getCustomer().getId() == null) { Customer customer = customerRepository.findByEmail(order.getCustomer().getUser().getEmail()).orElse(null); order.setCustomer(customer); } - order.setStatus(OrderStatus.ITEMS_SELECTED); - Order orderCreated = orderRepository.save(order); + } + + private void updateQuantitiesSold(Order order) { List lots = new ArrayList<>(); order.getItems().forEach(item -> { PackageLot lot = packageLotRepository.findById(item.getPackageLot().getId()).get(); @@ -57,54 +98,28 @@ public ResponseEntity createOrder(Order order) { %s articles ont été mis en vente. Le nombre total d'articles vendus ne peut pas dépasser la quantité totals du lot. """, - item.getQuantity(), - lot.getId(), - lot.getLabel(), - lot.getQuantitySold(), - lot.getQuantity())); + item.getQuantity(), + lot.getId(), + lot.getLabel(), + lot.getQuantitySold(), + lot.getQuantity())); } lot.setQuantitySold(updatedQuantySold); lots.add(lot); }); packageLotRepository.saveAll(lots); orderItemRepository.saveAll(order.getItems()); - return new ResponseEntity<>(orderCreated, HttpStatus.CREATED); - } - - @Override - public ResponseEntity getOrder(Integer orderId) { - Order order = orderRepository.findById(orderId).get(); - List items = orderItemRepository.findByOrder(order); - order.setItems(items); - return new ResponseEntity<>(order, HttpStatus.OK); - } - - @Override - public ResponseEntity createOrderPayment(Integer orderId) { - Order order = orderRepository.findById(orderId).get(); - try { - StripePayment payment = stripeService.createPayment(order); - return new ResponseEntity<>(payment, HttpStatus.OK); - } catch (Exception e) { - LOGGER.error("An error occurred when creating Stripe account data using Stripe API", e); - throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Une erreur s'est produite à la création du compte Stripe", e); - } - } - - public void processOrderPaymentInitialization(Order order) { - order.setStatus(OrderStatus.PAYMENT_STARTED); - orderRepository.save(order); } - public void processOrderPaymentValidation(Order order) { - stripeService.transferPaymentToProducers(order); - order.setStatus(OrderStatus.PAYED); + public void processOrderPaymentCompletion(Order order) { + order.setStatus(OrderStatus.PAYMENT_COMPLETED); // TODO: trigger an email to the customer orderRepository.save(order); } - public void processOrderPaymentFailure(Order order) { + public void processOrderPaymentExpiration(Order order) { order.setStatus(OrderStatus.PAYMENT_FAILED); + // TODO : update quantity to sold with product not paid orderRepository.save(order); } } diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeDirectPaymentManager.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeDirectPaymentManager.java new file mode 100644 index 0000000..281d367 --- /dev/null +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeDirectPaymentManager.java @@ -0,0 +1,82 @@ +package eu.viandeendirect.domains.payment; + +import com.stripe.exception.StripeException; +import com.stripe.model.checkout.Session; +import com.stripe.net.RequestOptions; +import com.stripe.param.checkout.SessionCreateParams; +import eu.viandeendirect.domains.production.PackageLotRepository; +import eu.viandeendirect.model.Order; +import eu.viandeendirect.model.OrderItem; +import eu.viandeendirect.model.Producer; +import eu.viandeendirect.model.StripePayment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.temporal.TemporalUnit; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Manager Stripe payments following the Stripe pattern "direct charge". + * To use when orders includes productions from the same producer. + * + * @see Stripe doc + */ +@Service +@Qualifier("StripeDirectPaymentManager") +public class StripeDirectPaymentManager implements StripePaymentManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(StripeDirectPaymentManager.class); + + @Value("${PRODUCER_FRONTEND_URL:http://localhost:3000}") + String viandeendirectProducerFrontendUrl; + + @Autowired + PackageLotRepository packageLotRepository; + + @Override + public StripePayment createPayment(Order order) throws StripeException { + SessionCreateParams.Builder builder = SessionCreateParams.builder(); + order.getItems().forEach(item -> builder.addLineItem(getLineItem(item))); + SessionCreateParams params = builder + .setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder() + .setDescription(String.format("Commande viandeendirect.eu n° %s de %s %s", order.getId(), order.getCustomer().getUser().getFirstName(), order.getCustomer().getUser().getLastName())) + .setApplicationFeeAmount(1L).build()) + .setMode(SessionCreateParams.Mode.PAYMENT) + .setCustomerEmail(order.getCustomer().getUser().getEmail()) + .setSuccessUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentSuccessful") + .setCancelUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentCancelled") + .setExpiresAt(Instant.now().plusSeconds(30 * 60).getEpochSecond()) + .build(); + RequestOptions requestOptions = RequestOptions.builder().setStripeAccount(getProducerStripeAccount(order).getStripeAccount().getStripeId()).build(); + Session session = Session.create(params, requestOptions); + StripePayment stripePayment = new StripePayment(); + stripePayment.setCheckoutSessionId(session.getId()); + stripePayment.setPaymentUrl(session.getUrl()); + return stripePayment; + } + + private Producer getProducerStripeAccount(Order order) { + List orderProducers = order.getItems().stream() + .map(OrderItem::getPackageLot) + .map(lot -> packageLotRepository.findById(lot.getId()).get()) + .map(lot -> lot.getProduction().getProducer()) + .distinct() + .toList(); + if (orderProducers.size() > 1) { + throw new IllegalStateException("StripeDirectPaymentManager can only be used with orders from the same producer"); + } + return orderProducers.stream().findFirst().get(); + } + + @Override + public void processPaymentValidation(Order order) { + LOGGER.debug("processing payment validation for order {} : do nothing while payment manager is {}", order.getId(), this.getClass().getSimpleName()); + } +} diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeEventHandler.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeEventHandler.java index 2ea1e3c..723d42c 100644 --- a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeEventHandler.java +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeEventHandler.java @@ -18,14 +18,21 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import static org.springframework.http.HttpStatus.*; +import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED; + /** * @link Stripe documentation */ @RestController public class StripeEventHandler { - @Value("${STRIPE_WEBHOOK_SECRET:default_stripe_webhook_secret_value}") - private String stripeWebhookSecret; + @Value("${STRIPE_WEBHOOK_ACCOUNT_SECRET:default_stripe_webhook_secret_value}") + private String stripeWebhookAccountSecret; + + @Value("${STRIPE_WEBHOOK_CONNECT_SECRET:default_stripe_webhook_secret_value}") + private String stripeWebhookConnectSecret; + private static final Logger LOGGER = LoggerFactory.getLogger(StripeEventHandler.class); @@ -36,50 +43,68 @@ public class StripeEventHandler { private OrderService orderService; - @PostMapping(value = "/payments/stripeEvents", produces = "application/json") - public ResponseEntity handleStripeEvent(@RequestBody String stripeEvent, @RequestHeader("Stripe-Signature") String stripeSignature) { + @PostMapping(value = "/payments/stripeAccountEvents", produces = "application/json") + public ResponseEntity handleStripeAccountEvent(@RequestBody String stripeEvent, @RequestHeader("Stripe-Signature") String stripeSignature) { try { - Event event = Webhook.constructEvent(stripeEvent, stripeSignature, stripeWebhookSecret); + Event event = Webhook.constructEvent(stripeEvent, stripeSignature, stripeWebhookAccountSecret); switch (event.getType()) { case "checkout.session.completed": - LOGGER.info("Stripe event handled : Checkout session completed"); - processOrderPaymentInitialization(event); + LOGGER.info("Stripe account event handled : Checkout session completed"); + break; + case "checkout.session.expired": + LOGGER.info("Stripe account event handled : Checkout session expired"); break; case "checkout.session.async_payment_succeeded": - LOGGER.info("Stripe event handled : Checkout session async payment succeeded"); - processOrderPaymentValidation(event); + LOGGER.info("Stripe account event handled : Checkout session async payment succeeded"); break; case "checkout.session.async_payment_failed": - LOGGER.warn("Stripe event handled : Checkout session async payment failed"); - processOrderPaymentFailure(event); + LOGGER.warn("Stripe account event handled : Checkout session async payment failed"); break; default: - LOGGER.error("Unhandled event type: {}", event.getType()); - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + LOGGER.info("Unhandled account event type: {}", event.getType()); + return new ResponseEntity<>(NOT_IMPLEMENTED); } } catch (SignatureVerificationException e) { - LOGGER.error("An error occurred when processing Stripe webhook event", e); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + LOGGER.error("An error occurred when processing Stripe webhook account event", e); + return new ResponseEntity<>(INTERNAL_SERVER_ERROR); } - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(OK); } - private void processOrderPaymentInitialization(Event event) { - Session checkoutSession = (Session) event.getData().getObject(); - Order order = findOrderByCheckoutSession(checkoutSession); - orderService.processOrderPaymentInitialization(order); + @PostMapping(value = "/payments/stripeConnectEvents", produces = "application/json") + public ResponseEntity handleStripeConnectEvent(@RequestBody String stripeEvent, @RequestHeader("Stripe-Signature") String stripeSignature) { + try { + Event event = Webhook.constructEvent(stripeEvent, stripeSignature, stripeWebhookConnectSecret); + switch (event.getType()) { + case "checkout.session.completed": + LOGGER.info("Stripe connect event handled : Checkout session completed"); + processOrderPaymentCompleted(event); + break; + case "checkout.session.expired": + LOGGER.info("Stripe connect event handled : Checkout session expired"); + processOrderPaymentExpiration(event); + break; + default: + LOGGER.info("Unhandled connect event type: {}", event.getType()); + return new ResponseEntity<>(NOT_IMPLEMENTED); + } + } catch (SignatureVerificationException e) { + LOGGER.error("An error occurred when processing Stripe webhook connect event", e); + return new ResponseEntity<>(INTERNAL_SERVER_ERROR); + } + return new ResponseEntity<>(OK); } - private void processOrderPaymentValidation(Event event) { + private void processOrderPaymentCompleted(Event event) { Session checkoutSession = (Session) event.getData().getObject(); Order order = findOrderByCheckoutSession(checkoutSession); - orderService.processOrderPaymentValidation(order); + orderService.processOrderPaymentCompletion(order); } - private void processOrderPaymentFailure(Event event) { + private void processOrderPaymentExpiration(Event event) { Session checkoutSession = (Session) event.getData().getObject(); Order order = findOrderByCheckoutSession(checkoutSession); - orderService.processOrderPaymentFailure(order); + orderService.processOrderPaymentExpiration(order); } private Order findOrderByCheckoutSession(Session checkoutSession) { diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePayAndTransferPaymentManager.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePayAndTransferPaymentManager.java new file mode 100644 index 0000000..1a3d716 --- /dev/null +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePayAndTransferPaymentManager.java @@ -0,0 +1,114 @@ +package eu.viandeendirect.domains.payment; + +import com.stripe.exception.StripeException; +import com.stripe.model.Transfer; +import com.stripe.model.checkout.Session; +import com.stripe.param.TransferCreateParams; +import com.stripe.param.checkout.SessionCreateParams; +import eu.viandeendirect.model.Order; +import eu.viandeendirect.model.OrderItem; +import eu.viandeendirect.model.Producer; +import eu.viandeendirect.model.StripePayment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * Manage Stripe payments following the Stripe pattern "separate charge and transfer". + * To use when orders includes productions from different producers. + * + * @see Stripe doc + */ +@Service +@Qualifier("StripePayAndTransferPaymentManager") +public class StripePayAndTransferPaymentManager implements StripePaymentManager{ + + private static final Logger LOGGER = LoggerFactory.getLogger(StripePayAndTransferPaymentManager.class); + + @Value("${PRODUCER_FRONTEND_URL:http://localhost:3000}") + String viandeendirectProducerFrontendUrl; + + + @Override + public StripePayment createPayment(Order order) throws StripeException { + SessionCreateParams.Builder builder = SessionCreateParams.builder(); + order.getItems().forEach(item -> builder.addLineItem(getLineItem(item))); + SessionCreateParams params = builder + .setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setTransferGroup(order.getId().toString()).build()) + .setMode(SessionCreateParams.Mode.PAYMENT) + .setSuccessUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentSuccessful") + .setCancelUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentCancelled") + .build(); + Session session = Session.create(params); + StripePayment stripePayment = new StripePayment(); + stripePayment.setCheckoutSessionId(session.getId()); + stripePayment.setPaymentUrl(session.getUrl()); + return stripePayment; + } + + @Override + public void processPaymentValidation(Order order) { + LOGGER.debug("processing payment validation for order {} : trigger producers transfers while payment manager is {}", order.getId(), this.getClass().getSimpleName()); + Map paymentTransferDatas = getPaymentTransferData(order); + paymentTransferDatas.forEach((producer, paymentTransferData) -> transferPaymentToProducer(order, producer, paymentTransferData)); + } + + Map getPaymentTransferData(Order order) { + return order.getItems().stream().map(item -> Map.entry( + item.getPackageLot().getProduction().getProducer(), + new PaymentTransferData( + item.getUnitPrice().longValue() * item.getQuantity().longValue(), + item.getPackageLot().getLabel() + " x" + item.getQuantity().toString()))) + .reduce(new HashMap<>(), + (map, entry) -> { + if (map.get(entry.getKey()) == null) { + map.put(entry.getKey(), entry.getValue()); + } else { + map.compute(entry.getKey(), (key, existingValue) -> combine(existingValue, entry.getValue())); + } + return map; + }, + (map1, map2) -> { + map2.keySet().forEach(producer -> { + if(map1.get(producer) == null) { + map1.put(producer, map2.get(producer)); + } else { + map1.compute(producer, (key, existingValue) -> combine(existingValue, map2.get(key))); + } + }); + return map1; + }); + } + + private PaymentTransferData combine(PaymentTransferData data1, PaymentTransferData data2) { + var combinedAmount = data1.amount() + data2.amount(); + var combinedDescription = data1.description() + " - " + data2.description(); + return new PaymentTransferData(combinedAmount, combinedDescription); + } + + private void transferPaymentToProducer(Order order, Producer producer, PaymentTransferData paymentTransferData) { + String customerLastName = order.getCustomer().getUser().getLastName(); + TransferCreateParams params = TransferCreateParams.builder() + .setAmount(paymentTransferData.amount()) + .setCurrency("eur") + .setDestination(producer.getStripeAccount().getStripeId()) + .setTransferGroup(order.getId().toString()) + .setDescription("commande " + customerLastName + " n° " + order.getId().toString() + " - " + paymentTransferData.description()) + .build(); + try { + Transfer transfer = Transfer.create(params); + LOGGER.info("the payment of {} for order {} has been transferred to producer {} with Strip transfer id {}", paymentTransferData.amount(), order.getId(), producer.getId(), transfer.getId()); + } catch (StripeException e) { + LOGGER.error("the payment of {} cannot for order {} be transferred to producer {} has failed", paymentTransferData.amount(), order.getId(), producer.getId(), e); + throw new RuntimeException(e); + } + } + + record PaymentTransferData(Long amount, String description) {} + +} diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentManager.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentManager.java new file mode 100644 index 0000000..3e707f9 --- /dev/null +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentManager.java @@ -0,0 +1,38 @@ +package eu.viandeendirect.domains.payment; + +import com.stripe.exception.StripeException; +import com.stripe.param.checkout.SessionCreateParams; +import eu.viandeendirect.model.Order; +import eu.viandeendirect.model.OrderItem; +import eu.viandeendirect.model.StripePayment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public interface StripePaymentManager { + + Logger LOGGER = LoggerFactory.getLogger(StripePaymentManager.class); + + StripePayment createPayment(Order order) throws StripeException; + + void processPaymentValidation(Order order); + + default SessionCreateParams.LineItem getLineItem(OrderItem orderItem) { + long unitAmount = orderItem.getUnitPrice().longValue() * orderItem.getPackageLot().getNetWeight().longValue() * 100; + long quantity = orderItem.getQuantity().longValue(); + LOGGER.debug("creating Stripe payment's line item for order item {} x{} for {} euro-cents each", orderItem.getPackageLot().getLabel(), quantity, unitAmount); + return SessionCreateParams.LineItem.builder() + .setPriceData( + SessionCreateParams.LineItem.PriceData.builder() + .setCurrency("eur") + .setProductData(SessionCreateParams.LineItem.PriceData.ProductData.builder() + .setName(orderItem.getPackageLot().getLabel()) + .build() + ) + .setUnitAmount(unitAmount) + .build() + ) + .setQuantity(quantity) + .build(); + } + +} diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentRepository.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentRepository.java new file mode 100644 index 0000000..311b7c8 --- /dev/null +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripePaymentRepository.java @@ -0,0 +1,7 @@ +package eu.viandeendirect.domains.payment; + +import eu.viandeendirect.model.StripePayment; +import org.springframework.data.repository.CrudRepository; + +public interface StripePaymentRepository extends CrudRepository { +} diff --git a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeService.java b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeService.java index 4a7048b..a3cf4b1 100644 --- a/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeService.java +++ b/backend/app/src/main/java/eu/viandeendirect/domains/payment/StripeService.java @@ -4,23 +4,19 @@ import com.stripe.exception.StripeException; import com.stripe.model.Account; import com.stripe.model.AccountLink; -import com.stripe.model.Transfer; -import com.stripe.model.checkout.Session; import com.stripe.param.AccountCreateParams; import com.stripe.param.AccountLinkCreateParams; -import com.stripe.param.TransferCreateParams; -import com.stripe.param.checkout.SessionCreateParams; -import com.stripe.param.checkout.SessionCreateParams.LineItem; -import eu.viandeendirect.model.*; +import eu.viandeendirect.model.Order; +import eu.viandeendirect.model.Producer; +import eu.viandeendirect.model.StripeAccount; +import eu.viandeendirect.model.StripePayment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.Map; - import static com.stripe.param.AccountLinkCreateParams.Type.ACCOUNT_ONBOARDING; @Service @@ -39,6 +35,10 @@ public void setStripeApiKey(String stripeApiKey) { @Autowired StripeAccountRepository stripeAccountRepository; + @Autowired + @Qualifier("StripeDirectPaymentManager") + StripePaymentManager stripePaymentManager; + public StripeAccount createStripeAccount(Producer producer) throws StripeException { Account account = Account.create( AccountCreateParams.builder() @@ -71,103 +71,11 @@ public void loadStripeAccount(StripeAccount stripeAccount) throws StripeExceptio stripeAccount.setAccountLink(accountLink.getUrl()); } - /** - * Create a Stripe payment following the Stripe pattern "separate charge and transfer" - * - * @param order - * @return - * @see Stripe doc - */ public StripePayment createPayment(Order order) throws StripeException { - SessionCreateParams.Builder builder = SessionCreateParams.builder(); - order.getItems().forEach(item -> builder.addLineItem(getLineItem(item))); - SessionCreateParams params = builder - .setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setTransferGroup(order.getId().toString()).build()) - .setMode(SessionCreateParams.Mode.PAYMENT) - .setSuccessUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentSuccessful") - .setCancelUrl(viandeendirectProducerFrontendUrl + "/order/" + order.getId() + "/paymentCancelled") - .build(); - Session session = Session.create(params); - StripePayment stripePayment = new StripePayment(); - stripePayment.setCheckoutSessionId(session.getId()); - stripePayment.setPaymentUrl(session.getUrl()); - return stripePayment; - } - - private LineItem getLineItem(OrderItem orderItem) { - long unitAmount = orderItem.getUnitPrice().longValue() * orderItem.getPackageLot().getNetWeight().longValue() * 100; - long quantity = orderItem.getQuantity().longValue(); - LOGGER.debug("creating Stripe payment's line item for order item {} x{} for {} euro-cents each", orderItem.getPackageLot().getLabel(), quantity, unitAmount); - return LineItem.builder() - .setPriceData( - LineItem.PriceData.builder() - .setCurrency("eur") - .setProductData(LineItem.PriceData.ProductData.builder() - .setName(orderItem.getPackageLot().getLabel()) - .build() - ) - .setUnitAmount(unitAmount) - .build() - ) - .setQuantity(quantity) - .build(); - } - - public void transferPaymentToProducers(Order order) { - Map paymentTransferDatas = getPaymentTransferData(order); - paymentTransferDatas.forEach((producer, paymentTransferData) -> transferPaymentToProducer(order, producer, paymentTransferData)); - } - - private void transferPaymentToProducer(Order order, Producer producer, PaymentTransferData paymentTransferData) { - String customerLastName = order.getCustomer().getUser().getLastName(); - TransferCreateParams params = TransferCreateParams.builder() - .setAmount(paymentTransferData.amount()) - .setCurrency("eur") - .setDestination(producer.getStripeAccount().getStripeId()) - .setTransferGroup(order.getId().toString()) - .setDescription("commande " + customerLastName + " n° " + order.getId().toString() + " - " + paymentTransferData.description()) - .build(); - try { - Transfer transfer = Transfer.create(params); - LOGGER.info("the payment of {} for order {} has been transferred to producer {} with Strip transfer id {}", paymentTransferData.amount(), order.getId(), producer.getId(), transfer.getId()); - } catch (StripeException e) { - LOGGER.error("the payment of {} cannot for order {} be transferred to producer {} has failed", paymentTransferData.amount(), order.getId(), producer.getId(), e); - throw new RuntimeException(e); - } + return stripePaymentManager.createPayment(order); } - Map getPaymentTransferData(Order order) { - return order.getItems().stream().map(item -> Map.entry( - item.getPackageLot().getProduction().getProducer(), - new PaymentTransferData( - item.getUnitPrice().longValue() * item.getQuantity().longValue(), - item.getPackageLot().getLabel() + " x" + item.getQuantity().toString()))) - .reduce(new HashMap<>(), - (map, entry) -> { - if (map.get(entry.getKey()) == null) { - map.put(entry.getKey(), entry.getValue()); - } else { - map.compute(entry.getKey(), (key, existingValue) -> combine(existingValue, entry.getValue())); - } - return map; - }, - (map1, map2) -> { - map2.keySet().forEach(producer -> { - if(map1.get(producer) == null) { - map1.put(producer, map2.get(producer)); - } else { - map1.compute(producer, (key, existingValue) -> combine(existingValue, map2.get(key))); - } - }); - return map1; - }); + public void processPaymentValidation(Order order) { + stripePaymentManager.processPaymentValidation(order); } - - private PaymentTransferData combine(PaymentTransferData data1, PaymentTransferData data2) { - var combinedAmount = data1.amount() + data2.amount(); - var combinedDescription = data1.description() + " - " + data2.description(); - return new PaymentTransferData(combinedAmount, combinedDescription); - } - - record PaymentTransferData(Long amount, String description) {} } diff --git a/backend/app/src/main/java/eu/viandeendirect/security/configuration/SecurityConfiguration.java b/backend/app/src/main/java/eu/viandeendirect/security/configuration/SecurityConfiguration.java index a965846..f9956a6 100644 --- a/backend/app/src/main/java/eu/viandeendirect/security/configuration/SecurityConfiguration.java +++ b/backend/app/src/main/java/eu/viandeendirect/security/configuration/SecurityConfiguration.java @@ -53,9 +53,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/producers", "/producers/**").hasRole(ROLE_PRODUCER) .requestMatchers("/orders", "/orders/**").hasAnyRole(ROLE_PRODUCER, ROLE_CUSTOMER) .requestMatchers("/error").permitAll() - //TODO : enable role protection - //.requestMatchers("/payments/stripe/**").hasRole(ROLE_PRODUCER) - .requestMatchers("/payments/stripe/**").permitAll() + .requestMatchers("/payments/stripeAccountEvents", "/payments/stripeConnectEvents").permitAll() ); http.oauth2Login(Customizer.withDefaults()); http.logout(logoutCustomizer -> logoutCustomizer.addLogoutHandler(keycloakLogoutHandler).logoutSuccessUrl("/")); diff --git a/backend/app/src/main/resources/application-dev.properties b/backend/app/src/main/resources/application-dev.properties index 7126512..cf0e685 100644 --- a/backend/app/src/main/resources/application-dev.properties +++ b/backend/app/src/main/resources/application-dev.properties @@ -11,8 +11,8 @@ spring.datasource.password=vndndrct-db-p4ssw0rd #spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # database / JPA config -#spring.jpa.hibernate.ddl-auto = validate -spring.jpa.hibernate.ddl-auto = update +spring.jpa.hibernate.ddl-auto = validate +#spring.jpa.hibernate.ddl-auto = update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true diff --git a/backend/app/src/main/resources/database/changelog/2024-06/changelog-2024-06.yaml b/backend/app/src/main/resources/database/changelog/2024-06/changelog-2024-06.yaml new file mode 100644 index 0000000..d06c5f9 --- /dev/null +++ b/backend/app/src/main/resources/database/changelog/2024-06/changelog-2024-06.yaml @@ -0,0 +1,17 @@ +databaseChangeLog: + - changeSet: + id: 2024-05-12 + author: benjamin + changes: + - modifyDataType: + tableName: orders + columnName: status + newDataType: VARCHAR(255) + - changeSet: + id: viandeendirect-2024-05-12 + author: benjamin + changes: + - renameColumn: + tableName: orders + newColumnName: payment_id + oldColumnName: stripe_payment_id \ No newline at end of file diff --git a/backend/app/src/test/java/eu/viandeendirect/domains/order/OrderTestUtils.java b/backend/app/src/test/java/eu/viandeendirect/domains/order/OrderTestUtils.java new file mode 100644 index 0000000..53b2b2f --- /dev/null +++ b/backend/app/src/test/java/eu/viandeendirect/domains/order/OrderTestUtils.java @@ -0,0 +1,59 @@ +package eu.viandeendirect.domains.order; + +import eu.viandeendirect.model.*; + +import java.util.List; + +public class OrderTestUtils { + + public static Order createOrder(PackageLot beefLotSteaksVache, PackageLot beefLotCoteVeau, PackageLot honeyLotMielDeSapin, PackageLot honeyLotMielDeColza) { + + Order order = new Order(); + + OrderItem item1= new OrderItem(); + item1.setQuantity(2); + item1.setPackageLot(beefLotSteaksVache); + item1.setUnitPrice(30f); + + OrderItem item2 = new OrderItem(); + item2.setQuantity(1); + item2.setPackageLot(beefLotCoteVeau); + item2.setUnitPrice(40f); + + OrderItem item3 = new OrderItem(); + item3.setQuantity(3); + item3.setPackageLot(honeyLotMielDeSapin); + item3.setUnitPrice(50f); + + OrderItem item4 = new OrderItem(); + item4.setQuantity(2); + item4.setPackageLot(honeyLotMielDeColza); + item4.setUnitPrice(25f); + + order.setItems(List.of(item1, item2, item3, item4)); + return order; + } + + public static PackageLot getHoneyPackageLot(Producer honeyProducer, String packageLotLabel, int quantity, int quantitySold) { + Production honeyProduction1 = new HonneyProduction(); + honeyProduction1.setProducer(honeyProducer); + PackageLot packageLot = new PackageLot(); + packageLot.setProduction(honeyProduction1); + packageLot.setLabel(packageLotLabel); + packageLot.setQuantity(quantity); + packageLot.setQuantitySold(quantitySold); + return packageLot; + } + + public static PackageLot createBeefPackageLot(Producer beefProducer, String packageLotLabel, int quantity, int quantitySold) { + Production beefProduction1 = new BeefProduction(); + beefProduction1.setProducer(beefProducer); + PackageLot packageLot = new PackageLot(); + packageLot.setProduction(beefProduction1); + packageLot.setLabel(packageLotLabel); + packageLot.setQuantity(quantity); + packageLot.setQuantitySold(quantitySold); + return packageLot; + } + +} diff --git a/backend/app/src/test/java/eu/viandeendirect/domains/order/TestOrder_createOrderPayment.java b/backend/app/src/test/java/eu/viandeendirect/domains/order/TestOrder_createOrderPayment.java new file mode 100644 index 0000000..c5d79a2 --- /dev/null +++ b/backend/app/src/test/java/eu/viandeendirect/domains/order/TestOrder_createOrderPayment.java @@ -0,0 +1,160 @@ +package eu.viandeendirect.domains.order; + +import com.stripe.exception.StripeException; +import eu.viandeendirect.domains.payment.StripeService; +import eu.viandeendirect.domains.production.PackageLotRepository; +import eu.viandeendirect.domains.production.ProductionRepository; +import eu.viandeendirect.domains.user.CustomerRepository; +import eu.viandeendirect.domains.user.ProducerRepository; +import eu.viandeendirect.domains.user.UserRepository; +import eu.viandeendirect.model.*; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static eu.viandeendirect.model.OrderStatus.ITEMS_SELECTED; +import static java.util.regex.Pattern.*; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles(value = "test") +@ExtendWith({SpringExtension.class}) +public class TestOrder_createOrderPayment { + + @Autowired + private OrderService service; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CustomerRepository customerRepository; + + @Autowired + private OrderItemRepository orderItemRepository; + + @MockBean + private StripeService stripeService; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private PackageLotRepository packageLotRepository; + + @Autowired + private ProductionRepository productionRepository; + + @Autowired + private ProducerRepository producerRepository; + + @Test + void should_persist_order_in_database() throws StripeException { + // given + Customer customer = createAndSaveCustomer(); + Producer beefProducer = createAndSaveProducer(1); + Producer honeyProducer = createAndSaveProducer(2); + + PackageLot beefLotSteaksVache = OrderTestUtils.createBeefPackageLot(beefProducer, "steaks de vache", 5, 0); + PackageLot beefLotCoteVeau = OrderTestUtils.createBeefPackageLot(beefProducer, "côte de veau", 5, 0); + PackageLot honeyLotMielDeSapin = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de sapin", 5, 0); + PackageLot honeyLotMielDeColza = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de colza", 5, 0); + + Order order = createAndSaveOrder(beefLotSteaksVache, beefLotCoteVeau, honeyLotMielDeSapin, honeyLotMielDeColza, customer); + + var stripePayment = new StripePayment(); + stripePayment.setId(1); + Mockito.when(stripeService.createPayment(Mockito.any())).thenReturn(stripePayment); + + // when + ResponseEntity response = service.createOrderPayment(order); + + // then + Order orderReloaded = orderRepository.findById(response.getBody().getId()).get(); + assertThat(orderReloaded.getPayment().getId()).isEqualTo(1); + assertThat(orderReloaded.getStatus()).isEqualTo(ITEMS_SELECTED); + PackageLot honeyLotMielDeSapinReloaded = packageLotRepository.findById(honeyLotMielDeSapin.getId()).get(); + assertThat(honeyLotMielDeSapinReloaded.getQuantitySold()).isEqualTo(3); + } + + @Test + void should_raise_an_error_when_all_items_are_already_sold() throws StripeException { + Customer customer = createAndSaveCustomer(); + Producer beefProducer = createAndSaveProducer(1); + Producer honeyProducer = createAndSaveProducer(2); + + PackageLot beefLotSteaksVache = OrderTestUtils.createBeefPackageLot(beefProducer, "steaks de vache", 5, 4); + PackageLot beefLotCoteVeau = OrderTestUtils.createBeefPackageLot(beefProducer, "côte de veau", 5, 4); + PackageLot honeyLotMielDeSapin = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de sapin", 5, 4); + PackageLot honeyLotMielDeColza = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de colza", 5, 4); + + Order order = createAndSaveOrder(beefLotSteaksVache, beefLotCoteVeau, honeyLotMielDeSapin, honeyLotMielDeColza, customer); + + var stripePayment = new StripePayment(); + stripePayment.setId(1); + Mockito.when(stripeService.createPayment(Mockito.any())).thenReturn(stripePayment); + + // when / then + Assertions.assertThatThrownBy(() -> service.createOrderPayment(order)) + .cause().hasMessageMatching(compile(".*Une erreur s'est produite lors de la création d'une commande de .* articles pour le lot '.* - .*'.*", MULTILINE | DOTALL)); + //TODO : revoir les status des commandes ! + Assertions.assertThat(order.getStatus()).isNotEqualTo(ITEMS_SELECTED); + } + + @Test + void should_change_order_status_after_payment_is_completed() { + // given + // when + // then + //TODO : faire un test pour la confirmation du paiement + Assertions.fail("To be implemented..."); + } + + @Test + void should_change_order_status_after_payment_is_aborted() { + // given + // when + // then + //TODO : faire un test pour l'abandon du paiement du paiement + Assertions.fail("To be implemented..."); + } + + + private Order createAndSaveOrder(PackageLot beefLotSteaksVache, PackageLot beefLotCoteVeau, PackageLot honeyLotMielDeSapin, PackageLot honeyLotMielDeColza, Customer customer) { + Order order = OrderTestUtils.createOrder( + beefLotSteaksVache, + beefLotCoteVeau, + honeyLotMielDeSapin, + honeyLotMielDeColza); + order.setCustomer(customer); + productionRepository.saveAll(order.getItems().stream().map(OrderItem::getPackageLot).map(PackageLot::getProduction).toList()); + packageLotRepository.saveAll(order.getItems().stream().map(OrderItem::getPackageLot).distinct().toList()); + return order; + } + + private Producer createAndSaveProducer(int id) { + Producer producer = new Producer(); + producer.setId(id); + producerRepository.save(producer); + return producer; + } + + private Customer createAndSaveCustomer() { + User user = new User(); + user.setEmail("customer@address.mail"); + userRepository.save(user); + + Customer customer = new Customer(); + customer.setUser(user); + customerRepository.save(customer); + return customer; + } + +} diff --git a/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripePayAndTransferPaymentManager.java b/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripePayAndTransferPaymentManager.java new file mode 100644 index 0000000..5012143 --- /dev/null +++ b/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripePayAndTransferPaymentManager.java @@ -0,0 +1,38 @@ +package eu.viandeendirect.domains.payment; + +import eu.viandeendirect.domains.order.OrderTestUtils; +import eu.viandeendirect.model.*; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class TestStripePayAndTransferPaymentManager { + @Test + void getPaymentTransferData_should_return_the_right_payment_transfer_data() { + //given + var paymentManager = new StripePayAndTransferPaymentManager(); + + Producer beefProducer = new Producer(); + beefProducer.setId(1); + + Producer honeyProducer = new Producer(); + honeyProducer.setId(2); + + PackageLot beefLotSteaksVache = OrderTestUtils.createBeefPackageLot(beefProducer, "steaks de vache", 5, 0); + PackageLot beefLotCoteVeau = OrderTestUtils.createBeefPackageLot(beefProducer, "côte de veau", 5, 0); + PackageLot honeyLotMielDeSapin = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de sapin", 5, 0); + PackageLot honeyLotMielDeColza = OrderTestUtils.getHoneyPackageLot(honeyProducer, "miel de colza", 5, 0); + + Order order = OrderTestUtils.createOrder(beefLotSteaksVache, beefLotCoteVeau, honeyLotMielDeSapin, honeyLotMielDeColza); + + //when + var paymentTransferData = paymentManager.getPaymentTransferData(order); + + //then + Assertions.assertThat(paymentTransferData.get(beefProducer).amount()).isEqualTo(100L); + Assertions.assertThat(paymentTransferData.get(honeyProducer).amount()).isEqualTo(200L); + Assertions.assertThat(paymentTransferData.get(beefProducer).description()).contains("steaks de vache x2"); + Assertions.assertThat(paymentTransferData.get(beefProducer).description()).contains("côte de veau x1"); + Assertions.assertThat(paymentTransferData.get(honeyProducer).description()).contains("miel de sapin x3"); + Assertions.assertThat(paymentTransferData.get(honeyProducer).description()).contains("miel de colza x2"); + } +} \ No newline at end of file diff --git a/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripeService.java b/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripeService.java deleted file mode 100644 index e80c315..0000000 --- a/backend/app/src/test/java/eu/viandeendirect/domains/payment/TestStripeService.java +++ /dev/null @@ -1,81 +0,0 @@ -package eu.viandeendirect.domains.payment; - -import eu.viandeendirect.model.*; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -class TestStripeService { - @Test - void getPaymentTransferData_should_return_the_right_payment_transfer_data() { - //given - StripeService stripeService = new StripeService(); - - Producer beefProducer = new Producer(); - beefProducer.setId(1); - - Production beefProduction1 = new BeefProduction(); - beefProduction1.setProducer(beefProducer); - PackageLot beefLotSteaksVache = new PackageLot(); - beefLotSteaksVache.setProduction(beefProduction1); - beefLotSteaksVache.setLabel("steaks de vache"); - - Production beefProduction2 = new BeefProduction(); - beefProduction2.setProducer(beefProducer); - PackageLot beefLotCoteVeau = new PackageLot(); - beefLotCoteVeau.setProduction(beefProduction2); - beefLotCoteVeau.setLabel("côte de veau"); - - - Producer honeyProducer = new Producer(); - honeyProducer.setId(2); - - Production honeyProduction1 = new HonneyProduction(); - honeyProduction1.setProducer(honeyProducer); - PackageLot honeyLotMielDeSapin = new PackageLot(); - honeyLotMielDeSapin.setProduction(honeyProduction1); - honeyLotMielDeSapin.setLabel("miel de sapin"); - - Production honeyProduction2 = new HonneyProduction(); - honeyProduction2.setProducer(honeyProducer); - PackageLot honeyLotMielDeColza = new PackageLot(); - honeyLotMielDeColza.setProduction(honeyProduction2); - honeyLotMielDeColza.setLabel("miel de colza"); - - Order order = new Order(); - - OrderItem item1= new OrderItem(); - item1.setQuantity(2); - item1.setPackageLot(beefLotSteaksVache); - item1.setUnitPrice(30f); - - OrderItem item2 = new OrderItem(); - item2.setQuantity(1); - item2.setPackageLot(beefLotCoteVeau); - item2.setUnitPrice(40f); - - OrderItem item3 = new OrderItem(); - item3.setQuantity(3); - item3.setPackageLot(honeyLotMielDeSapin); - item3.setUnitPrice(50f); - - OrderItem item4 = new OrderItem(); - item4.setQuantity(2); - item4.setPackageLot(honeyLotMielDeColza); - item4.setUnitPrice(25f); - - order.setItems(List.of(item1, item2, item3, item4)); - - //when - var paymentTransferData = stripeService.getPaymentTransferData(order); - - //then - Assertions.assertThat(paymentTransferData.get(beefProducer).amount()).isEqualTo(100L); - Assertions.assertThat(paymentTransferData.get(honeyProducer).amount()).isEqualTo(200L); - Assertions.assertThat(paymentTransferData.get(beefProducer).description()).contains("steaks de vache x2"); - Assertions.assertThat(paymentTransferData.get(beefProducer).description()).contains("côte de veau x1"); - Assertions.assertThat(paymentTransferData.get(honeyProducer).description()).contains("miel de sapin x3"); - Assertions.assertThat(paymentTransferData.get(honeyProducer).description()).contains("miel de colza x2"); - } -} \ No newline at end of file diff --git a/backend/model/src/main/java/eu/viandeendirect/model/Order.java b/backend/model/src/main/java/eu/viandeendirect/model/Order.java index dfe7e66..c8fe16f 100644 --- a/backend/model/src/main/java/eu/viandeendirect/model/Order.java +++ b/backend/model/src/main/java/eu/viandeendirect/model/Order.java @@ -62,6 +62,7 @@ public class Order { private StripePayment payment; @JsonProperty("status") + @Enumerated(EnumType.STRING) private OrderStatus status; public Order id(Integer id) { diff --git a/backend/model/src/main/java/eu/viandeendirect/model/OrderItem.java b/backend/model/src/main/java/eu/viandeendirect/model/OrderItem.java index 7f6ff5b..b413ebc 100644 --- a/backend/model/src/main/java/eu/viandeendirect/model/OrderItem.java +++ b/backend/model/src/main/java/eu/viandeendirect/model/OrderItem.java @@ -174,7 +174,6 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class OrderItem {\n"); sb.append(" id: ").append(toIndentedString(id)).append("\n"); - sb.append(" order: ").append(toIndentedString(order)).append("\n"); sb.append(" packageLot: ").append(toIndentedString(packageLot)).append("\n"); sb.append(" unitPrice: ").append(toIndentedString(unitPrice)).append("\n"); sb.append(" quantity: ").append(toIndentedString(quantity)).append("\n"); diff --git a/backend/model/src/main/java/eu/viandeendirect/model/OrderStatus.java b/backend/model/src/main/java/eu/viandeendirect/model/OrderStatus.java index 0c60793..d3687c2 100644 --- a/backend/model/src/main/java/eu/viandeendirect/model/OrderStatus.java +++ b/backend/model/src/main/java/eu/viandeendirect/model/OrderStatus.java @@ -12,10 +12,10 @@ public enum OrderStatus { ITEMS_SELECTED("ITEMS_SELECTED"), - - PAYMENT_STARTED("PAYMENT_STARTED"), - - PAYED("PAYED"), + + PAYMENT_COMPLETED("PAYMENT_COMPLETED"), + + PAYEMENT_VALIDATED("PAYEMENT_VALIDATED"), PAYMENT_FAILED("PAYMENT_FAILED"), diff --git a/frontend/app/src/domains/sale/views/CustomerOrderForm.tsx b/frontend/app/src/domains/sale/views/CustomerOrderForm.tsx index fd0e90a..dfede0f 100644 --- a/frontend/app/src/domains/sale/views/CustomerOrderForm.tsx +++ b/frontend/app/src/domains/sale/views/CustomerOrderForm.tsx @@ -106,7 +106,7 @@ export default function CustomerOrderForm({ sale: sale, returnCallback: returnCa J'accepte les conditions - + @@ -167,7 +167,7 @@ export default function CustomerOrderForm({ sale: sale, returnCallback: returnCa email: authenticationService.getCurrentUserEmail() } } - createOrder({...order, customer: customer}) + setOrder({...order, customer: customer}) setActiveStep(CONFIRMATION_STEP) } @@ -209,7 +209,7 @@ export default function CustomerOrderForm({ sale: sale, returnCallback: returnCa } function payOrder() { - apiInvoker.callApiAuthenticatedly(keycloak, api => api.createOrderPayment, order.id, payment => redirectToStripePayment(payment.paymentUrl), console.error) + apiInvoker.callApiAuthenticatedly(keycloak, api => api.createOrderPayment, order, order => redirectToStripePayment(order.payment.paymentUrl), console.error) } function redirectToStripePayment(url: string) { diff --git a/openapi/openapi.yml b/openapi/openapi.yml index abd9d14..317f1fa 100644 --- a/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -467,7 +467,7 @@ paths: in: path required: true - /orders/{orderId}/payment: + /orders/payment: summary: Path used to manage an order payment. post: security: @@ -477,18 +477,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StripePayment' - description: Successful response - returns a single `Order`. + $ref: '#/components/schemas/Order' + description: Successful response - returns a single `Order` with its `StripePayment`. operationId: createOrderPayment - summary: Creates an order payment - description: Creates an order payment that should be processes afterward on payment platform. - parameters: - - name: orderId - description: A unique identifier for a `Order`. - schema: - type: integer - in: path - required: true + summary: Creates a payment for the order given as a payload + description: Creates an order and its payment that should be processes afterward on payment platform. + requestBody: + description: A new `Order` to be created. + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + required: true components: securitySchemes: @@ -911,4 +911,4 @@ components: enum: [ON_BOARDING, ACTIVE] OrderStatus: type: string - enum: [ITEMS_SELECTED, PAYMENT_STARTED, PAYED, PAYMENT_FAILED, DELIVERED] + enum: [ITEMS_SELECTED, PAYMENT_COMPLETED, PAYEMENT_VALIDATED, PAYMENT_FAILED, DELIVERED]