Skip to content

Commit

Permalink
Merge pull request #51 from Reyzis2021/feature/Payment-(1-4)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sunagatov committed Jul 20, 2023
2 parents 4bd1154 + 7282841 commit 1578952
Show file tree
Hide file tree
Showing 21 changed files with 404 additions and 6 deletions.
24 changes: 19 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<slf4j-api.version>2.0.5</slf4j-api.version>
<jackson-databind.version>2.15.1</jackson-databind.version>
<jackson-databind.version>2.14.0</jackson-databind.version>
<modelmapper.version>3.1.1</modelmapper.version>
<junit-jupiter-engine.version>5.4.2</junit-jupiter-engine.version>
<junit-jupiter-api.version>5.4.2</junit-jupiter-api.version>
<junit-platform-commons.version>1.4.0</junit-platform-commons.version>
<junit-platform-launcher.version>1.4.0</junit-platform-launcher.version>
<junit-jupiter.version>5.9.2</junit-jupiter.version>
<jackson-core.version>2.14.2</jackson-core.version>
<jackson-core.version>2.14.0</jackson-core.version>
<springfox.swagger.version>3.0.0</springfox.swagger.version>
<swagger-core-version>2.2.8</swagger-core-version>
<version.keycloak>21.0.1</version.keycloak>
Expand All @@ -54,9 +54,11 @@
<jjwt-api.version>0.11.5</jjwt-api.version>
<jjwt-jackson.version>0.11.5</jjwt-jackson.version>
<logstash-logback-encoder.version>7.3</logstash-logback-encoder.version>
<spring-boot-maven-plugin.version>3.0.0</spring-boot-maven-plugin.version>
<spring-boot-maven-plugin.version>3.1.0</spring-boot-maven-plugin.version>
<postgresql.version>42.6.0</postgresql.version>
<liquibase.verson>4.23.0</liquibase.verson>
<stripe.version>22.15.0</stripe.version>
<apache-commons-lang3.version>3.12.0</apache-commons-lang3.version>
</properties>
<dependencies>

Expand Down Expand Up @@ -198,15 +200,27 @@
<artifactId>liquibase-core</artifactId>
<version>${liquibase.verson}</version>
</dependency>
</dependencies>

<!-- Stripe Api -->
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
<version>${stripe.version}</version>
</dependency>

<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${apache-commons-lang3.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-maven-plugin.version}</version>
<configuration>
<excludes>
<exclude>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.zufar.onlinestore;

import com.zufar.onlinestore.payment.config.StripeConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(StripeConfiguration.class)
public class OnlineStoreApplication {

public static void main(String[] args) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/zufar/onlinestore/payment/PaymentApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.zufar.onlinestore.payment;

import com.stripe.exception.StripeException;
import com.zufar.onlinestore.payment.dto.CreatePaymentDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsWithTokenDto;
import com.zufar.onlinestore.payment.exception.PaymentNotFoundException;
import org.springframework.http.ResponseEntity;

public interface PaymentApi {

ResponseEntity<PaymentDetailsWithTokenDto> paymentProcess(CreatePaymentDto paymentDto) throws StripeException;

ResponseEntity<PaymentDetailsDto> getPaymentDetails(Long paymentId) throws PaymentNotFoundException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.zufar.onlinestore.payment.config;

import com.stripe.Stripe;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* This record responsible for displaying the Stripe Api configuration in Spring as a bean.
* Configuration stores special keys with which Stripe controls data security.
*
* @param secretKey used to work with the Stripe Api from the backend side.
*
* @param publishableKey used to work with the Stripe Api from the frontend side.
*
* */
@ConfigurationProperties(prefix = "stripe")
public record StripeConfiguration(String secretKey, String publishableKey) {

@PostConstruct
private void init() {
Stripe.apiKey = secretKey;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.zufar.onlinestore.payment.controller;

import com.stripe.exception.StripeException;
import com.zufar.onlinestore.payment.PaymentApi;
import com.zufar.onlinestore.payment.dto.CreatePaymentDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsDto;
import com.zufar.onlinestore.payment.dto.PaymentDetailsWithTokenDto;
import com.zufar.onlinestore.payment.dto.PriceDetailsDto;
import com.zufar.onlinestore.payment.service.PaymentService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping(PaymentController.PAYMENT_URL)
public class PaymentController implements PaymentApi {

public static final String PAYMENT_URL = "/api/v1/payment";

private final PaymentService paymentService;

@PostMapping
public ResponseEntity<PaymentDetailsWithTokenDto> paymentProcess(@RequestBody @Valid CreatePaymentDto paymentRequest) throws StripeException {
log.info("payment process: receive request to create payment: paymentRequest: {}.", paymentRequest);
PriceDetailsDto priceDetails = paymentRequest.priceDetails();
PaymentDetailsWithTokenDto processedPayment = paymentService.createPayment(paymentRequest.paymentMethodId(),
priceDetails.totalPrice(), priceDetails.currency());
log.info("payment process: payment successfully processed: processedPayment: {}.", processedPayment);

return ResponseEntity.status(HttpStatus.CREATED).body(processedPayment);
}

@GetMapping("/{paymentId}")
public ResponseEntity<PaymentDetailsDto> getPaymentDetails(@PathVariable Long paymentId) {
log.info("get payment details: receive payment id: paymentId: {}.", paymentId);
PaymentDetailsDto retrievedPayment = paymentService.getPayment(paymentId);
if (retrievedPayment == null) {
log.info("get payment details: not found payment details by id: paymentId: {}.", paymentId);
return ResponseEntity.notFound().build();
}
log.info("get payment details: payment successfully retrieved: retrievedPayment: {}.", retrievedPayment);

return ResponseEntity.ok().body(retrievedPayment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zufar.onlinestore.payment.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record CreatePaymentDto(

@NotBlank(message = "Payment method id is mandatory attribute")
String paymentMethodId,

@NotNull(message = "Price details is mandatory attribute")
PriceDetailsDto priceDetails) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zufar.onlinestore.payment.dto;

import lombok.Builder;
import java.math.BigDecimal;

@Builder
public record PaymentDetailsDto(Long paymentId,
BigDecimal totalPrice,
String currency,
String status,
String description) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.zufar.onlinestore.payment.dto;

import lombok.Builder;

@Builder
public record PaymentDetailsWithTokenDto(String paymentToken, PaymentDetailsDto paymentDetailsDto) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.zufar.onlinestore.payment.dto;

import lombok.Builder;

@Builder
public record PaymentWithTokenDetailsDto(String paymentToken, PaymentDetailsDto paymentDetailsDto) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.zufar.onlinestore.payment.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import java.math.BigDecimal;

public record PriceDetailsDto(

@NotNull(message = "Total price is mandatory attribute")
BigDecimal totalPrice,

@NotBlank(message = "Currency is mandatory attribute")
@Size(min = 3, max = 3, message = "Currency value must be only 3 characters long")
String currency) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zufar.onlinestore.payment.enums;

public enum PaymentStatus {
REQUIRES_PAYMENT_METHOD,
REQUIRES_CONFIRMATION,
REQUIRES_CAPTURE,
REQUIRES_ACTION,
PROCESSING,
CANCELED,
SUCCEEDED
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.zufar.onlinestore.payment.exception;

import lombok.Getter;

@Getter
public class PaymentNotFoundException extends RuntimeException {

private final Long paymentId;

public PaymentNotFoundException(Long paymentId) {
super(String.format("Payment with id %s not found.", paymentId));
this.paymentId = paymentId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.zufar.onlinestore.payment.mapper;

import com.zufar.onlinestore.payment.dto.PaymentDetailsDto;
import com.zufar.onlinestore.payment.model.Payment;
import org.springframework.stereotype.Component;

@Component
public class PaymentConverter {

public PaymentDetailsDto toDto(Payment entity) {
return PaymentDetailsDto.builder()
.paymentId(entity.getPaymentId())
.totalPrice(entity.getItemsTotalPrice())
.currency(entity.getCurrency())
.description(entity.getDescription())
.status(entity.getStatus())
.build();
}

public Payment toEntity(PaymentDetailsDto dto) {
return Payment.builder()
.paymentId(dto.paymentId())
.itemsTotalPrice(dto.totalPrice())
.currency(dto.currency())
.description(dto.description())
.status(dto.status())
.build();
}

}
40 changes: 40 additions & 0 deletions src/main/java/com/zufar/onlinestore/payment/model/Payment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.zufar.onlinestore.payment.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Column;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Getter;
import java.math.BigDecimal;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Payment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "payment_id")
private Long paymentId;

@Column(nullable = false, name = "currency")
private String currency;

@Column(nullable = false, name = "items_total_price")
private BigDecimal itemsTotalPrice;

@Column(name = "status")
private String status;

@Column(name = "description")
private String description;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.zufar.onlinestore.payment.processor;

import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import com.stripe.model.PaymentMethod;
import com.stripe.param.PaymentIntentCreateParams;
import com.zufar.onlinestore.payment.config.StripeConfiguration;
import com.zufar.onlinestore.payment.model.Payment;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;

@Slf4j
@RequiredArgsConstructor
@Component
public class PaymentProcessor {

public static final String PAYMENT_MESSAGE = "Payment made by user: %s, using the payment method: %s.";
public static final Integer PAYMENT_DELIMITER = 100;

private final StripeConfiguration stripeConfig;

public Pair<String, Payment> process(String paymentMethodId, BigDecimal totalPrice, String currency) throws StripeException {
PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodId);
PaymentIntentCreateParams params = getPaymentParams(paymentMethod, totalPrice, currency);
log.info("process: get payment intent params for payment creation: params: {}.", params);
PaymentIntent paymentIntent = PaymentIntent.create(params);
log.info("process: payment intent successfully created: paymentIntentId: {}.", paymentIntent.getId());
String paymentToken = paymentIntent.getClientSecret();
Payment payment = getProcessedPayment(paymentIntent);

return Pair.of(paymentToken, payment);
}

private PaymentIntentCreateParams getPaymentParams(PaymentMethod paymentMethod, BigDecimal totalPrice, String currency) {
String email = paymentMethod.getBillingDetails().getEmail();
return PaymentIntentCreateParams.builder()
.setAmount(totalPrice.longValue() * PAYMENT_DELIMITER)
.setCurrency(currency)
.setPaymentMethod(paymentMethod.getId())
.setReceiptEmail(email)
.setDescription(PAYMENT_MESSAGE.formatted(email, paymentMethod.getType()))
.build();
}

private Payment getProcessedPayment(PaymentIntent paymentIntent) {
return Payment.builder()
.itemsTotalPrice(BigDecimal.valueOf(paymentIntent.getAmount() / PAYMENT_DELIMITER))
.currency(paymentIntent.getCurrency())
.description(paymentIntent.getDescription())
.status(paymentIntent.getStatus())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.zufar.onlinestore.payment.repository;

import com.zufar.onlinestore.payment.model.Payment;
import org.springframework.data.repository.CrudRepository;

public interface PaymentRepository extends CrudRepository<Payment, Long> {
}
Loading

0 comments on commit 1578952

Please sign in to comment.