0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

バック:SpringBoot で決済機能を実装する。Stripeでの決済結果をローカルのデータベース上に記録する 3/4

Last updated at Posted at 2023-03-03

概要

今回は決済結果をMySQLに保存する機能を実装します。
Stripeを使った処理は無いです。

開発環境

OS:windows10

バックエンド側:
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java : 11
API検証ツール:POSTMAN

図 

実装したい動き

下図の赤枠部分を実装します。
image.png

Stripeでの決済が成功した場合実行される処理です。
手順としては3つの流れになっています。
1.注文情報(金額、日付、顧客Id)を保存する。(ordersテーブルに INSERT)
2.注文した商品情報を保存する。(order_itemsテーブルにINSERT)
3.顧客の買い物カゴを空にする、(cart_ItemsテーブルにDELETE処理)

データベース

今回の処理はテーブルを3つ使っています。
書き方はテキトーです。

image.png

クラス図

image.png

メインの処理はOrderServiceImplementで行っています。

※CartServiceImplemntは商品情報から総額を算出するために使っています。

コード部分(とても長いのでスキップ推奨、クラス図を先に見て気になる部分だけご覧ください)

Order(Entity)

Order.java
package com.example.restapi.domain.order;


import lombok.*;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@Table(name = "orders")
@NoArgsConstructor
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Integer id;

    // 顧客のId
    @JoinColumn(name ="customer_id")
    Integer customerId;

    // お客様が支払う金額
    @JoinColumn(name ="amount")
    Integer amount;

    @JoinColumn(name ="created_at")
    Date createdAt;

    // 支払い方法について 0だとクレカ 1だと現金着払い
    @JoinColumn(name ="type")
    Integer type;

    // 支払状況
    // クレカ決済の場合、データベースに追加時にtrueになる
    // 現金払いの場合、データベースに追加時にはfalseになる。
    @Column(name ="is_payment_finished", nullable = false)
    boolean isPaymentFinished;

    // 出荷状況
    // データベースに追加された時はfalseが入る
    // 客に商品を渡したときに発送担当者が、trueにする。
    @Column(name ="is_shipping_finished", nullable = false)
    boolean isShippingFinished;

    public Order(Integer customerId, Integer amount, Date createdAt, Integer type, boolean isPaymentFinished, boolean isShippingFinished) {
        this.customerId = customerId;
        this.amount = amount;
        this.createdAt = createdAt;
        this.type = type;
        this.isPaymentFinished = isPaymentFinished;
        this.isShippingFinished = isShippingFinished;
    }
}

OrderRepository

OrderRepository.java
package com.example.restapi.domain.order;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderRepository extends CrudRepository<Order,Integer> {

    Order save(Order order);

}


OrderService

java_OrderService.java
package com.example.restapi.domain.order;

import com.example.restapi.implement.cartItem.CartItemDto;
import com.example.restapi.implement.payment.PaymentInfoRequest;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public interface OrderService {

    // クレジットカード決済をするために、Stripe側の処理を行う
    // mySQLに書き込む処理はなし
    PaymentIntent createPaymentIntent(PaymentInfoRequest paymentInfoRequest) throws StripeException;

    // 決済を行う
    // OrderとOrderItemのテーブルにデータを追加する。CartItemの情報を初期化する Stripe上の処理はなし
    void finishOrder(int customerId, int amount, int type, List<CartItemDto> cartItemDtos);

}


OrderServiceImplemnt

OrderServiceImplemnt.java
package com.example.restapi.implement.order;

import com.example.restapi.domain.cartItem.CartItemRepository;
import com.example.restapi.domain.order.Order;
import com.example.restapi.domain.order.OrderRepository;
import com.example.restapi.domain.order.OrderService;
import com.example.restapi.domain.orderItem.OrderItem;
import com.example.restapi.domain.orderItem.OrderItemRepository;
import com.example.restapi.implement.cartItem.CartItemDto;
import com.example.restapi.implement.payment.PaymentInfoRequest;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@Transactional
public class OrderServiceImplement implements OrderService {

    @Autowired
    CartItemRepository cartItemRepository;

    @Autowired
    OrderItemRepository orderItemRepository;

    @Autowired
    OrderRepository orderRepository;

    @Override
    public PaymentIntent createPaymentIntent(PaymentInfoRequest paymentInfoRequest) throws StripeException {
        List<String> paymentMethodTypes = new ArrayList<>();
        paymentMethodTypes.add("card");
        List<String> paymentMethod = new ArrayList<>();
        Map<String, Object> automaticPaymentMethods =
                new HashMap<>();
        automaticPaymentMethods.put("enabled", true);
        Map<String, Object> params = new HashMap<>();
        params.put("amount", paymentInfoRequest.getAmount());
        params.put("currency", paymentInfoRequest.getCurrency());
        params.put("payment_method_types", paymentMethodTypes);

        return PaymentIntent.create(params);
    }

+    @Override
+    public void finishOrder(int customerId,int amount,int type,List<CartItemDto> cartItemDtos) {
+        // Orderを作成する
+        Order order = new Order(customerId,amount, new Date(),0,true,false);
+        orderRepository.save(order);
+
+        // cartItemsとorderの情報を元に顧客の。
+        List<OrderItem> orderItems = new ArrayList<>();
+        for(CartItemDto cartItemDto :cartItemDtos) {
+           OrderItem aartItem  = new OrderItem();
+           aartItem = orderItemRepository.save(new OrderItem(order.getId(),cartItemDto.getProductId(),cartItemDto.getQuantity()));
+        }
+        // 顧客のcartItemを削除する
+        cartItemRepository.deleteByCustomerId(customerId);
+
+    }
}

OrderRestController

OrderRestController.java
package com.example.restapi.implement.order;

import com.example.restapi.domain.cartItem.CartItemService;
import com.example.restapi.domain.customer.CustomerService;
import com.example.restapi.domain.order.OrderService;
import com.example.restapi.implement.cartItem.CartItemResponse;
import com.example.restapi.implement.payment.PaymentInfoRequest;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@CrossOrigin(origins = "http://127.0.0.1:3000")
@RestController
public class OrderRestController {

    @Autowired
    OrderService orderService;

    @Autowired
    CartItemService cartItemService;

    @Autowired
    CustomerService customerService;

    @PostMapping("/api/pay/preparePaymentIntent")
    public ResponseEntity<String> preparePayment(@RequestBody PaymentInfoRequest paymentInfoRequest)throws StripeException{
        PaymentIntent paymentIntent = orderService.createPaymentIntent(paymentInfoRequest);
        String paymentStr = paymentIntent.toJson();
        return new ResponseEntity<>(paymentStr, HttpStatus.OK);
    }

+    @PutMapping("/api/pay/finish")
+    public void finish( HttpServletRequest request){
+        Integer customerId = customerService.getIdfromJwtToken(request);
+        CartItemResponse cartItemResponse = cartItemService.listCartItems(customerId);
+        orderService.finishOrder(customerId,(int)cartItemResponse.getTotal(),0,cartItemResponse.getCartItemDtos());
+    }
}

OrderItem(Entity)

OrderItem.java
package com.example.restapi.domain.orderItem;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "order_items")
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Integer id;

    @JoinColumn(name ="order_id", nullable = false)
    Integer orderId;

    // 商品のId 商品名や商品価格などを参照するときに使う。
    // 値上げなどで商品情報が変化したときに対応できなくなる問題がある
    @JoinColumn(name ="product_id", nullable = false)
    Integer productId;

    // 購入した商品の量
    @JoinColumn(name ="quantity", nullable = false)
    Integer quantity;

    public OrderItem(Integer orderId, Integer productId, Integer quantity) {
        this.orderId = orderId;
        this.productId = productId;
        this.quantity = quantity;
    }

    public OrderItem() {
    }
}

OrderItemRepository

OrderItemRepository.java
package com.example.restapi.domain.orderItem;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderItemRepository extends CrudRepository<OrderItem,Integer> {
    OrderItem save(OrderItem orderItem);

}


CartItem(Entity)

CartItem.java
package com.example.restapi.domain.cartItem;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;

@Entity
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Table(name = "cart_items")
public class CartItem{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Integer id;

    @JoinColumn(name ="customer_id")
    Integer customerId;

    @JoinColumn(name ="product_id")
    Integer productId;

    @JoinColumn(name ="quantity")
    Integer quantity;

    public CartItem(int customerId, int productId, int quantity) {
        this.customerId = customerId;
        this.productId = productId;
        this.quantity = quantity;
    }
    public CartItem(Integer id, int customerId, int productId, int quantity) {
        this.id = id;
        this.customerId = customerId;
        this.productId = productId;
        this.quantity = quantity;
    }
}


CartItemRepository

CartItemRepository.java
package com.example.restapi.domain.cartItem;

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CartItemRepository extends CrudRepository<CartItem, Integer> {
    public List<CartItem> findByCustomerId(Integer customerId);
    public void deleteByCustomerId(Integer customerId);
}

CartItemService

java_CartItemService.java
package com.example.restapi.domain.cartItem;

import com.example.restapi.implement.cartItem.CartItemDto;
import com.example.restapi.implement.cartItem.CartItemResponse;
import org.springframework.stereotype.Service;

import java.util.List;

// todo クラスのJavadocを追加します。
@Service
public interface CartItemService {
    // customerIdの顧客が買い物カゴの情報を取得します。
    public CartItemResponse listCartItems(Integer customerId);
    // customerIdの顧客の買い物かごを全て削除する。
    public void deleteByCustomer(Integer customerId);
}



CartItemServiceImplement

CartItemServiceImplement.java
package com.example.restapi.implement.cartItem;

import com.example.restapi.domain.cartItem.CartItem;
import com.example.restapi.domain.cartItem.CartItemRepository;
import com.example.restapi.domain.cartItem.CartItemService;
import com.example.restapi.domain.product.Product;
import com.example.restapi.domain.product.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class CartItemServiceImplement  implements CartItemService {

    @Autowired
    CartItemRepository cartItemRepository;

    @Autowired
    ProductRepository productRepository;

    @Override
    public CartItemResponse listCartItems(Integer customerId) {

        List<CartItem> cartItems = cartItemRepository.findByCustomerId(customerId);
        CartItemResponse cartItemResponse = new CartItemResponse();
        List<CartItemDto> cartItemDtos = new ArrayList<>() ;
        // setCartItemResponse
        for(var cartItem:cartItems){
            Product product = productRepository.getProductById(cartItem.getProductId());
            CartItemDto cartItemDto=new CartItemDto(cartItem.getId(),cartItem.getCustomerId(),cartItem.getProductId(),cartItem.getQuantity(),product.getName(),product.getPrice(),product.getPrice()*(1+product.getTaxRate()));
            cartItemDtos.add(cartItemDto);
        }
        cartItemResponse.setCartItemDtos(cartItemDtos);
        // cal productCost
        for(var cartItem :cartItems){
            float miniSum = cartItem.getQuantity()*productRepository.getProductById(cartItem.getProductId()).getPrice();
            cartItemResponse.setProductCost(cartItemResponse.getProductCost() + miniSum);
        }
        // cal shipping
        if( cartItemResponse.getProductCost() <= 4000f){
            cartItemResponse.setShippingCost(300);
        }else{
            cartItemResponse.setShippingCost(0);
        }

        // cal subtotal
        cartItemResponse.setSubTotal(cartItemResponse.getProductCost() + cartItemResponse.getShippingCost());

        // cal tax
        for(var cartItem :cartItems){
            float eachTax = cartItem.getQuantity()*productRepository.getProductById(cartItem.getProductId()).getPrice()*productRepository.getProductById(cartItem.getProductId()).getTaxRate();
            cartItemResponse.setTax(cartItemResponse.getTax() + eachTax);
        }
        // cal total;
        cartItemResponse.setTotal(cartItemResponse.getSubTotal() + cartItemResponse.getTax());
        return cartItemResponse;
//        return cartItemList.stream().map(cartItem -> mapToDTO(cartItem)).collect(Collectors.toList());
    }


    @Override
    public void deleteByCustomer(Integer customerId) {
        cartItemRepository.deleteByCustomerId(customerId);
    }

    private CartItemDto mapToDTO(CartItem cartItem){
        CartItemDto cartItemDto = new CartItemDto();
        cartItemDto.setId(cartItem.getId());
        cartItemDto.setProductId(cartItem.getProductId());
        cartItemDto.setCustomerId(cartItem.getCustomerId());
        cartItemDto.setQuantity(cartItem.getQuantity());
        return cartItemDto;
    }

    private CartItem mapToEntity(CartItemDto cartItemDto){
        CartItem cartItem = new CartItem();
        cartItemDto.setId(cartItem.getId());
        cartItemDto.setQuantity(cartItem.getQuantity());
        cartItemDto.setProductId(cartItem.getProductId());
        cartItemDto.setCustomerId(cartItem.getCustomerId());

        return cartItem;
    }
}



動かして確認

PUTメソッドで以下APIを叩きます。(POSTMANを使用)
以下画像から、決済情報が保存され顧客の買い物カゴが空になっていることを確認できます。
API実行後の各テーブルの変化
image.png

まだできていない箇所

とりあえず動けばよしでの実装になっております。
私が把握している限りでも以下の対策がとれていません。
1.商品の値段が変化したときにorderItemの値段も変化し、 orderItemとCartItemで金額があわなくなる。
  orderItemのパラメーターを修正する必要がある

2.着払い決済対応していない。
  現状だとクレカの場合しか想定していません。着払いできません。
  着払い対応にする場合はOrderItemService.finishOrderに分岐の処理を追加する必要があると考えます。

参考

github commit分

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?