결제하기
1. 결제 세션 토큰 발급
- [고객사 서버가 해야할 일 1] 고객사 서버에서 point3 서버로 결제정보를 넘기면 결제 세션 토큰을 발급받습니다.결제 토큰 생성 API
[POST]:
/purchase/token
Request Header:
Authorization Basic <발급 받은 고객사 비밀키> Request Body:
json{ "clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0", // 고객사에서 발급하고 관리하는 유저 ID [고유 필수] "totalCharge": 1000, // 결제 총액 (integer) "orderIdFromClient": "orderIdRandom", // 고객사에서 관리하는 결제 ID [고유 권장] (string) "cultureExpenseType": "a", // 문화비 소득공제 타입 (1자리 string) "cashReceiptType": "a", // 현금영수증 발급 주체 타입 (1자리 string) "clientBusinessNum": "1428801897", // 고객사 사업자 번호 (10자리 string) "clientNickname": "바이올렛 캔디 앤 젤리", // 고객사 닉네임 (string) "purchasingProductName": "바이올렛 솜사탕맛 젤리", // 상품/서비스명 (string) "taxFreeAmount": 900, // 면세 금액 (integer) "vat": 100 // 부가세 (integer) }
Response:
200json{ "statusCode": 200, "message": "success", "result": { "token": "결제 토큰", }, }
400 Bad Request요청이 잘못되었습니다.
401 Unauthorized요청이 잘못되었습니다. 인증 헤더가 없거나 잘못되었습니다.
500 Internal Server Error필드 유의 사항
💡vat(부가세) 와 taxFreeAmount(부가세 제외 결제액)의 합은 totalCharge (총 결제액)이 되어야 합니다.💡vat(부가세) 와 taxFreeAmount(부가세 제외 결제액)의 합은 totalCharge (총 결제액)이 되어야 합니다.💡vat, taxFreeAmount, 그리고 totalCharge는 모두 0 보다 큰 양수이어야 합니다.💡cultureExpenseType은 다음 중 하나이어야 합니다.- ‘a’: 소득공제용 일반
- ‘b’: 소득공제용 공연
- ‘c’: 소득공제용 대중교통
- ‘d’: 지출증빙용 일반
- ‘f’: 지출 증빙용 공연
- ‘g’: 지출 증빙용 대중교통
💡cashReceiptType은 다음 중 하나이어야 합니다.- ‘a’: 개인 (현재 고정값)
💡orderIdFromClient: 고객사에서 제공하는 주문번호 (고유하지 않아도 괜찮으나, 이후 결제 조회 시 여러개의 주문이 검색 될 수 있음)💡clientGeneratedUserId (고객사에서 관리하는 유저 ID): [중요] 고객사에서 발급하고 내부적으로 관리하는 고유한 유저의 ID입니다. 이 필드를 통해서 유저를 식별하기에, 한 유저당 하나의 ID가 보장될 수 있어야 합니다. 만약 ID가 바뀌게 되면 유저는 다시 전화번호 인증을 해야하고, 다른 유저의 아이디가 중복으로 사용된다면 유저들은 다른 유저의 결제 화면을 보게될 수 있습니다.발급 받은 토큰은 point3 위젯을 호출하는데에 사용됩니다.
2. 결제 위젯 호출
- [고객사 웹/앱이 해야할 일1] 고객사 웹/앱에서 발급 받은 결제 토큰을 이용하여 point3 위젯을 호출합니다.위젯 호출 방법
결제가 이루어지는 주소는 다음과 같습니다.
이때 토큰에 특수문자가 포함되어 있을 수 있으므로, 정상적으로 URL에 토큰을 넣어 결제 위젯을 호출할 수 있게 토큰을 인코딩 해주세요. Javascript의 경우에는 encodeURIComponent() 내장함수를 사용하면 됩니다.
html"https://widget.point3.io/?d=<토큰>"
위 URL을 통해 결제 위젯을 호출합니다.
위젯 호출 시 point3 내부 로직을 통해 결제가 진행됩니다.
각 프레임워크 및 기기별 결제 위젯 연동 방법은 [point3 결제 위젯 연동하기] 를 참고해주세요!
3. 결제 위젯에서 시그니처 받기
- [고객사 웹/앱이 해야할 일2] point3 결제 위젯에서 시그니처를 받습니다.시그니처 받기
결제가 완료되면 point3 결제 위젯에서 시그니처를 반환합니다.
이 시그니처는 각각의 주문에 대해 부여된 고유한
key
값입니다.결제 위젯이 반환하는 시그니처를 수신하는 방법은 [point3 결제 위젯 연동하기] 를 참고해주세요!
4. 결제 승인 요청 및 결제 완료
- [고객사 서버가 해야할 일2] 고객사 서버에서 point3 서버로 30초 이내에 결제 시그니처를 보내 결제 승인을 요청합니다. 30초가 지나면 결제가 자동 취소되므로 응답 요청 직후 바로 보내주세요!결제 승인 요청 API
[POST]:
/purchase/confirm
결제 토큰을 통해 결제를 준비합니다. 토큰을 통해 결제요청을 성공적으로 받게되면, 결제가 이루어집니다!
Request Header:
Authorization Basic <발급 받은 고객사 비밀키> json{ "signature": "시그니처", }
Response:
200json{ "statusCode": 200, "message": "success", "result": { // 아래는 임시 데이터입니다. 일단 전부 반환하게 되어있음. signature: string; userId: string; clientNickname: string; purchasingProductName: string; totalCharge: number; taxFreeAmount: number; vat: number; orderIdFromClient: string; clientIdentification: string; clientGeneratedUserId: string; paymentMethodType: methodType; cultureExpenseType: culturalExpenseType; timeStampOnRequest: string; // Format: YYYYMMDDHHmmss timeStampOnAccept: string; // Format: YYYYMMDDHHmmss }, }
400 Bad Request요청이 잘못되었습니다.
401 Unauthorized요청이 잘못되었습니다. 인증 헤더가 없거나 잘못되었습니다.
500 Internal Server Error내부 서버에 알 수 없는 오류가 발생했습니다.
성공적으로 상태코드
200
이 반환되면 결제가 최종적으로 완료됩니다 .
결제 이후 API로 거래 관리하기
토큰 갱신 API
[POST]:
/api/token/refresh
기존 토큰을 파기하고 새 토큰을 생성합니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"token": "갱신된 고객사 토큰",
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
정산금 조회 API
[GET]:
/api/settlement
정산 가능한 금액을 조회합니다. 정산금은 30분 간격으로 갱신됩니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"balance": 150000, // 가능한 정산금
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
OrderID로 결제 조회 API
[GET]:
/api/payments/orderid/{orderId}
OrderID
로 결제를 조회합니다. 결제 토큰 생성 요청 시 Point3 백엔드에서 OrderID
의 유일성을 검증하지 않으므로, 여러 개의 결제 건 수가 반환될 수 있습니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"orders": [
{
"signature": "CnwGnosGohK2iu3cSrE68Ew4xVLr98Gl",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 10000000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0"
"isCanceled": true,
},
{
"signature": "NsIlFFvv8zdEMMaFZSI3i7iEKXRdUuMz",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 90000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0"
"isCanceled": false,
},
],
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
Signature로 결제 조회 API
[GET]:
/api/payments/{signature}
Signature
로 결제를 조회합니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"signature": "CnwGnosGohK2iu3cSrE68Ew4xVLr98Gl",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 10000000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0",
"isCanceled": false,
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
UserID로 결제 조회 API
[GET]:
/api/payments/userId/{userId}
userId
(clientGeneratedUserId) 로 결제를 조회합니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"signature": "CnwGnosGohK2iu3cSrE68Ew4xVLr98Gl",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 10000000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0",
"isCanceled": false,
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
날짜로 결제 조회 API
[GET]:
/api/payments/date?startDate={startDate}&endDate={endDate}&page={page}&count={count}
날짜로 결제를 조회합니다.
- 날짜 형식은 ISO 8601 (
yyyy-MM-dd'T'hh:mm:ss
) 포맷을 따릅니다.
endDate
가 비어 있는 경우,startDate
부터 현재까지 모든 결제를 조회합니다.
count
기본값은30
이며 선택 사항입니다.
page
는1
부터 시작합니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {
"orders": [
{
"signature": "CnwGnosGohK2iu3cSrE68Ew4xVLr98Gl",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 10000000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0"
"isCanceled": true,
},
{
"signature": "NsIlFFvv8zdEMMaFZSI3i7iEKXRdUuMz",
"userId": "1",
"clientNickname": "바이올렛 캔디 앤 젤리",
"purchasingProductName": "바이올렛 솜사탕맛 젤리",
"totalCharge": 90000,
"taxFreeAmount": 0,
"vat": 0,
"orderIdFromClient": "orderIdRandom",
"clientIdentification": "widgetpaytester0",
"clientGeneratedUserId": "IDIDIDIDIDIDIDIDIDIDIDIDIDIDT0"
"isCanceled": false,
},
],
},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
결제 취소 API
[POST]:
/api/payments/{signature}/cancel
Signature
로 결제를 취소합니다. 취소 건은 5분 간격으로 처리됩니다.
Request Header:
Authorization | Basic <발급 받은 고객사 비밀키> |
Response:
{
"statusCode": 200,
"message": "success",
"result": {},
}
요청이 잘못되었습니다.
요청이 잘못되었습니다.
내부 서버에 알 수 없는 오류가 발생했습니다.
정산하기
결제 위젯 시작하기
Android에서 WebView로 연동하기
import android.os.Bundle;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;
import android.webkit.JavascriptInterface
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Point3WidgetBridge(), "Point3WidgetBridge");
webView.loadUrl("https://widget.point3.io/?d=토큰"); // Point3 위젯 호출
}
}
private class Point3WidgetBridge {
public Point3WidgetBridge() {
}
@JavascriptInterface
public void postMessage(String rawJson) {
JSONObject data = new JSONObject(rawJson);
// 결제 성공 시 data.get("msg") 는 siganture를 반환합니다.
switch (data.get("status")) {
case "success":
// 결제 위젯 성공 시 수행할 동작
break;
case "error":
// 결제 실패 시 로직 (유저 이탈)
break;
default:
break;
}
}
}
iOS에서 WKWebView로 연동하기
import UIKit
import WebKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
// WebView에서 전송하는 데이터를 수신할 메세지 핸들러를 추가합니다.
contentController.add(self, name: "point3WidgetHandler")
configuration.userContentController = contentController
let webView = WKWebView(frame: view.frame, configuration: configuration)
view.addSubview(webView)
if let url = URL(string: "https://widget.point3.io/?d=토큰") { // Point3 위젯 호출
webView.load(URLRequest(url: url))
}
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard message.name = "point3WidgetHandler",
let dictionary = message.body as? [String: String],
let status = dictionary["status"],
let msg = dictionary["msg"] else { return }
// 결제 성공 시 msg 변수에는 siganture가 채워집니다.
switch status {
case "success":
// 결제 성공 시 로직
break;
case "error":
// 결제 실패 시 로직 (유저 이탈)
break;
default:
break
}
}
}
React Native에서 WebView로 연동하기
import React from "react";
import { WebView } from "react-native-webview";
const WebViewScreen = () => {
const handlePoint3Widget = (e) => {
const data = JSON.parse(e.nativeEvent.data)
const { status, msg } = data
// 결제 성공 시 msg 변수에는 signature가 채워집니다.
switch (status) {
case "success":
// 결제 성공 시 로직
break;
case "error":
// 결제 실패 시 로직 (유저 이탈)
break;
default:
break;
}
}
return (
<View>
<WebView
onMessage={handlePoint3Widget}
source={"https://widget.point3.io/?d=토큰"}
/>
</View>
);
}
Javascript (HTML)에서 연동하기
<head>
<!-- Point3 위젯 스크립트 추가 -->
<script src="https://resources.point3.io/widget.js"></script>
<link rel="stylesheet" href="https://resources.point3.io/widget.css" />
<!-- 스크립트 끝 -->
</head>
<body>
<div class="point3-widget">
<div class="point3-widget-inner">
<iframe
id="point3-widget-iframe"
src="https://widget.point3.io/?d=토큰"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation allow-modals allow-popups-to-escape-sandbox allow-presentation allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
/>
</div>
</div>
<script>
function onPaymentError() {
// 결제 실패 시 로직 (유저 이탈)
console.log("유저가 위젯을 종료하였습니다.")
}
function onPaymentSuccess(signature) {
// 결제 성공 시 수행할 동작
// 결제 성공 시 signature 파라미터 변수에는 signature가 채워집니다.
}
</script>
</body>
Javascript (React.js)에서 연동하기
widget.css
.point3-widget {
position: absolute;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
background-color: #0D0D0D;
-webkit-overflow-scrolling: touch;
}
.point3-widget > .point3-widget-inner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(calc((-312px / 2)), calc((-560px / 2)));
width: 312px;
height: 560px;
}
#point3-widget-iframe {
position: absolute;
width: 100%;
height: 100%;
border: none;
}
import React from 'react'
// 위젯 CSS 로드
import './widget.css'
export const WidgetPage = () => {
const onPaymentSuccess = (signature) => {
// 결제 성공 시 수행할 동작
// 결제 성공 시 signature 파라미터 변수에는 signature가 채워집니다.
}
const onPaymentError = () => {
// 결제 실패 시 로직 (유저 이탈)
}
// 이벤트 리스너 추가
window.addEventListener('message', function (e) {
if (e.origin !== "https://widget.point3.io") return;
if (!e.data) return;
const { status, msg } = e.data
if (!status && !msg) return;
if (status === 'error') {
onPaymentError()
}
if (msg === 'success') {
onPaymentSuccess(msg)
}
});
return (
<>
<div className="point3-widget">
<div className="point3-widget-inner">
<iframe
id="point3-widget-iframe"
key={1}
src="https://widget.point3.io/?d=토큰"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation allow-modals allow-popups-to-escape-sandbox allow-presentation allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
/>
</div>
</div>
</>
)
}
Javascript (기타)에서 연동하기
.point3-widget {
position: absolute;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
background-color: #0D0D0D;
-webkit-overflow-scrolling: touch;
}
.point3-widget > .point3-widget-inner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(calc((-312px / 2)), calc((-560px / 2)));
width: 312px;
height: 560px;
}
#point3-widget-iframe {
position: absolute;
width: 100%;
height: 100%;
border: none;
}
<div class="point3-widget">
<div class="point3-widget-inner">
<iframe
id="point3-widget-iframe"
src="https://widget.point3.io/?d=토큰"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation allow-modals allow-popups-to-escape-sandbox allow-presentation allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
/>
</div>
</div>
위젯을 iframe
형태로 호출해주시고, 위젯에서 보내는 메세지를 수신할 수 있도록 window
에 EventListener
를 추가해주세요.
function onPaymentSuccess(signature) {
// 결제 성공 시 수행할 동작
// 결제 성공 시 변수에는 signature가 채워집니다.
}
function onPaymentError() {
// 결제 실패 시 로직 (유저 이탈)
}
window.addEventListener('message', function (e) {
if (e.origin !== "https://widget.point3.io") return;
if (!e.data) return;
const { status, msg } = e.data
if (!status && !msg) return;
if (status === 'error') {
onPaymentError()
}
if (msg === 'success') {
onPaymentSuccess(msg)
}
});