+ 00 00 0000

Have any Questions?

๊ฒฐ์žฌ API sample

๊ฒฐ์žฌ API sample

์‰ฌ์šด ๋ชฉ์ฐจ

๐Ÿ“ƒ ์š”์•ฝ

ํ† ์Šค ๊ฒฐ์žฌ API ๋ฅผ ์ด์šฉํ•ด์„œ ์‡ผํ•‘๋ชฐ ๊ฒฐ์žฌ ๋ถ€๋ถ„ ์ƒ˜ํ”Œ์„ ์ง„ํ–‰ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
์ฃผ๋ฌธ๋ฒˆํ˜ธ๋ฅผ ํ† ์Šค ๊ฒฐ์žฌ API ๋กœ ์ „๋‹ฌํ•˜๊ณ  ํ† ์Šค ๊ฒฐ์žฌ์—์„œ ๊ฒฐ์žฌ ์ง„ํ–‰ ํ›„์— ๊ฐ„๋žตํ•œ ๊ฒฐ๊ณผ๋ฅผ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ด ๋ณด๊ฒŸ์Šต๋‹ˆ๋‹ค.

๊ฒฐ์žฌ ์ง„ํ–‰ํ›„์— ๊ฒฐ์žฌ ํ…Œ์ด๋ธ”์— ๊ฒฐ์žฌ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
๋‹จ, ์ฃผ๋ฌธ์ •๋ณดํ…Œ์ด๋ธ”์— ์ฃผ๋ฌธ์ƒํƒœ(์ฃผ๋ฌธ์™„๋ฃŒ->๊ฒฐ์žฌ์™„๋ฃŒ) ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋‚˜ ๊ฒฐ์žฌ API ํ™œ์šฉ๋ฐฉ๋ฒ•์— ์ง‘์ค‘ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ๋žตํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ๋Š” Vue ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋ฒก์—”๋“œ๋Š” spring boot ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

DB ๋Š” ์˜ค๋ผํด ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ณ„์ •์€ scott ( ์•”ํ˜ธ : !Ds1234567890 ) ๊ฐœ๋ฐœ์ž ๊ณ„์ •์„ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
DB ๊ฐœ๋ฐœ์ž ๊ณ„์ • ๋ฐ ์„ค์น˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ƒ๋žตํ•ฉ๋‹ˆ๋‹ค.

์š”์†Œ ๊ธฐ์ˆ  :

– ํ”„๋ก ํŠธ์—”๋“œ : Vue

– ๋ฒก์—”๋“œ : ์Šคํ”„๋ง๋ถ€ํŠธ & JPA

– DB : Oracle 18xe(Docker)
( Oracle 18xe ๋„์ปค ์ด๋ฏธ์ง€ ์ฃผ์†Œ : https://hub.docker.com/r/kangtaegyung/oraclexe-18c )

๊ฒฐ๊ณผ ํ™”๋ฉด :

  • ๊ฒฐ์žฌ ๋ฒ„ํŠผ ํ™”๋ฉด
  • ํ† ์Šค ๊ฒฐ์žฌ ํ™”๋ฉด #1 : ์‹ ์šฉ์นด๋“œ ์„ ํƒ
  • ํ† ์Šค ๊ฒฐ์žฌ ํ™”๋ฉด #2 : ์•ฑ์นด๋“œ ๊ฒฐ์žฌ
  • ํ† ์Šค ๊ฒฐ์žฌ ํ™”๋ฉด #3 : ์•ฑ์นด๋“œ ๊ฒฐ์žฌ ์ง„ํ–‰
  • ํ† ์Šค ๊ฒฐ์žฌ ์™„๋ฃŒ #4
  • ๊ฒฐ์žฌ ์™„๋ฃŒ ํ›„ ๊ฒฐ์žฌ ํ…Œ์ด๋ธ”์— ์ €์žฅ๋จ

ํ”„๋กœ์ ํŠธ ํƒ์ƒ‰๊ธฐ : Vue

ํ”„๋กœ์ ํŠธ ํƒ์ƒ‰๊ธฐ : String Boot

๋ฒก์—”๋“œ Rest API ์ •๋ณด :

๋ฉ”์†Œ๋“œURL์„ค๋ช…
POST/api/shop/simple-approval๊ฒฐ์žฌ ์ €์žฅ

๐Ÿ“ƒ ๊ธฐ์ˆ  ๊ตฌํ˜„

์ŠคํŽ™ :

- Vue 3.x
- toss ๊ฒฐ์žฌ API ( ์ฐธ์กฐ : https://github.com/tosspayments/payment-widget-sample )
- jdk 17
- spring boot 3.x
- intellij IDEA & gradle
- logging tool : logback 

ํ…Œ์ด๋ธ” ์„ค๊ณ„

-- Table , ์‹œํ€€์Šค ๋“ฑ ๊ตฌ์กฐ ์ •์˜
DROP SEQUENCE SQ_SIMPLE_APPROVAL;
CREATE SEQUENCE SQ_SIMPLE_APPROVAL START WITH 1 INCREMENT BY 1;

DROP TABLE TB_SIMPLE_APPROVAL CASCADE CONSTRAINT;

-- ๊ฒฐ์žฌ ์ •๋ณด ํ…Œ์ด๋ธ”
CREATE TABLE TB_SIMPLE_APPROVAL
(
    SANO            NUMBER NOT NULL PRIMARY KEY,       -- ๊ฒฐ์žฌ๋ฒˆํ˜ธ(PK), ์‹œํ€€์Šค
    APPROVAL_DATE   VARCHAR2(1000) ,                   -- ๊ฒฐ์žฌ์ผ์ž : YYYY-MM-DD HH24:MI:SS
    APPROVAL_AMOUNT NUMBER NOT NULL
);

๊ฒฐ์žฌ API ๊ตฌํ˜„์„ ์œ„ํ•œ ํ…Œ์ด๋ธ” ์„ค๊ณ„์ž…๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์˜ค๋ผํด(Docker)์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Spring build.gradle : dependencies ๋ธ”๋Ÿญ ๋‚ด ์ถ”๊ฐ€

dependencies {
    //    ์˜ค๋ผํด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ( 19c )
    implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
    implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
    implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
    implementation 'com.oracle.database.security:osdt_core:19.14.0.0'

    //    logback , log4jdbc ์„ค์ •
    implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
        ... ์ƒ๋žต
}

๊ฒฐ์žฌ ์—”ํ‹ฐํ‹ฐ

– SimpleApproval.java

package com.example.simpledms.model.entity.shop;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;


/**
 * @fileName : SimpleApproval
 * @author : kangtaegyung
 * @since : 11/22/23
 * description : ๊ฒฐ์žฌ
 */
@Entity
@Table(name = "TB_SIMPLE_APPROVAL")
@SequenceGenerator(
        name = "SQ_SIMPLE_APPROVAL_GENERATOR"
        , sequenceName = "SQ_SIMPLE_APPROVAL"
        , initialValue = 1
        , allocationSize = 1
)
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class SimpleApproval {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            , generator = "SQ_SIMPLE_APPROVAL_GENERATOR"
    )
    private Integer sano;             // ๊ฒฐ์žฌ๋ฒˆํ˜ธ, ๊ธฐ๋ณธํ‚ค, ์‹œํ€€์Šค
    private String  approvalDate;     // ๊ฒฐ์žฌ์ผ
    private Integer approvalAmount;   // ๊ฒฐ์žฌ๊ธˆ์•ก

}

๊ฒฐ์žฌ ๋ ˆํฌ์ง€ํ† ๋ฆฌ

– SimpleApproval.java

package com.example.simpledms.repository.shop;

import com.example.simpledms.model.entity.shop.SimpleApproval;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * @fileName : SimpleApprovalRepository
 * @author : kangtaegyung
 * @since : 11/22/23
 * description :  ์ฃผ๋ฌธ ๋ ˆํฌ์ง€ํ† ๋ฆฌ
 */
@Repository
public interface SimpleApprovalRepository extends JpaRepository<SimpleApproval, Integer> {
}

๊ฒฐ์žฌ ์„œ๋น„์Šค

– SimpleApprovalService.java

package com.example.simpledms.service.shop;

import com.example.simpledms.model.entity.shop.SimpleApproval;
import com.example.simpledms.repository.shop.SimpleApprovalRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @fileName : SimpleApprovalService
 * @author : kangtaegyung
 * @since : 11/22/23
 * description :  ๊ฒฐ์žฌ ์„œ๋น„์Šค
 */
@Service
@RequiredArgsConstructor
public class SimpleApprovalService {

    private final SimpleApprovalRepository simpleApprovalRepository;

    public void save(SimpleApproval simpleApproval) {
        simpleApprovalRepository.save(simpleApproval);
    }
}

๊ฒฐ์žฌ ์ปจํŠธ๋กค๋Ÿฌ

– SimpleApprovalController.java

package com.example.simpledms.controller.shop;

import com.example.simpledms.model.entity.shop.SimpleApproval;
import com.example.simpledms.service.shop.SimpleApprovalService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
 * @fileName : SimpleApprovalController
 * @author : kangtaegyung
 * @since : 11/22/23
 * description : ๊ฒฐ์žฌ ์ปจํŠธ๋กค๋Ÿฌ
 */
@Slf4j
@RestController
@RequestMapping("/api/shop")
@RequiredArgsConstructor
public class SimpleApprovalController {

    private final SimpleApprovalService simpleApprovalService;

    @PostMapping("/simple-approval")
    public ResponseEntity<Object> create(@RequestBody SimpleApproval simpleApproval) {

            simpleApprovalService.save(simpleApproval);

            return new ResponseEntity<>(HttpStatus.OK);
    }
}

๊ณตํ†ต Exception ย ํด๋ž˜์Šค

– CommonExceptionAdvice.java

  • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์–ด๋–ค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ด ํด๋ž˜์Šค์˜ internalServerErrorException ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จ
package com.example.simpledms.exceptions;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author : kangtaegyung
 * @fileName : ExControllerAdvice
 * @since : 24. 5. 27.
 * description : ๊ณตํ†ต๋œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
 *  @ResponseStatus(HttpStatus.์ƒํƒœ์ฝ”๋“œ) : ์ƒํƒœ์ฝ”๋“œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด
 *  @ExceptionHandler(Exception.class) : Exception ์˜ˆ์™ธ ํด๋ž˜์Šค์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌํ•œ๋‹ค.
 */
@Slf4j
@RestControllerAdvice
public class CommonExceptionAdvice {

//  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์–ด๋–ค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ด ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จ
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> internalServerErrorException(Exception e) {
        log.debug("๋ฒก์—”๋“œ ์—๋Ÿฌ: " + e.getMessage());

        return new ResponseEntity<>("๋ฒก์—”๋“œ ์—๋Ÿฌ: " + e.getMessage()
                , HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Vue ํŽ˜์ด์ง€

– Vue ํŒจํ‚ค์ง€ ์ถ”๊ฐ€ :

npm i @tosspayments/payment-widget-sdk
npm i axios

1) ๊ณตํ†ต js

– utils/axiosDefaultConfig.js : axios ๊ธฐ๋ณธ ์„ค์ • ํŒŒ์ผ

import axios from "axios";

// axios ๊ธฐ๋ณธ ์„ค์ •
export default axios.create({
  baseURL: "http://localhost:8000/api",
  headers: {
    "Content-Type": "application/json"
  }
});

– services/shop/SimpleApprovalService.js

import axiosDefault from "@/utils/axiosDefaultConfig";

class SimpleApprovalService {

  create(data) {
    return axiosDefault.post("/shop/simple-approval", data);
  }
}

export default new SimpleApprovalService();

– router/index.js

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: "/",
    name: "home",
    component: () => import("../views/HomeView.vue"),
  },
  // ๊ฒฐ์žฌ ๋ฒ„ํŠผ ๋ฉ”๋‰ด
  {
    path: "/simple-order",
    name: "simple-order",
    component: () => import("@/views/shop/simple-product/SimpleOrderList.vue"),
  },
  // ํ† ์Šค ๊ฒฐ์žฌ
  {
    path: '/toss-check/:sono',
    component: () => import('@/views/shop/toss/CheckoutView.vue')
  },
  {
    path: '/success',
    component: () => import('@/views/shop/toss/SuccessView.vue')
  },
  {
    path: '/fail',
    component: () => import('@/views/shop/toss/FailView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

ํ† ์Šค ๊ฒฐ์žฌ API ํ•จ์ˆ˜ : ํ† ์Šค ๊ฒฐ์ œ ์‚ฌ์ดํŠธ์—์„œ ์ œ๊ณต

– utils/confirmPayment.js

export async function confirmPayment(requestData) {
    // TODO: ๊ฐœ๋ฐœ์ž์„ผํ„ฐ์— ๋กœ๊ทธ์ธํ•ด์„œ ๋‚ด ๊ฒฐ์ œ์œ„์ ฏ ์—ฐ๋™ ํ‚ค > ์‹œํฌ๋ฆฟ ํ‚ค๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. ์‹œํฌ๋ฆฟ ํ‚ค๋Š” ์™ธ๋ถ€์— ๊ณต๊ฐœ๋˜๋ฉด ์•ˆ๋ผ์š”.
    // @docs https://docs.tosspayments.com/reference/using-api/api-keys
    const secretKey = "test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6";

    // ํ† ์ŠคํŽ˜์ด๋จผ์ธ  API๋Š” ์‹œํฌ๋ฆฟ ํ‚ค๋ฅผ ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ฆฌ๊ธฐ ์œ„ํ•ด ์‹œํฌ๋ฆฟ ํ‚ค ๋’ค์— ์ฝœ๋ก ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    // @docs https://docs.tosspayments.com/reference/using-api/authorization#%EC%9D%B8%EC%A6%9D
    const encryptedSecretKey = btoa(secretKey+":")

    // ------ ๊ฒฐ์ œ ์Šน์ธ API ํ˜ธ์ถœ ------
    // @docs https://docs.tosspayments.com/guides/payment-widget/integration#3-๊ฒฐ์ œ-์Šน์ธํ•˜๊ธฐ
    const response = await fetch("https://api.tosspayments.com/v1/payments/confirm", {
      method: "POST",
      headers: {
        "Authorization": `Basic ${encryptedSecretKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(requestData),
    });
    
    const json = await response.json();

    return { response, json };
}

Vue ํŽ˜์ด์ง€

๊ฒฐ์žฌ ๋ฒ„ํŠผ ๊ฒฐ๊ณผ ํ™”๋ฉด :

– views/shop/SimpleOrderList.vue

<template>
  <div>
    <!-- ๊ฒฐ์žฌ ๋ฒ„ํŠผ -->
    <div class="row d-flex justify-content-end">
      <button type="button" @click="goApproval" class="btn btn-warning w-25">
        Go Approval
      </button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      // ์ฃผ๋ฌธ๋ฒˆํ˜ธ ์ƒ˜ํ”Œ(6์ž๋ฆฌ์ด์ƒ : ํ† ์Šค ๊ฒฐ์žฌ API ํ•„์ˆ˜์ž๋ฆฌ์ˆ˜) : ์ฃผ๋ฌธ๋ฒˆํ˜ธ๋Š” ๊ณ ์œ ํ•œ ๊ฐ’์œผ๋กœ ํ…Œ์ŠคํŠธ ํ• ๊ฒƒ
      sono: 222235
    }
  },
  methods: {
    // ํ† ์Šค ๊ฒฐ์žฌํŽ˜์ด์ง€๋กœ ์ด๋™
    goApproval() {
      this.$router.push("/toss-check/" + this.sono);
    },
  },
};
</script>
<style></style>
  • ์ฃผ๋ฌธ๋ฒˆํ˜ธ : ๊ณ ์œ ํ•œ ๊ฐ’์œผ๋กœ ํ…Œ์ŠคํŠธํ•ด์•ผ ์ •์ƒ ์‹คํ–‰๋จ
  • ๊ฒฐ์žฌ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํ† ์Šค API ๊ฒฐ์žฌ ํŽ˜์ด์ง€๋กœ ์ด๋™๋จ,
  • ์ด๋™ ์‹œ ์ฃผ๋ฌธ๋ฒˆํ˜ธ๊ฐ€ ์›น๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ์œผ๋กœ ์ „๋‹ฌ๋จ : /toss-check/์ฃผ๋ฌธ๋ฒˆํ˜ธ

ํ† ์Šค ๊ฒฐ์žฌ ํ™”๋ฉด #1 :

– views/toss/CheckoutView.vue : ํ† ์Šค API ์‚ฌ์ดํŠธ์—์„œ ์ œ๊ณต

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

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

export default {
  data() {
    return {
      paymentWidget: null,
      paymentMethodWidget: null,
      // TODO: clientKey๋Š” ๊ฐœ๋ฐœ์ž์„ผํ„ฐ์˜ ๊ฒฐ์ œ์œ„์ ฏ ์—ฐ๋™ ํ‚ค > ํด๋ผ์ด์–ธํŠธ ํ‚ค๋กœ ๋ฐ”๊พธ์„ธ์š”.
      // TODO: customerKey๋Š” ๊ตฌ๋งค์ž์™€ 1:1 ๊ด€๊ณ„๋กœ ๋ฌด์ž‘์œ„ํ•œ ๊ณ ์œ ๊ฐ’์„ ์ƒ์„ฑํ•˜์„ธ์š”.
      clientKey: "test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm",
      customerKey: this.$route.params.sono,       // ์ฃผ๋ฌธ๋ฒˆํ˜ธ(๊ณ ์œ ๊ฐ’, ๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ)
      amount: 0,
      inputEnabled: false,
    };
  },
  methods: {
    // TODO: ํ† ์Šค API ๊ฒฐ์žฌ 
    async requestPayment() {
      try {
        if (this.paymentWidget) {
          // TODO: ๊ฒฐ์žฌ ์š”์ฒญ ์ž„์‹œ ์ •๋ณด
          let data = {
            orderId: this.$route.params.sono,                // ์ฃผ๋ฌธ๋ฒˆํ˜ธ(๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ), ํ•„์ˆ˜
            orderName: "ํ…Œ์ŠคํŠธ ์ด๋ฆ„",                           // ์ฃผ๋ฌธ๋ช…(๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ) ํ•„์ˆ˜
            successUrl: `${window.location.origin}/success`, // ์„ฑ๊ณต url, ํ•„์ˆ˜
            failUrl: `${window.location.origin}/fail`,       // ์‹คํŒจ url, ํ•„์ˆ˜
          };
          console.log(data);
          // ------ '๊ฒฐ์ œํ•˜๊ธฐ' ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ๊ฒฐ์ œ์ฐฝ ๋„์šฐ๊ธฐ ------
          // ๊ฒฐ์ œ๋ฅผ ์š”์ฒญํ•˜๊ธฐ ์ „์— orderId, amount๋ฅผ ์„œ๋ฒ„์— ์ €์žฅํ•˜์„ธ์š”.
          // ๊ฒฐ์ œ ๊ณผ์ •์—์„œ ์•…์˜์ ์œผ๋กœ ๊ฒฐ์ œ ๊ธˆ์•ก์ด ๋ฐ”๋€Œ๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๋Š” ์šฉ๋„์ž…๋‹ˆ๋‹ค.
          // @docs https://docs.tosspayments.com/reference/widget-sdk#requestpayment๊ฒฐ์ œ-์ •๋ณด
          await this.paymentWidget.requestPayment(data);
        }
      } catch (error) {
        // ์—๋Ÿฌ ์ฒ˜๋ฆฌํ•˜๊ธฐ
        console.error(error);
      }
    },
  },
  async mounted() {

    // TODO: ํ† ์Šค API ------  ๊ฒฐ์ œ์œ„์ ฏ ์ดˆ๊ธฐํ™” ------
    // @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-์„ค์น˜-๋ฐ-์ดˆ๊ธฐํ™”
    this.paymentWidget = await loadPaymentWidget(this.clientKey, ANONYMOUS);

    // TODO: ํ† ์Šค API ------  ๊ฒฐ์ œ UI ๋ Œ๋”๋ง ------
    // @docs https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods์„ ํƒ์ž-๊ฒฐ์ œ-๊ธˆ์•ก-์˜ต์…˜
    this.paymentMethodWidget = this.paymentWidget.renderPaymentMethods(
      "#payment-method",
      { value: 13000 },                // ๊ฒฐ์žฌ๊ธˆ์•ก(๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ)
      { variantKey: "DEFAULT" }
    );

    // TODO: ํ† ์Šค API ------  ์ด์šฉ์•ฝ๊ด€ UI ๋ Œ๋”๋ง ------
    // @docs https://docs.tosspayments.com/reference/widget-sdk#renderagreement์„ ํƒ์ž-์˜ต์…˜
    this.paymentWidget.renderAgreement("#agreement", {
      variantKey: "AGREEMENT",
    });

    // TODO: ํ† ์Šค API  
    this.paymentMethodWidget.on("ready", () => {
      this.inputEnabled = true;
    });
  },
};
</script>

<style>
@import "@/assets/css/style.css";
</style>
  • ํ† ์Šค API ์‚ฌ์ดํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” ์†Œ์Šค
  • ๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ : ์ด ๋ถ€๋ถ„์ด ์ง์ ‘ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•ด์„œ ์‘์šฉํ•˜๋Š” ๋ถ€๋ถ„์ž„
  • ์œ„์—์„œ๋Š” ์ฃผ๋ฌธ๋ฒˆํ˜ธ, ์ฃผ๋ฌธ๋ช…์„ ์ง์ ‘ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฝ”๋”ฉํ–ˆ์Œ

ํ† ์Šค ๊ฒฐ์žฌ ํ™”๋ฉด #4

– views/toss/SuccessView.vue : ํ† ์Šค API ์‚ฌ์ดํŠธ์—์„œ ์ œ๊ณต

<template>
  <section v-if="confirmed">
    <div class="box_section" style="width: 600px">
      <img
        style="width: 100px"
        src="https://static.toss.im/illusts/check-blue-spot-ending-frame.png"
      />
      <h2>๊ฒฐ์ œ๋ฅผ ์™„๋ฃŒํ–ˆ์–ด์š”</h2>

      <div class="p-grid typography--p" style="margin-top: 50px">
        <div class="p-grid-col text--left"><b>๊ฒฐ์ œ๊ธˆ์•ก</b></div>
        <div class="p-grid-col text--right" id="amount">
          {{ jsonData.totalAmount }}์›
        </div>
      </div>
      <div class="p-grid typography--p" style="margin-top: 10px">
        <div class="p-grid-col text--left"><b>์ฃผ๋ฌธ๋ฒˆํ˜ธ</b></div>
        <div class="p-grid-col text--right" id="orderId">
          {{ jsonData.orderId }}
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import { confirmPayment } from "@/utils/confirmPayment";
import SimpleApprovalService from '@/services/shop/SimpleApprovalService';

export default {
  data() {
    return {
      requestData: {
        orderId: this.$route.query.orderId, 
        amount: this.$route.query.amount, 
        paymentKey: this.$route.query.paymentKey, 
      },
      confirmed: false,
      jsonData: null,
    };
  },
  methods: {
    // TODO: ํ† ์Šค ๊ฒฐ์žฌ ํ•จ์ˆ˜
    async confirm() {
      try {
        const { response, json } = await confirmPayment(this.requestData);
        console.log(json);
        if (!response.ok) {
          this.$router.push(`/fail?message=${json.message}&code=${json.code}`);
        } else {
          this.confirmed = true;
          this.jsonData = json;

          // ๊ฒฐ์žฌ ์ €์žฅ ํ•จ์ˆ˜ ์‹คํ–‰(๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ)
          this.confirmApproval()
        }
      } catch (e) {
        console.log(e);
      }
    },
    // ๊ฒฐ์žฌ ์ €์žฅ ํ•จ์ˆ˜ ์ •์˜(๊ฐœ๋ฐœ์ž์ฝ”๋”ฉ)
    async confirmApproval() {
      let now              = new Date();
      let yearMonthDay     = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
      let hourMinuteSecond = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
      let dateFormat       = `${yearMonthDay} ${hourMinuteSecond}`;

      try {
        let approval = {
          sono: this.$route.query.orderId,          // ์ฃผ๋ฌธ๋ฒˆํ˜ธ
          approvalDate: dateFormat,                 // ๊ฒฐ์žฌ์ผ
          approvalAmount: this.$route.query.amount, // ์ฃผ๋ฌธ๊ธˆ์•ก
        };
        let response = await SimpleApprovalService.create(approval);
        this.simpleApproval = response.data;
        
        console.log(response.data);
      } catch (e) {
        console.log(e);
      }
    },
  },
  mounted() {
    this.confirm();
  },
};
</script>

<style>
@import "@/assets/css/style.css";
</style>
  • ๊ฐœ๋ฐœ์ž ์ฝ”๋”ฉ : ์ด ๋ถ€๋ถ„์ด ์ง์ ‘ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•ด์„œ ์‘์šฉํ•˜๋Š” ๋ถ€๋ถ„์ž„
  • ์œ„์—์„œ๋Š” ๊ฒฐ์žฌ ์ €์žฅ ํ•จ์ˆ˜ ์ •์˜ํ–ˆ๊ณ (confirmApproval()), ๊ฒฐ์žฌ ์ €์žฅ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ–ˆ์Œ

๐Ÿ“ƒ ๊ฒฐ๋ก 

ํ† ์Šค ๊ฒฐ์žฌ API ๋ฅผ ์ด์šฉํ•ด์„œ Vue & Spring boot ๋กœ ์—ฐ๋™ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
ํ† ์Šค ๊ฒฐ์žฌ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋”ฉ๋ ๋•Œ ์ฃผ๋ฌธ๋ฒˆํ˜ธ๋ฅผ ์›น๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ์œผ๋กœ ์ „๋‹ฌํ–ˆ์œผ๋ฉฐ,
ํ† ์Šค ๊ฒฐ์žฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ๊ฒฐ์žฌ ํ…Œ์ด๋ธ”์— Create ํ•˜๋Š” ๊ธฐ๋Šฅ๋„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค..

Spring Boot ๋Š” @RestController ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•ด ๊ตฌํ˜„ํ–ˆ์œผ๋ฉฐ, ๊ฒฐ๊ณผ๋Š” JSON ๋ฐ์ดํ„ฐ๋กœ ๋ฆฌํ„ด๋ฉ๋‹ˆ๋‹ค.
Vue ๋Š” axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฒก์—”๋“œ์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

DB ํ”„๋ ˆ์ž„์›Œํฌ๋Š” JPA ๋ฅผ ์ด์šฉํ•ด์„œ sql ๋ฌธ์„ ์ง์ ‘ ์ œ์ž‘ํ•˜์ง€ ์•Š๊ณ  ์ž๋™ํ™”๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
Mybatis ์˜ ์ง์ ‘ sql ๋ฌธ ์ œ์ž‘ ๊ธฐ๋Šฅ์— ๋Œ€์‘ํ•ด ์ž์ฃผ ๋ฐ˜๋ณต๋˜๊ณ  ์‰ฌ์šด ๊ธฐ๋Šฅ์€ JPA ์˜ sql ์ž๋™์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๊ณ ,
๋ณต์žกํ•œ sql ๋ฌธ์€ @Query ๋ฅผ ์ด์šฉํ•ด ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด ์š”์ฆ˜ ์„œ๋น„์Šค ์—…์ฒด ๊ธฐ์ค€์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ์žฌ API ์— ๊ด€์‹ฌ ์žˆ์œผ์‹œ๋‹ค๋ฉด Source ๋Š” ์•„๋ž˜์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋‹ต๊ธ€ ๋‚จ๊ธฐ๊ธฐ

์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” ๊ณต๊ฐœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์ˆ˜ ํ•„๋“œ๋Š” *๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค