-
-
Notifications
You must be signed in to change notification settings - Fork 119
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
Changes from all commits
91ce3e6
51a28f8
06db561
0f098ad
3003d75
890d5ad
82ae42b
1660628
98ed903
5a44313
e2ae8a6
28acd3a
c5be6d3
e0d33fe
d317ec2
274c6e3
ba5f096
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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") | ||
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}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe better use "spring" in the componentModel instead of MappingConstants.ComponentModel.SPRING ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe better use "spring" in the componentModel instead of MappingConstants.ComponentModel.SPRING ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no |
||
public interface ShoppingSessionItemDtoConverter { | ||
|
||
@Named("toShoppingSessionItemDto") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add new line at the end of the file There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
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.CreateCardDetailsTokenRequest; | ||
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 createCardDetailsTokenRequest 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 CreateCardDetailsTokenRequest createCardDetailsTokenRequest); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.zufar.onlinestore.payment.api.dto; | ||
|
||
import jakarta.validation.constraints.NotEmpty; | ||
import lombok.Builder; | ||
|
||
@Builder | ||
public record CreateCardDetailsTokenRequest( | ||
|
||
@NotEmpty(message = "CardNumber is the mandatory attribute") | ||
String cardNumber, | ||
|
||
@NotEmpty(message = "ExpMonth is the mandatory attribute") | ||
String expMonth, | ||
|
||
@NotEmpty(message = "ExpYear is the mandatory attribute") | ||
String expYear, | ||
|
||
@NotEmpty(message = "Cvc is the mandatory attribute") | ||
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 ProcessPaymentRequest( | ||
|
||
@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 |
---|---|---|
@@ -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 |
---|---|---|
@@ -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.CreateCardDetailsTokenRequest; | ||
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(CreateCardDetailsTokenRequest createCardDetailsTokenRequest) { | ||
StripeConfiguration.setStripeKey(stripeConfiguration.publishableKey()); | ||
|
||
Map<String, Object> card = createCardDetails(createCardDetailsTokenRequest); | ||
Token cardDetailsToken; | ||
try { | ||
cardDetailsToken = Token.create(Map.of("card", card)); | ||
} catch (StripeException e) { | ||
throw new CardTokenCreationException(createCardDetailsTokenRequest.cardNumber()); | ||
} | ||
return cardDetailsToken.getId(); | ||
} | ||
|
||
private Map<String, Object> createCardDetails(CreateCardDetailsTokenRequest createCardDetailsTokenRequest) { | ||
return Map.of("number", createCardDetailsTokenRequest.cardNumber(), | ||
"exp_month", Integer.parseInt(createCardDetailsTokenRequest.expMonth()), | ||
"exp_year", Integer.parseInt(createCardDetailsTokenRequest.expYear()), | ||
"cvc", createCardDetailsTokenRequest.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()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.