Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[payment-8]: Payment api refactoring with customer and shoping session logic #87

Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.zufar.onlinestore.cart.api;

import com.zufar.onlinestore.cart.dto.ShoppingSessionItemDto;
import com.zufar.onlinestore.cart.entity.ShoppingSessionItem;
import org.mapstruct.Named;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;

@Service
public class ItemsTotalPriceCalculator {

public BigDecimal calculate(List<ShoppingSessionItemDto> items) {
@Named("toItemsTotalPrice")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this annotation here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

public BigDecimal calculate(Set<ShoppingSessionItem> items) {
return items.stream()
.map(item -> item.productInfo().price()
.multiply(BigDecimal.valueOf(item.productsQuantity())))
.map(item -> item.getProductInfo().getPrice().multiply(BigDecimal.valueOf(item.getProductsQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.zufar.onlinestore.cart.converter;

import com.zufar.onlinestore.cart.api.ItemsTotalPriceCalculator;
import com.zufar.onlinestore.cart.dto.ShoppingSessionDto;
import com.zufar.onlinestore.cart.entity.ShoppingSession;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;


@Mapper(componentModel = "spring", uses = ShoppingSessionItemDtoConverter.class)
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {ShoppingSessionItemDtoConverter.class, ItemsTotalPriceCalculator.class})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better use "spring" in the componentModel instead of MappingConstants.ComponentModel.SPRING ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

public interface ShoppingSessionDtoConverter {

@Mapping(target = "items", source = "entity.items", qualifiedByName = {"toShoppingSessionItemDto"})
@Mapping(target = "itemsTotalPrice", source = "entity.items", qualifiedByName = {"toItemsTotalPrice"})
ShoppingSessionDto toDto(final ShoppingSession entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import com.zufar.onlinestore.product.converter.ProductInfoDtoConverter;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.Named;


@Mapper(componentModel = "spring", uses = ProductInfoDtoConverter.class)
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = ProductInfoDtoConverter.class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better use "spring" in the componentModel instead of MappingConstants.ComponentModel.SPRING ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

public interface ShoppingSessionItemDtoConverter {

@Named("toShoppingSessionItemDto")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new line at the end of the file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Mapping(target = "productInfo", source = "entity.productInfo", qualifiedByName = {"toProductInfoFullDto"})
ShoppingSessionItemDto toDto(final ShoppingSessionItem entity);
}
}
47 changes: 18 additions & 29 deletions src/main/java/com/zufar/onlinestore/payment/api/PaymentApi.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,41 @@
package com.zufar.onlinestore.payment.api;

import com.zufar.onlinestore.payment.dto.CreatePaymentDto;
import com.zufar.onlinestore.payment.dto.CreatePaymentMethodDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsWithTokenDto;
import com.zufar.onlinestore.payment.exception.PaymentEventParsingException;
import com.zufar.onlinestore.payment.exception.PaymentEventProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentIntentProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentMethodProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentNotFoundException;
import com.zufar.onlinestore.payment.api.dto.CreateCardDetailsTokenDto;
import com.zufar.onlinestore.payment.api.dto.ProcessedPaymentDetailsDto;
import com.zufar.onlinestore.payment.api.dto.ProcessedPaymentWithClientSecretDto;

public interface PaymentApi {

/**
* This method allows to create a payment object
* This method is responsible for payment processing
*
* @param createPaymentDto the request dto to create a payment object
* @return PaymentDetailsWithTokenDto combines payment details and a payment token for payment processing on the front end side
* @throws PaymentIntentProcessingException this error occurs in cases where the data passed to process a payment intent is not valid.
* @param cardDetailsTokenId is stripe token collected based on information about the user's payment card
* @return PaymentDetailsWithTokenDto combines payment identifier and payment token for processing on front-end side
* */
PaymentDetailsWithTokenDto createPayment(final CreatePaymentDto createPaymentDto) throws PaymentIntentProcessingException;
ProcessedPaymentWithClientSecretDto processPayment(final String cardDetailsTokenId);

/**
* This method allows to create a payment method object
*
* @param createPaymentMethodDto the request dto to create a payment method object
* @return String payment method identifier, for secure method transfer using the Stripe API
* @throws PaymentMethodProcessingException this error occurs in cases when the data transmitted to create a payment method is not valid.
* @param paymentId the payment identifier for retrieve payment details
* @return PaymentDetailsDto is payment details object
* */
String createPaymentMethod(final CreatePaymentMethodDto createPaymentMethodDto) throws PaymentMethodProcessingException;
ProcessedPaymentDetailsDto getPaymentDetails(final Long paymentId);

/**
* This method allows to create a payment method object
*
* @param paymentId the payment identifier to search payment details
* @return PaymentDetailsDto these are payment details
* @throws PaymentNotFoundException this error is thrown in cases when the payment by the passed identifier was not found
* @param paymentIntentPayload string describing of the payment intent event type.
* @param stripeSignatureHeader stripe signature, which provide safe work with Stripe API webhooks mechanism
* */
PaymentDetailsDto getPaymentDetails(final Long paymentId) throws PaymentNotFoundException;
void processPaymentEvent(final String paymentIntentPayload, final String stripeSignatureHeader);

/**
* This method allows to create a payment method object
* This method is a temporary solution until the front-end goes functional to pass us a token with payment card details.
* In the meantime, this method serves to test the payment api.
*
* @param paymentIntentPayload this param it is a string describing of the payment intent event type.
* @param stripeSignatureHeader this param it is a string describing of the stripe signature, which provide safe work with Stripe API webhooks mechanism
* @throws PaymentEventProcessingException this error occurs in cases where webhook event data is not valid.
* @throws PaymentEventParsingException this error occurs when it is impossible to start the event in the intent due to invalid data
* @param createCardDetailsTokenDto object that contains data about customer payment card.
* @return returns card details token in string form
* */
void processPaymentEvent(final String paymentIntentPayload, final String stripeSignatureHeader) throws PaymentEventProcessingException, PaymentEventParsingException;

String processCardDetailsToken(final CreateCardDetailsTokenDto createCardDetailsTokenDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zufar.onlinestore.payment.api.dto;

import lombok.Builder;

@Builder
public record CreateCardDetailsTokenDto(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCardDetailsTokenRequest looks better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are @notempty annotations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

String cardNumber,
String expMonth,
String expYear,
String cvc
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.zufar.onlinestore.payment.api.dto;

import jakarta.validation.constraints.NotBlank;
import java.util.UUID;

public record ProcessPaymentDto(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessPaymentRequest looks better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


@NotBlank(message = "CardInfoToken is the mandatory attribute")
String cardInfoToken,

@NotBlank(message = "CustomerId is the mandatory attribute")
UUID customerId
) {
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.zufar.onlinestore.payment.dto;
package com.zufar.onlinestore.payment.api.dto;

import com.zufar.onlinestore.cart.dto.ShoppingSessionItemDto;
import com.zufar.onlinestore.payment.enums.PaymentStatus;
import lombok.Builder;
import java.math.BigDecimal;
import java.util.Set;

@Builder
public record PaymentDetailsDto(
public record ProcessedPaymentDetailsDto(
Long paymentId,
String paymentIntentId,
BigDecimal itemsTotalPrice,
String paymentIntentId,
Set<ShoppingSessionItemDto> items,
PaymentStatus status,
String description
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.zufar.onlinestore.payment.api.dto;

import lombok.Builder;

@Builder
public record ProcessedPaymentWithClientSecretDto(
Long paymentId,
String clientSecret
) {
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package com.zufar.onlinestore.payment.api.impl;

import com.zufar.onlinestore.payment.api.PaymentApi;
import com.zufar.onlinestore.payment.api.dto.CreateCardDetailsTokenDto;
import com.zufar.onlinestore.payment.api.dto.ProcessedPaymentDetailsDto;
import com.zufar.onlinestore.payment.api.dto.ProcessedPaymentWithClientSecretDto;
import com.zufar.onlinestore.payment.api.impl.event.PaymentEventProcessor;
import com.zufar.onlinestore.payment.api.impl.intent.PaymentCreator;
import com.zufar.onlinestore.payment.api.impl.intent.PaymentMethodCreator;
import com.zufar.onlinestore.payment.api.impl.customer.CardDetailsProcessor;
import com.zufar.onlinestore.payment.api.impl.intent.PaymentProcessor;
import com.zufar.onlinestore.payment.api.impl.intent.PaymentRetriever;
import com.zufar.onlinestore.payment.dto.CreatePaymentDto;
import com.zufar.onlinestore.payment.dto.CreatePaymentMethodDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsWithTokenDto;
import com.zufar.onlinestore.payment.exception.PaymentEventParsingException;
import com.zufar.onlinestore.payment.exception.PaymentEventProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentIntentProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentMethodProcessingException;
import com.zufar.onlinestore.payment.exception.PaymentNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -24,27 +18,27 @@
public class PaymentApiImpl implements PaymentApi {

private final PaymentRetriever paymentRetriever;
private final PaymentCreator paymentCreator;
private final PaymentMethodCreator paymentMethodCreator;
private final PaymentProcessor paymentProcessor;
private final PaymentEventProcessor paymentEventProcessor;
private final CardDetailsProcessor cardDetailsProcessor;

@Override
public PaymentDetailsWithTokenDto createPayment(final CreatePaymentDto createPaymentDto) throws PaymentIntentProcessingException {
return paymentCreator.createPayment(createPaymentDto);
public ProcessedPaymentWithClientSecretDto processPayment(final String cardDetailsTokenId) {
return paymentProcessor.processPayment(cardDetailsTokenId);
}

@Override
public String createPaymentMethod(final CreatePaymentMethodDto createPaymentMethodDto) throws PaymentMethodProcessingException {
return paymentMethodCreator.createPaymentMethod(createPaymentMethodDto);
public ProcessedPaymentDetailsDto getPaymentDetails(final Long paymentId) {
return paymentRetriever.getPaymentDetails(paymentId);
}

@Override
public PaymentDetailsDto getPaymentDetails(Long paymentId) throws PaymentNotFoundException {
return paymentRetriever.getPaymentDetails(paymentId);
public void processPaymentEvent(final String paymentIntentPayload, final String stripeSignatureHeader) {
paymentEventProcessor.processPaymentEvent(paymentIntentPayload, stripeSignatureHeader);
}

@Override
public void processPaymentEvent(final String paymentIntentPayload, final String stripeSignatureHeader) throws PaymentEventProcessingException, PaymentEventParsingException {
paymentEventProcessor.processPaymentEvent(paymentIntentPayload, stripeSignatureHeader);
public String processCardDetailsToken(CreateCardDetailsTokenDto createCardDetailsTokenDto) {
return cardDetailsProcessor.processCardDetails(createCardDetailsTokenDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.zufar.onlinestore.payment.api.impl.customer;

import com.stripe.exception.StripeException;
import com.stripe.model.Token;
import com.zufar.onlinestore.payment.api.dto.CreateCardDetailsTokenDto;
import com.zufar.onlinestore.payment.config.StripeConfiguration;
import com.zufar.onlinestore.payment.exception.CardTokenCreationException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class CardDetailsProcessor {

private final StripeConfiguration stripeConfiguration;

public String processCardDetails(CreateCardDetailsTokenDto createCardDetailsTokenDto) {
StripeConfiguration.setStripeKey(stripeConfiguration.publishableKey());

Map<String, Object> card = createCardDetails(createCardDetailsTokenDto);
Token cardDetailsToken;
try {
cardDetailsToken = Token.create(Map.of("card", card));
} catch (StripeException e) {
throw new CardTokenCreationException(createCardDetailsTokenDto.cardNumber());
}
return cardDetailsToken.getId();
}

private static Map<String, Object> createCardDetails(CreateCardDetailsTokenDto createCardDetailsTokenDto) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove static

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return Map.of("number", createCardDetailsTokenDto.cardNumber(),
"exp_month", Integer.parseInt(createCardDetailsTokenDto.expMonth()),
"exp_year", Integer.parseInt(createCardDetailsTokenDto.expYear()),
"cvc", createCardDetailsTokenDto.cvc());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.zufar.onlinestore.payment.api.impl.customer;

import com.stripe.exception.StripeException;
import com.stripe.model.Customer;
import com.stripe.param.CustomerCreateParams;
import com.zufar.onlinestore.payment.converter.StripeCustomerConverter;
import com.zufar.onlinestore.payment.exception.StripeCustomerProcessingException;
import com.zufar.onlinestore.user.entity.UserEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@RequiredArgsConstructor
@Service
public class StripeCustomerCreator {

private final StripeCustomerConverter stripeCustomerConverter;

public Customer createStripeCustomer(UserEntity authorizedUser, String paymentMethodToken) {
try {
log.info("Create stripe customer: in progress: start stripe customer creation");
CustomerCreateParams customerCreateParams = stripeCustomerConverter.toStripeObject(authorizedUser, paymentMethodToken);
Customer createdStripeCustomer = Customer.create(customerCreateParams);
String createdStripeCustomerId = createdStripeCustomer.getId();
log.info("Create stripe customer: successful: stripe customer was created with createdStripeCustomerId = {}.", createdStripeCustomerId);

return createdStripeCustomer;

} catch (StripeException e) {
log.error("Process stripe customer: failed: stripe customer was not created");
throw new StripeCustomerProcessingException(authorizedUser.getEmail());
}
}
}
Loading