웹뷰 아이템 구매 가이드
Android/Unity 공통 웹뷰 구매 플로우와 엔드포인트, 통신 규격을 정리했습니다.
테스트 서버 URL
- API 서버: https://dev-api.fetamix.com
- Site 서버: https://dev.fetamix.com:8081
구매 플로우
- pending list 요청 » 구매 준비 가능한 아이템 목록 조회(미지급 아이템 지급처리)
- 로그인 인증을 통한 게임앱 JWT 토큰, 구매 아이템 ID 로 구매준비 API 호출
- 구매 준비 API 호출 » payment_id, point_use_token
- 웹뷰 로드 (HTTP 헤더로 payment_id, point_use_token 전달)
- 웹뷰 내 구매 완료(크래딧 차감) » 앱에 payment_id, receipt_number 전달
- 게임서버에서 payment_id/receipt_number 영수증 확인 API 호출을 통한 검증
- 구매 아이템소비 처리(아이템 지급)
구매 플로우
sequenceDiagram
participant User as 사용자
participant GameApp as 게임App
participant WebView as WebView<br/>(플랫폼 제공)
participant API as fetaMix API
participant GameServer as 게임 서버
Note over User,GameServer: Phase 1: 아이템 선택 및 구매 시작
User->>GameApp: 1. 게임 실행
GameApp->>API: 1-1. pending list 요청
API-->>GameApp: 1-2. 응답(pending list)
User->>GameApp: 2. 상점 진입
User->>GameApp: 3. 상품 클릭
GameApp->>API: 4. 아이템구매 요청(과금)<br/>(API 호출, 상품정보생성)
API-->>GameApp: 응답(결제ID 발급)
GameApp->>WebView: 5. OAuth 토큰+결제ID로<br/>WebView 호출
Note over User,GameServer: Phase 2: WebView를 통한 결제 처리
WebView->>API: 6. OAuth 검증 + 상품정보 요청
API-->>WebView: 응답(잔액)
alt [잔액 모자람]
WebView->>User: 7-1. 결제 화면 표시<br/>[잔액모자람]
else [잔액 충분]
WebView->>User: 7-2. 과금정보 표시
end
User->>WebView: 8. 과금(아이템구매)
WebView->>API: 9. 크레딧 차감 API 호출
API-->>WebView: 응답(구매 완료)
WebView->>User: (완료 화면 표시)
Note over User,GameServer: Phase 3: 구매 후 검증 및 아이템 지급
User->>WebView: 10. 닫기 버튼 클릭
WebView->>GameApp: 11. postMessage<br/>(결제ID, 영수증번호 전달)
GameApp->>GameServer: 12. 결제ID, 영수증번호 전달
GameServer->>API: 13. 결제 검증 API 호출
API-->>GameServer: 응답(검증 결과)
GameServer->>GameServer: 14. DB에 아이템 지급
GameServer-->>GameServer: 응답(정상지급)
GameServer->>GameApp: 영수증 확인 완료 알림
GameApp->>API:15.아이템 소비완료처리
API-->>GameApp:응답(성공/실패)
GameApp->>User: 아이템 지급 완료
API 엔드포인트
0) 구매 펜딩 목록
GET https://dev-api.fetamix.com/api/v1/game/games/purchases/pendinglist
Authorization: Bearer <JWT>
Content-Type: application/json
Response: { success, data: [{ id, payment_id, receipt_number, user_id, game_id, item_id, amount, status }] }| 상황 | HTTP 상태 | error_code | 응답 구조 |
|---|---|---|---|
| 펜딩 목록 조회 성공 | 200 OK | - | { success: true, data: [{ id, payment_id, receipt_number, user_id, game_id, item_id, amount, status }] } |
| 펜딩 내역 없음 | 200 OK | - | { success: true, data: [] } |
| 토큰 헤더 누락 | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer 토큰이 필요합니다." } |
| 토큰 검증 실패 | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "유효하지 않은 토큰입니다." } |
| 기타 오류 | 400 Bad Request | - | { success: false, message: "구매 펜딩 내역 조회 실패: ..." } |
1) 구매 준비
POST https://dev-api.fetamix.com/api/v1/game/games/items/purchase
Authorization: Bearer <JWT>
Content-Type: application/json
Body: { "item_id": "ITEM_ID" }
Response: { success, payment_id, point_use_token, user_balance, item_info }| 상황 | HTTP 상태 | 응답 구조 (요약) |
|---|---|---|
| 구매 준비 성공 | 200 OK | { success: true, payment_id, point_use_token, item_info, user_balance, insufficient, purchase } |
| Authorization 헤더 누락 | 401 Unauthorized | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer 토큰이 필요합니다." } |
| JWT 토큰 검증 실패 | 401 Unauthorized | { success: false, error_code: "INVALID_TOKEN", message: "유효하지 않은 토큰입니다." } |
| 아이템 미존재 | 404 Not Found | { success: false, error_code: "ITEM_NOT_FOUND", message: "아이템을 찾을 수 없습니다. (...)" } |
| 포인트 토큰 발급 실패 | 500 Internal Server Error | { success: false, error_code: "POINT_TOKEN_GENERATION_FAILED", message: "포인트 사용 토큰 발행에 실패했습니다." } |
| 기타 서버 오류 | 500 Internal Server Error | { success: false, error_code: "PURCHASE_REQUEST_FAILED", message: "아이템 구매 실패: ..." } |
2) 구매하기
POST https://dev.fetamix.com:8081/api/webviews/purchases/{paymentId}/complete
Headers: Authorization: Bearer <point_use_token>
Body: {}
비고: 웹뷰에서 호출 (Laravel 프록시 경유)| 상황 | HTTP 상태 | 응답 구조 (요약) |
|---|---|---|
| 구매 완료 성공 | 200 OK | { success: true, payment_id, deducted_amount, previous_balance, new_balance, receipt_number, message } |
| Authorization 헤더 누락 | 401 Unauthorized | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer 토큰이 필요합니다." } |
| 포인트 토큰 검증 실패 | 401 Unauthorized | { success: false, error_code: "INVALID_POINT_TOKEN", message: "유효하지 않은 포인트 사용 토큰입니다." } |
| 구매 기록 없음 | 404 Not Found | { success: false, error_code: "PURCHASE_NOT_FOUND", message: "구매 기록을 찾을 수 없습니다." } |
| 이미 처리된 구매 | 409 Conflict | { success: false, error_code: "PURCHASE_ALREADY_PROCESSED", message: "이미 처리된 구매입니다." } |
| 포인트 부족 | 402 Payment Required | { success: false, error_code: "INSUFFICIENT_POINTS", message: "포인트가 부족합니다. ..." } |
| 포인트 차감 실패 | 400 Bad Request | { success: false, error_code: "POINT_DEDUCT_FAILED", message: "포인트 차감 실패: ..." } |
| 기타 서버 오류 | 500 Internal Server Error | { success: false, error_code: "PURCHASE_COMPLETE_FAILED", message: "구매 완료 실패: ..." } |
3) 영수증 확인
POST https://dev-api.fetamix.com/api/v1/game/games/purchases/verify-completion
Content-Type: application/json
Body: { "payment_id": "PAYMENT_ID", "receipt_number": "RECEIPT_NO" }
비고: 토큰 불필요 (게임 서버/클라이언트에서 검증 호출)| 상황 | HTTP 상태 | 응답 구조 (요약) |
|---|---|---|
| 검증 성공 | 200 OK | { verified: true, payment_id, receipt_number, user_id, item_id, amount, completed_at } |
| 검증 실패 (내역 없음) | 200 OK | { verified: false } |
| 파라미터 누락 | 400 Bad Request | { success: false, verified: false, error_code: "MISSING_PARAMETERS", message: "payment_id와 receipt_number는 필수입니다." } |
| IP 화이트리스트 실패 | 403 Forbidden | ForbiddenException: IP ...는 허용되지 않은 IP입니다. |
| 기타 서버 오류 | 500 Internal Server Error | { success: false, verified: false, error_code: "VERIFY_COMPLETION_FAILED", message: "검증 실패: ..." } |
4) 구매 아이템 소비
POST https://dev-api.fetamix.com/api/v1/game/games/purchases/consume
Authorization: Bearer <JWT>
Content-Type: application/json
Body: { "payment_id": "PAYMENT_ID", "receipt_number": "RECEIPT_NO" }
Response: { success, message }| 상황 | HTTP 상태 | error_code | 응답 구조 |
|---|---|---|---|
| 구매 아이템 소비 성공 | 200 OK | - | { success: true, message: "구매 아이템 소비 성공" } |
| 필수 파라미터 누락 | 400 Bad Request | MISSING_PARAMETERS | { success: false, error_code: "MISSING_PARAMETERS", message: "payment_id와 receipt_number는 필수입니다." } |
| 토큰 헤더 누락 | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer 토큰이 필요합니다." } |
| 토큰 검증 실패 | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "유효하지 않은 토큰입니다." } |
| 펜딩 내역 없음 | 404 Not Found | PURCHASE_NOT_FOUND | { success: false, error_code: "PURCHASE_NOT_FOUND", message: "구매 기록을 찾을 수 없습니다." } |
| 이미 소비된 내역 | 409 Conflict | PURCHASE_ALREADY_CONSUMED | { success: false, error_code: "PURCHASE_ALREADY_CONSUMED", message: "이미 소비된 구매입니다." } |
| 기타 서버 오류 | 500 Internal Server Error | PENDING_CONSUME_FAILED | { success: false, error_code: "PENDING_CONSUME_FAILED", message: "구매 펜딩 내역 처리 실패: ..." } |
5) 게임 아이템 리스트
GET https://dev-api.fetamix.com/api/v1/game/games/items/list
Authorization: Bearer <JWT>
Content-Type: application/json
Response: { success, data: [{ item_id, uid, name, amount, credits, is_cancelable, sale_type, purchase_limit }] }| 상황 | HTTP 상태 | error_code | 응답 구조 |
|---|---|---|---|
| 아이템 리스트 조회 성공 | 200 OK | - | { success: true, data: [{ item_id, uid, name, amount, credits, is_cancelable, sale_type, purchase_limit }] } |
| 토큰 헤더 누락 | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer 토큰이 필요합니다." } |
| 토큰 검증 실패 | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "유효하지 않은 토큰입니다." } |
| 예외 발생 | 500 Internal Server Error | ITEMS_LIST_FAILED | { success: false, error_code: "ITEMS_LIST_FAILED", message: "아이템 리스트 조회 실패: ..." } |
Android 연동 요약
0) 구매 펜딩 목록 조회 (게임 시작 시)
String apiBase = "https://dev-api.fetamix.com";
String url = apiBase + "/api/v1/game/games/purchases/pendinglist";
HttpURLConnection conn = createConnection(url, "GET");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Content-Type", "application/json");
// 응답: { success: true, data: [{ id, payment_id, receipt_number, ... }] }
// 미지급 아이템이 있으면 게임서버에 전달하여 지급 처리1) 구매 준비 API 호출
String apiBase = "https://dev-api.fetamix.com";
String url = apiBase + "/api/v1/game/games/items/purchase";
HttpURLConnection conn = createConnection(url, "POST");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Content-Type", "application/json");
JSONObject requestBody = new JSONObject();
requestBody.put("item_id", itemId);
// ... 요청 전송 및 응답 파싱2) 웹뷰 로드 (헤더 전달)
String siteBase = "https://dev.fetamix.com:8081";
String url = siteBase + "/api/webviews/purchase?v=" + System.currentTimeMillis();
Map<String,String> headers = new HashMap<>();
headers.put("X-Payment-ID", paymentId);
headers.put("X-Point-Use-Token", pointUseToken);
headers.put("X-Access-Token", token);
headers.put("X-Item-ID", itemId);
headers.put("X-User-Balance", String.valueOf(userBalance));
webView.loadUrl(url, headers);3) 완료 콜백 (JSInterface)
@JavascriptInterface
public void onPurchaseCompleted(String json) {
// { payment_id, receipt_number, new_balance }
JSONObject data = new JSONObject(json);
String paymentId = data.getString("payment_id");
String receiptNumber = data.getString("receipt_number");
// UI 업데이트 및 검증 API 호출
}4) 구매 검증 API 호출
String apiBase = "https://dev-api.fetamix.com";
String url = apiBase + "/api/v1/game/games/purchases/verify-completion";
// 토큰 불필요, payment_id와 receipt_number만 전달
JSONObject body = new JSONObject();
body.put("payment_id", paymentId);
body.put("receipt_number", receiptNumber);
// ... 요청 전송 및 응답 파싱5) 구매 아이템 소비
String apiBase = "https://dev-api.fetamix.com";
String url = apiBase + "/api/v1/game/games/purchases/consume";
HttpURLConnection conn = createConnection(url, "POST");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Content-Type", "application/json");
JSONObject body = new JSONObject();
body.put("payment_id", paymentId);
body.put("receipt_number", receiptNumber);
// ... 요청 전송 및 응답 파싱
// 응답: { success: true, message: "구매 아이템 소비 성공" }Unity 연동 요약
0) 구매 펜딩 목록 조회 (게임 시작 시)
string apiBase = "https://dev-api.fetamix.com";
string url = $"{apiBase}/api/v1/game/games/purchases/pendinglist";
var req = new UnityWebRequest(url, "GET");
req.SetRequestHeader("Authorization", $"Bearer {token}");
req.SetRequestHeader("Content-Type", "application/json");
req.downloadHandler = new DownloadHandlerBuffer();
// 응답: { success: true, data: [{ id, payment_id, receipt_number, ... }] }
// 미지급 아이템이 있으면 게임서버에 전달하여 지급 처리1) 구매 준비
string apiBase = "https://dev-api.fetamix.com";
string url = $"{apiBase}/api/v1/game/games/items/purchase";
var req = new UnityWebRequest(url, "POST");
req.SetRequestHeader("Authorization", $"Bearer {token}");
req.SetRequestHeader("Content-Type", "application/json");
string jsonBody = $"{{"item_id":"{itemId}"}}";
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
req.downloadHandler = new DownloadHandlerBuffer();2) WebView 로드
string siteBase = "https://dev.fetamix.com:8081";
string url = $"{siteBase}/api/webviews/purchase";
// gree/unity-webview 기준 예시
webView.LoadURL(url, new Dictionary<string,string>{
{"X-Payment-ID", paymentId},
{"X-Point-Use-Token", pointUseToken},
{"X-Access-Token", accessToken},
{"X-Item-ID", itemId},
{"X-User-Balance", userBalance.ToString()}
});3) 메시지 수신
// 웹뷰에서 Unity로 메시지 전달
window.Unity.call(JSON.stringify({
type: 'purchase_completed',
payment_id: paymentId,
receipt_number: receiptNumber
}));
// Unity에서 수신 처리
webView.OnMessage.AddListener((message) => {
var data = JsonUtility.FromJson<PurchaseCompleteData>(message);
// 검증 API 호출
});4) 구매 검증
string apiBase = "https://dev-api.fetamix.com";
string url = $"{apiBase}/api/v1/game/games/purchases/verify-completion";
// 토큰 불필요, payment_id와 receipt_number만 전달
string jsonBody = $"{{"payment_id":"{paymentId}","receipt_number":"{receiptNumber}"}}";
var req = new UnityWebRequest(url, "POST");
req.SetRequestHeader("Content-Type", "application/json");
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
req.downloadHandler = new DownloadHandlerBuffer();5) 구매 아이템 소비
string apiBase = "https://dev-api.fetamix.com";
string url = $"{apiBase}/api/v1/game/games/purchases/consume";
var req = new UnityWebRequest(url, "POST");
req.SetRequestHeader("Authorization", $"Bearer {token}");
req.SetRequestHeader("Content-Type", "application/json");
string jsonBody = $"{{"payment_id":"{paymentId}","receipt_number":"{receiptNumber}"}}";
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
req.downloadHandler = new DownloadHandlerBuffer();
// 응답: { success: true, message: "구매 아이템 소비 성공" }WebView 헤더 규격
X-Payment-ID(필수): 구매 식별자X-Point-Use-Token(필수): 1회성 포인트 사용 토큰X-Access-Token(선택): 사용자 JWTX-Item-ID(선택): 아이템 식별자X-User-Balance(선택): 사용자 잔액
참고
- 모든 호출은 HTTPS를 사용합니다.
- 민감 정보는 헤더를 사용하고 URL 파라미터로 전달하지 않습니다.