티스토리 뷰

 

프로젝트 [만원의 행복] 개발 중에 이미지 저장하는 DB가 아직 구축이 되지 않아서,

일단 MySql에 이미지로 저장하고 프론트엔드한테는 endpoint api로 전달하기로 했다. 

 

✔️ 결제 처리 로직 

1) 짠처리 상품 구매 

2) 서버 : 결제 API 처리 

3) 서버 : QR code 발급

 

1) 짠처리 상품 사용하고 싶음

2) 업주 : QR code 찍음

3) 서버 : QR code 사용처리 

4) 상품 제공

 

 

✔️ ERD 는 다음과 같다.  (아직 수정 중이고 많이 부족하다ㅜ)

 

 

📌 QR image Entity (QR code 이미지 저장 entity)

일단 QR 코드 (이미지) 를 저장할 수 있는 entity는 다음과 같다. 

이미지는 byte 타입으로 저장해야하므로, byte[] 로 선언한다. 

 

✔️ @Lob @Column(name = "qr_image", columnDefinition = "MEDIUMBLOB")

해줘야 긴 url도 큐알로 저장이 가능함. 

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class QrImage {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "qr_image_id")
    @Comment("QR Image id")
    private Long id;

    @Lob
    @Column(name = "qr_image", columnDefinition = "MEDIUMBLOB")
    @Comment("QR 이미지")
    private byte[] qrImage;
}

 

 

📌 QR code 생성하는 방법 

출처 : https://isshosng.tistory.com/131

 

1. 의존성 

대부분의 QR 코드는 구글에서 나온 api를 사용한다. 

// QR CODE 관련
implementation group: 'com.google.zxing', name: 'javase', version: '3.5.0'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.0'

 

 

2. QR Util

1) QR 코드 생성할 수 있는 로직 

2) QR 코드 이미지 얻을 수 있는 api -> DB에 저장

@Slf4j
public class QRCodeUtil {

    /**
     * 주어진 링크를 인코딩하여 QR 코드 이미지를 생성하고,
     * 그 이미지를 byte 배열 형태로 반환하는 메서드
     * @param link
     * @return QR 코드 이미지를 바이트 배열 형태로 변환
     * @throws DefaultException
     */
    public static byte[] generateQRCodeImage(String link) {
        try {
            int width = 200, height = 200;

            // QR코드 생성 옵션 설정
            Map<EncodeHintType, Object> hintMap = new HashMap<>();
            hintMap.put(EncodeHintType.MARGIN, 0);
            hintMap.put(EncodeHintType.CHARACTER_SET,"UTF-8");

            // QR 코드 생성
            QRCodeWriter qrCodeWriter = new QRCodeWriter();
            BitMatrix bitMatrix = qrCodeWriter.encode(link, BarcodeFormat.QR_CODE, width, height, hintMap);

            // QR 코드 이미지 생성
            BufferedImage qrCodeImage = MatrixToImageWriter.toBufferedImage(bitMatrix);

            // QR 코드 이미지를 바이트 배열로 변환, byteArrayOutputStream 에 저장
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(qrCodeImage,"png", byteArrayOutputStream);
            byteArrayOutputStream.flush();

            byte[] qrCodeBytes = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.close();

            return qrCodeBytes;

        } catch (Exception e) {
            log.info("QRCode Error");
            throw new QrCodeException(FAILED_CREATE_QR);
        }
    }

    /**
     * 큐알 코드 이미지 얻을 수 있는 api endpoint를 담은 url 반환
     * @param qrImage
     * @return
     */
    public static String getQrImageURL(QrImage qrImage) {
        return "http://localhost:8080/api/zzan-items/qr-code-images/" + qrImage.getId();
    }

}

 

 

 

3. 결제 기록 테이블에 QR 이미지 링크 삽입 

 

📝 PurchaseHistory : 결제 기록 테이블 -> 결제 후 QR 코드가 생성되면, 테이블에 저장

public class PurchaseHistory {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "purchase_history_id")
    @Comment("구매 기록 ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    @Comment("구매자")
    private Member member;

    @Comment("짠처리 아이템 FK")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "zzan_item_id")
    private ZzanItem zzanItem;

    @Comment("구매 가능한 QR 코드 id")
    @JoinColumn(name = "qr_image_id")
    @OneToOne
    @Setter
    private QrImage qrImage;

    @Comment("QR 사용 여부")
    @Enumerated(EnumType.STRING)
    @Setter
    private PurchaseStatusType status;

    @Comment("구매 날짜 및 시간")
    private LocalDateTime createdAt;

    @Comment("QR 사용 날짜 및 시간")
    @Setter
    private LocalDateTime usedAt;

    @Comment("구매 가격")
    private Integer price;

}

 

 

📝 생성된 QR를 저장할 수 있는 로직

QR 코드 목적 : 주문 기록의 status 를 사용 완료로 표기 (카카오톡 기프티콘 같은 원리)

즉, 결제는 완료한 상품을 실제로 사용하기 위해서 필요한 QR 코드이다. 

QR 코드에는 결제한 상품을 사용완료로 변경할 수 있는 api endpoint를 link로 만들어서 들어간다. 

현재 배포를 하지 않은 상태이므로, 로컬로 만들어줬다. 

 

▶️ QR에는 purchase_Id 가 필수로 들어간다. 

▶️ ▶️ 그래서 purchase 객체를 미리 save()한 후에, setQrImage(qrImage)를 해서 또 save() 해줘야한다. (save 2번)

 

    private static final String useLink = "http://localhost:8080/api/zzan-items/use/";
    
    private PurchaseHistory saveQrImageInPurchaseHistory(PurchaseHistory ph) {
        StringBuilder link = new StringBuilder(useLink).append(ph.getId());

        QrImage qrImage = createQrImage(link.toString());
        ph.setQrImage(qrImage);

        PurchaseHistory savePh = purchaseHistoryRepository.save(ph);
        if (savePh == null) {
            throw new PurchaseException(PurchaseExceptionCode.FAILED_PURCHASE);
        }
        return savePh;
    }

 

 

4. API

 

1) 주문 처리 api

@RestController
@RequestMapping("/api/zzan-items")
@RequiredArgsConstructor
public class PurchaseController {

    private final PurchaseService purchaseService;

    @GetMapping("/{zzanItemId}/purchase")
    public ResponseEntity<DataBody<PurchaseResultResponse>> purchase(
            @PathVariable final Long zzanItemId
    ) {
        return ResponseDTO.created(
                purchaseService.purchase(zzanItemId),
                "주문 처리 완료"
        );
    }

 

 

2) QR 이미지 반환 api

[GET] 방식으로 해줘야한다. 

    /** QR code Image 반환 **/
    @GetMapping("/qr-code-images/{qrId}")
    public ResponseEntity<byte[]> getQRCodeImage(@PathVariable Long qrId) {
        // 이미지 데이터와 헤더 같이 반환
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.IMAGE_PNG);
        return ResponseEntity.ok().headers(headers).body(
                purchaseService.getQRCodeImage(qrId)
        );
    }

 

 

▶️ 서비스에서는

    @Transactional(readOnly = true)
    public byte[] getQRCodeImage(Long qrId) {
        return qrImageRepository.findById(qrId)
                .orElseThrow(() -> new QrCodeException(NOT_FOUND_QRCODE_IMAGE))
                .getQrImage();
    }

 

 

 

3) QR 코드의 링크 들어갔을 때 api 

[GET] 방식으로 해줘야 한다. 

    @GetMapping("/use/{purchaseId}")
    public ResponseEntity<DataBody<Void>> useQrCode(
            @PathVariable final Long purchaseId
    ) {
        purchaseService.usePurchase(purchaseId);
        return ResponseDTO.ok("QR 사용 완료");
    }

 

 

 

🌟 실행 결과 

 

1. zzan-item 의 139번 id를 가진 상품을 결제하면, 다음과 같은 응답을 받는다. 

usedTime : 사용 완료 시점 (결제 직후 이므로, null 로 반환)

 

 

2. 빨간 박스 안에 있는 이미지를 클릭하면, 다음과 같은 QR 이미지를 받는다. 

 

 

3. 해당 큐알을 카메라로 인식하고, url를 타고 들어가면 다음과 같은 응답을 받고, 결과가 갱신된다. 

 

4. 데이터베이스에서는 status 가 To Be Use -> In Use 로 변경된다!