PROJECT

AirDnS ํšŒ๊ณ  #2

๋ฆฐ์˜ˆ์กฐ 2024. 4. 12. 21:48

 

ํ† ์ŠคํŽ˜์ด๋จผ์ธ 

 

ํ† ์ŠคํŽ˜์ด๋จผ์ธ 

 

https://docs.tosspayments.com/guides/payment-widget/integration

 

์—ฐ๋™ํ•˜๊ธฐ | ํ† ์ŠคํŽ˜์ด๋จผ์ธ  ๊ฐœ๋ฐœ์ž์„ผํ„ฐ

ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์˜ ๊ฐ„ํŽธํ•œ ๊ฒฐ์ œ ์—ฐ๋™ ๊ณผ์ •์„ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…๊ณผ ํ•จ๊ป˜ ๋‹ฌ๋ผ์ง€๋Š” UI์™€ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.

docs.tosspayments.com

ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒฐ์ œ flow๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

์ฃผ์š” ์ •๋ณด ๋ช‡๊ฐ€์ง€๋งŒ ์บก์ณํ•ด์„œ ๋„ฃ์—ˆ์ง€๋งŒ ์•„๋ž˜ ์ด๋ฏธ์ง€์—์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ

ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์—์„œ๋Š” ๊ฐœ๋ฐœํ•˜๊ธฐ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ƒ์„ธํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค. 

 

๊ฐ„ํŽธ๊ฒฐ์ œ๋ฅผ ์ ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์šฐ์„  ๋ณธ์ธ์ด ์›ํ•˜๋Š” ํ”Œ๋žซํผ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒฐ์ œํ๋ฆ„์ด๋‚˜ ์ ์šฉ ๋ฐฉ์‹์— ๋Œ€ํ•ด ๋จผ์ € ์ƒ์„ธํ•˜๊ฒŒ ๊ณต๋ถ€ํ•˜๊ธฐ๋ฅผ ์ถ”์ฒœํ•œ๋‹ค.

๋‚˜๋Š” ์‚ฌ์ „์— ๋งŽ์€ ์ •๋ณด๋ฅผ ์ฐพ์•„๋ณด๊ณ  ํ† ์ŠคํŽ˜์ด๋จผ์ธ  flow๋ฅผ ์ดํ•ดํ–ˆ๋‹ค! ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ง‰์ƒ ๊ฐœ๋ฐœํ•  ๋•Œ ๋งŽ์€ ์• ๋ฅผ ๋จน์—ˆ๋‹ค.. 

   

 

 

์•„์ฃผ! ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•ด๋ณด๋ฉด

๊ฒฐ์ œ ์œ„์ ฏ์„ ํ™”๋ฉด์— ๋„์šฐ๊ณ  ๊ฒฐ์ œ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•œ ํ›„ ์š”์ฒญ์„ ํ•œ๋‹ค.

ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ์Šน์ธํ•œ ํ›„์— ๊ฒฐ์ œ์— ์„ฑ๊ณตํ•˜๋ฉด ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฟผ๋ฆฌ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•ด์ค€๋‹ค.

์ฟผ๋ฆฌ์— ๋‹ด๊ธด ๊ฒฐ์ œ๋ฅผ ๊ตฌ๋ถ„ํ•  payment key, orderId, ๊ธˆ์•ก๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๋ฝ‘์•„์„œ ๋ฐฑ์—”๋“œ๋กœ ๋„˜๊ธด๋‹ค.

๋ฐฑ์—”๋“œ๋Š” ํ•ด๋‹น ์ •๋ณด๋ฅผ DB์— ์ €์žฅํ•œ๋‹ค.

 

 

https://github.com/tosspayments/payment-widget-sample

 

GitHub - tosspayments/payment-widget-sample: ํ† ์ŠคํŽ˜์ด๋จผ์ธ  ๊ฒฐ์ œ์œ„์ ฏ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค.

ํ† ์ŠคํŽ˜์ด๋จผ์ธ  ๊ฒฐ์ œ์œ„์ ฏ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. . Contribute to tosspayments/payment-widget-sample development by creating an account on GitHub.

github.com

 

-> ์œ„ ๋งํฌ์— ์ ‘์†ํ•˜๋ฉด ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ์–ธ์–ด๋กœ ๊ฐœ๋ฐœ๋œ ๊ฒฐ์ œ์œ„์ ฏ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 


 

<template>
  <div class="wrapper">
    <div class="box_section">
      <!-- ๊ฒฐ์ œ UI -->
      <div id="payment-method"></div>
      <!-- ์ด์šฉ์•ฝ๊ด€ UI -->
      <div id="agreement"></div>
      <!-- ๊ฒฐ์ œํ•˜๊ธฐ ๋ฒ„ํŠผ -->
      <div class="result-wrapper">
        <button @click="requestPayment" class="button" id="payment-button" style="margin-top: 30px">
          ๊ฒฐ์ œํ•˜๊ธฐ
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import {loadPaymentWidget, ANONYMOUS} from "@tosspayments/payment-widget-sdk";
import {nanoid} from "nanoid";

export default {
  data() {
    return {
      data: null,
      paymentWidget: null,
      paymentMethodWidget: null,
      clientKey: "test_ck_Ba5PzR0ArnWdaxZloyQ18vmYnNeD",
      customerKey: nanoid(),
      amount: null,
    };
  },
  methods: {
    async requestPayment() {
      try {
        if (this.paymentWidget) {
          var regex = /[^0-9]/g;
          var result = this.data.userContact.replace(regex, "");
          await this.paymentWidget.requestPayment({
            orderId: nanoid(),
            orderName: this.data.reservationName,
            customerName: this.data.userName,
            customerEmail: this.data.userEmail,
            reservationId: this.data.id,
            customerMobilePhone: result,
            successUrl: `${window.location.origin}/success?orderName=${encodeURIComponent(
                this.data.reservationName)}&reservationId=${this.data.id}`,
            failUrl: `${window.location.origin}/fail`,
          });
        }
      } catch (error) {
        console.error(error);
      }
    },
  },
  async mounted() {
    this.paymentWidget = await loadPaymentWidget(this.clientKey, ANONYMOUS);
    this.paymentMethodWidget = this.paymentWidget.renderPaymentMethods("#payment-method",
        {value: this.amount}, {variantKey: "DEFAULT"});
    this.paymentWidget.renderAgreement("#agreement", {variantKey: "AGREEMENT"});
  },
  created() {
    const {data} = history.state;
    this.data = data;
    this.amount = this.data.price
  }
};
</script>
<style>
.box_section {
  width: 650px;
  background-color: white;
  border-radius: 10px;
  box-shadow: 0 10px 20px rgb(0 0 0 / 1%), 0 6px 6px rgb(0 0 0 / 6%);
  padding: 40px 30px 50px 30px;
  margin: auto;
  margin-top: 100px;
  color: #333D4B;
}
.button{
  color: #f9fafb;
  background-color: #3182f6;
  margin: 0;
  font-size: 15px;
  font-weight: 400;
  line-height: 18px;
  white-space: nowrap;
  text-align: center;
  cursor: pointer;
  border: 0 solid transparent;
  user-select: none;
  transition: background 0.2s ease, color 0.1s ease;
  text-decoration: none;
  border-radius: 7px;
  padding: 10px 13px;
}

.result-wrapper{
  display: flex;
  flex-direction: column;
  align-items: center;
}
</style>

 

 

์šฐ๋ฆฌ๋Š” ๊ฒฐ์ œํ™”๋ฉด์„ Vue๋กœ ๋„์› ๋‹ค. 

ํ•ด๋‹น ์ฝ”๋“œ๋กœ ๊ฒฐ์ œ ์œ„์ ฏ์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์ž…๋ ฅํ•œ ๊ฒฐ์ œ ์ •๋ณด๋ฅผ ๋ฐฑ์—”๋“œ๋กœ ๋„˜๊ธฐ๋ฉด 

์„œ๋ฒ„์—์„œ ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์ธก์— ๊ฒฐ์ œ ์Šน์ธ์„ ์š”์ฒญํ•œ๋‹ค. 

 

์œ„ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ๊นŒ์ง€๋„ ์ฐธ ๋งŽ์€ ๊ณผ์ •์ด ์žˆ์—ˆ๋‹ค.

Vue์˜ ๊ฐ€์žฅ ์ตœ๊ทผ ๋ฒ„์ „์ด Vue3 ๋ผ๊ณ  ์•Œ๊ณ  ๊ทธ๊ฑธ ๊ธฐ์ค€์œผ๋กœ webpack์œผ๋กœ ์„ค์น˜๊นŒ์ง€ ์™„๋ฃŒํ–ˆ๋Š”๋ฐ

ํ† ์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒŒ vite ๋ฒ„์ „์ด๋ผ ํ”„๋กœ์ ํŠธ ์„ค์ •์„ ์•„์˜ˆ ๋ฐ”๊ฟ”์•ผํ•˜๋‚˜ ์‹ถ์–ด์„œ ๋ฐ”๊พธ๋ ค๊ณ  ์‚ฝ์งˆํ•˜๋‹ค๊ฐ€ 

์‚ญ์ œ ์žฌ์„ค์น˜ ์‚ญ์ œ ์žฌ์„ค์น˜ ๋ฐ˜๋ณตํ•˜๋‹ค ๋‹ค์‹œ webpack์œผ๋กœ ๋‘๊ณ  ์ฝ”๋“œ๋ฅผ ๋œฏ์–ด๋ณด๋‹ˆ ์„ค์ • ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ

์ฝ”๋“œ ๋ฌธ์ œ๋ผ๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ˆ˜์ •ํ•ด์„œ ๊ฒฐ๊ตญ ํ•ด๊ฒฐํ•œ ๊ฒฝํ—˜.. 

์•„๋ฌด๋ž˜๋„ ํ•ด๋‹น ์–ธ์–ด, ํŒŒ์ผ๊ตฌ์กฐ๊ฐ€ ์ต์ˆ™ํ•˜์ง€ ์•Š์•„์„œ ์‚ฌ์†Œํ•œ ๋ฌธ์ œ๊ฐ€ ๊ณ„์† ๋ฐœ์ƒํ–ˆ๋‹ค..

   

public class PaymentController {

    private final PaymentServiceImplV1 paymentService;

    @PostMapping("reservation/{reservationId}/payment")
    @Operation(summary = "๊ฒฐ์ œ ์š”์ฒญ", description = "์˜ˆ์•ฝ ๊ฑด์— ๋Œ€ํ•ด ๊ฒฐ์ œ ์š”์ฒญ์„ ํ•œ๋‹ค.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "๊ฒฐ์ œ ์„ฑ๊ณต"),
            @ApiResponse(responseCode = "400", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์˜ˆ์•ฝ์ž…๋‹ˆ๋‹ค."),
    })
    public ResponseEntity<CommonResponse<PaymentResponseDto.CreatePaymentResponseDto>> createPayment(
            @AuthenticationPrincipal UserDetailsImpl userDetails,
            @PathVariable Long reservationId,
            @RequestBody PaymentRequestDto.CreatePaymentRequestDto requestDto) {

        PaymentResponseDto.CreatePaymentResponseDto paymentResponseDto = paymentService.createPayment(
                userDetails.getUser().getId(),
                reservationId,
                requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(
                new CommonResponse<>(
                        HttpStatus.CREATED,
                        "๊ฒฐ์ œ ์„ฑ๊ณต",
                        paymentResponseDto
                )
        );

    }

 


 

์šฐ๋ฆฌ๋Š” ์˜ˆ์•ฝ ์ •๋ณด๋ฅผ ํฌํ•จํ•ด์„œ ๋„˜๊ธฐ๊ณ  ์˜ˆ์•ฝ์ •๋ณด๊ฐ€ ์ผ์น˜ํ•  ๊ฒฝ์šฐ์—๋งŒ ๊ฒฐ์ œ ์š”์ฒญ์„ ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ฒฐ์ œ ์Šน์ธ์ด ๋–จ์–ด์ง€๋ฉด ํ† ์ŠคํŽ˜์ด๋จผ์ธ  ์ธก์—์„œ ํด๋ผ์ด์–ธํŠธ์— ์„ฑ๊ณต URL์„ ๋„˜๊ฒจ์ฃผ๊ณ  ํด๋ผ์ด์–ธํŠธ๋Š” ๋ฐ›์•„์˜จ ์ •๋ณด๋ฅผ ๋‹ค์‹œ ์„œ๋ฒ„์— ๋„˜๊ฒจ

DB์— ์ €์žฅ๋œ๋‹ค.

 

    private PaymentResponseDto.CreatePaymentResponseDto handleSuccessfulResponse(
            PaymentRequestDto.CreatePaymentRequestDto requestDto
            , Reservation reservation) {
        Payment payment = Payment.builder()
                .orderId(requestDto.getOrderId())
                .orderName(requestDto.getOrderName())
                .amount(requestDto.getAmount())
                .paymentKey(requestDto.getPaymentKey())
                .paymentType(requestDto.getPaymentType())
                .reservation(reservation)
                .build();
        paymentRepository.save(payment);
        return PaymentResponseDto.CreatePaymentResponseDto.from(payment);
    }



 

์„œ๋ฒ„์—์„œ ํ† ์ŠคํŽ˜์ด๋จผ์ธ  ์ธก์œผ๋กœ ๊ฒฐ์ œ์š”์ฒญ์„ ํ•˜๋Š” ์ฝ”๋“œ๋Š” ์ƒ๋‹จ์˜ Github ๋งํฌ์—์„œ ์ฐธ๊ณ ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹๋‹ค.

 

๋‚˜๋„ ์ œ๊ณต๋œ ์ฝ”๋“œ๋ฅผ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ปค์Šคํ…€ํ•ด์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

 

 

์šฐ์—ฌ๊ณก์ ˆ ๋์— ๊ฒฐ๊ตญ ์™„์„ฑํ•˜๊ธด ํ–ˆ๋‹ค.

๋‹ค๋งŒ, ์‹œ๊ฐ„ ๊ด€๊ณ„์ƒ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•œ ๋ถ€๋ถ„๋“ค์ด ๊ฝค๋‚˜ ์žˆ์–ด ์ƒ๋‹นํžˆ ์•„์‰ฝ๋‹ค.

Vue.js๋ฅผ ์ฒ˜์Œ ๋‹ค๋ค„๋ณด๋Š” ๊ฑฐ๋ผ ์ด ๋ถ€๋ถ„์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•˜๊ฒŒ ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋๋‹ค.

 

ํ”„๋ก ํŠธ๋ž‘ ๋ฐฑ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒฝํ—˜ ์ž์ฒด๊ฐ€ ์ฒ˜์Œ์ด๋ผ ์ •๋ง ์• ๋ฅผ ๋จน์—ˆ๋‹ค..

๊ฒฐ์ œ์ •๋ณด๊ฐ€ DB์— ์•ˆ ์Œ“์ด๋Š”๊ฒŒ ๊ฐ€์žฅ ํฐ ๋‚œ๊ด€์ด์—ˆ๋Š”๋ฐ ์–ด์ฒ˜๊ตฌ๋‹ˆ ์—†์ด ์ฝ”๋“œ ํ•œ ์ค„๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๋Š” ๋ฌธ์ œ์˜€๋‹ค.. (์ฝ”๋“œ๋ฅผ ๊ผผ๊ผผํ•˜๊ฒŒ ์‚ดํ•์‹œ๋‹ค ..)   

๊ทธ๋ฆฌ๊ณ  DB ์—ฐ๊ฒฐ๋œ ๊ฒƒ ํ™•์ธํ–ˆ๋Š”๋ฐ ๋ฐฐํฌํ•˜๊ณ  ๋‚˜์„œ ๊ฐ‘์ž๊ธฐ ๋˜ ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ..  (์ด๊ฒƒ ๋˜ํ•œ ์ฃผ์†Œ ์˜คํƒ€ ๋ฌธ์ œ.. ๊ผผ๊ผผํ•˜๊ฒŒ ํ™•์ธํ•ฉ์‹œ๋‹ค..)

๋ฐฐํฌํ•˜๊ณ  ๋‚˜์„œ๋Š” ๋กœ์ปฌ, ๋ฐฐํฌ ์ฃผ์†Œ ์™”๋‹ค ๊ฐ”๋‹ค ํ•˜๋ฉด์„œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ–ˆ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ๊ผผ๊ผผํ•˜๊ฒŒ ํ™•์ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฐ•์‚ด๋‚œ๋‹ค๋Š”๊ฑธ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค..

 

์šฐ๋ฆฌ๊ฐ€ ์‹œ๊ฐ„์— ์ซ“๊ฒจ ํ”„๋ก ํŠธ ๋ถ€๋ถ„์€ ์ฝ”๋“œ์ปจ๋ฒค์…˜์„ ์ œ๋Œ€๋กœ ์ง€ํ‚ค์ง€ ๋ชปํ–ˆ๋Š”๋ฐ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์„ ํ†ตํ•ด

ํ˜‘์—…์—์„œ ์ฝ”๋“œ์ปจ๋ฒค์…˜, github์„ ์ž˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ํ•„์ˆ˜์ ์ด๊ณ  ์ค‘์š”ํ•œ ๊ฒƒ์ธ์ง€ ๊นจ๋‹ฌ์•˜๋‹ค.

 

 

 

'PROJECT' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

AirDnS ํšŒ๊ณ  #1  (0) 2024.04.11
Spring PROJECT  (5) 2023.12.26