WebView item purchase guide
Android/Unity common WebView purchase flow, endpoints, and communication specs.
Test server URL
- API server: https://api.fetamix.com
- Site server: https://fetamix.com
Purchase flow
- Request pending list » Fetch purchasable items (and process unfulfilled items)
- Call purchase-prepare API with game app JWT and item ID
- Purchase prepare API » returns payment_id, point_use_token
- Load WebView (pass payment_id, point_use_token in HTTP headers)
- Complete purchase in WebView (credit deduction) » pass payment_id, receipt_number to app
- Game server verifies receipt via payment_id/receipt_number verification API
- Consume purchased item (fulfill item)
Purchase flow
sequenceDiagram
participant User as User
participant GameApp as Game App
participant WebView as WebView<br/>(Platform)
participant API as fetaMix API
participant GameServer as Game Server
Note over User,GameServer: Phase 1: Item selection and purchase start
User->>GameApp: 1. Launch game
GameApp->>API: 1-1. Request pending list
API-->>GameApp: 1-2. Response (pending list)
User->>GameApp: 2. Enter shop
User->>GameApp: 3. Click item
GameApp->>API: 4. Item purchase request<br/>(API call, create order)
API-->>GameApp: Response (payment_id)
GameApp->>WebView: 5. Open WebView with<br/>OAuth token + payment_id
Note over User,GameServer: Phase 2: Payment via WebView
WebView->>API: 6. Verify OAuth + request order
API-->>WebView: Response (balance)
alt [Insufficient balance]
WebView->>User: 7-1. Show payment screen<br/>[Insufficient balance]
else [Sufficient balance]
WebView->>User: 7-2. Show charge info
end
User->>WebView: 8. Confirm purchase
WebView->>API: 9. Credit deduction API
API-->>WebView: Response (purchase complete)
WebView->>User: (Show completion)
Note over User,GameServer: Phase 3: Verify and deliver item
User->>WebView: 10. Close
WebView->>GameApp: 11. postMessage<br/>(payment_id, receipt_number)
GameApp->>GameServer: 12. payment_id, receipt_number
GameServer->>API: 13. Verify completion API
API-->>GameServer: Response (verified)
GameServer->>GameServer: 14. Grant item in DB
GameServer-->>GameServer: Response (OK)
GameServer->>GameApp: Receipt confirmed
GameApp->>API: 15. Consume item
API-->>GameApp: Response (success/fail)
GameApp->>User: Item delivered
API endpoints
0) Purchase pending list
GET https://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 }] }| Situation | HTTP status | error_code | Response structure |
|---|---|---|---|
| Pending list retrieved | 200 OK | - | { success: true, data: [{ id, payment_id, receipt_number, user_id, game_id, item_id, amount, status }] } |
| No pending items | 200 OK | - | { success: true, data: [] } |
| Authorization header missing | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer token is required." } |
| Invalid token | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "Invalid token." } |
| Other error | 400 Bad Request | - | { success: false, message: "Failed to fetch purchase pending list: ..." } |
1) Purchase prepare
POST https://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 }| Situation | HTTP status | Response structure |
|---|---|---|
| Purchase prepare success | 200 OK | { success: true, payment_id, point_use_token, item_info, user_balance, insufficient, purchase } |
| Authorization header missing | 401 Unauthorized | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer token is required." } |
| JWT verification failed | 401 Unauthorized | { success: false, error_code: "INVALID_TOKEN", message: "Invalid token." } |
| Item not found | 404 Not Found | { success: false, error_code: "ITEM_NOT_FOUND", message: "Item not found. (...)" } |
| Point token generation failed | 500 Internal Server Error | { success: false, error_code: "POINT_TOKEN_GENERATION_FAILED", message: "Failed to issue point use token." } |
| Other server error | 500 Internal Server Error | { success: false, error_code: "PURCHASE_REQUEST_FAILED", message: "Item purchase failed: ..." } |
2) Purchase
POST https://fetamix.com/api/webviews/purchases/{paymentId}/complete
Headers: Authorization: Bearer <point_use_token>
Body: {}
Note: Called from WebView (via Laravel proxy).| Situation | HTTP status | Response structure |
|---|---|---|
| Purchase complete success | 200 OK | { success: true, payment_id, deducted_amount, previous_balance, new_balance, receipt_number, message } |
| Authorization header missing | 401 Unauthorized | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer token is required." } |
| Point token verification failed | 401 Unauthorized | { success: false, error_code: "INVALID_POINT_TOKEN", message: "Invalid point use token." } |
| Purchase not found | 404 Not Found | { success: false, error_code: "PURCHASE_NOT_FOUND", message: "Purchase record not found." } |
| Purchase already processed | 409 Conflict | { success: false, error_code: "PURCHASE_ALREADY_PROCESSED", message: "Purchase already processed." } |
| Insufficient points | 402 Payment Required | { success: false, error_code: "INSUFFICIENT_POINTS", message: "Insufficient points. ..." } |
| Point deduction failed | 400 Bad Request | { success: false, error_code: "POINT_DEDUCT_FAILED", message: "Point deduction failed: ..." } |
| Other server error | 500 Internal Server Error | { success: false, error_code: "PURCHASE_COMPLETE_FAILED", message: "Purchase complete failed: ..." } |
3) Receipt verification
POST https://api.fetamix.com/api/v1/game/games/purchases/verify-completion
Content-Type: application/json
Body: { "payment_id": "PAYMENT_ID", "receipt_number": "RECEIPT_NO" }
Note: No token required (called from game server/client for verification).| Situation | HTTP status | Response structure |
|---|---|---|
| Verification success | 200 OK | { verified: true, payment_id, receipt_number, user_id, item_id, amount, completed_at } |
| Verification failed (no record) | 200 OK | { verified: false } |
| Parameters missing | 400 Bad Request | { success: false, verified: false, error_code: "MISSING_PARAMETERS", message: "payment_id and receipt_number are required." } |
| IP whitelist failed | 403 Forbidden | ForbiddenException: IP is not allowed. |
| Other server error | 500 Internal Server Error | { success: false, verified: false, error_code: "VERIFY_COMPLETION_FAILED", message: "Verification failed: ..." } |
4) Consume purchased item
POST https://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 }| Situation | HTTP status | error_code | Response structure |
|---|---|---|---|
| Item consumed success | 200 OK | - | { success: true, message: "Purchased item consumed successfully." } |
| Parameters missing | 400 Bad Request | MISSING_PARAMETERS | { success: false, error_code: "MISSING_PARAMETERS", message: "payment_id and receipt_number are required." } |
| Authorization header missing | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer token is required." } |
| Invalid token | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "Invalid token." } |
| Purchase not found | 404 Not Found | PURCHASE_NOT_FOUND | { success: false, error_code: "PURCHASE_NOT_FOUND", message: "Purchase record not found." } |
| Already consumed | 409 Conflict | PURCHASE_ALREADY_CONSUMED | { success: false, error_code: "PURCHASE_ALREADY_CONSUMED", message: "Purchase already consumed." } |
| Other server error | 500 Internal Server Error | PENDING_CONSUME_FAILED | { success: false, error_code: "PENDING_CONSUME_FAILED", message: "Purchase pending process failed: ..." } |
5) Game item list
GET https://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 }] }| Situation | HTTP status | error_code | Response structure |
|---|---|---|---|
| Item list retrieved | 200 OK | - | { success: true, data: [{ item_id, uid, name, amount, credits, is_cancelable, sale_type, purchase_limit }] } |
| Authorization header missing | 401 Unauthorized | AUTH_HEADER_MISSING | { success: false, error_code: "AUTH_HEADER_MISSING", message: "Bearer token is required." } |
| Invalid token | 401 Unauthorized | INVALID_TOKEN | { success: false, error_code: "INVALID_TOKEN", message: "Invalid token." } |
| Exception | 500 Internal Server Error | ITEMS_LIST_FAILED | { success: false, error_code: "ITEMS_LIST_FAILED", message: "Item list fetch failed: ..." } |
Android integration summary
0) Purchase pending list (on game start)
String apiBase = "https://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");
// Response: { success: true, data: [{ id, payment_id, receipt_number, ... }] }
// If there are unfulfilled items, pass to game server for delivery.1) Call purchase prepare API
String apiBase = "https://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) Load WebView (with headers)
String siteBase = "https://fetamix.com";
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) Completion callback (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");
// Call verification API
}4) Call receipt verification API
String apiBase = "https://api.fetamix.com";
String url = apiBase + "/api/v1/game/games/purchases/verify-completion";
// No token required, pass payment_id and receipt_number only.
JSONObject body = new JSONObject();
body.put("payment_id", paymentId);
body.put("receipt_number", receiptNumber);
// ... request and parse response5) Consume purchased item
String apiBase = "https://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);
// ... request and parse response
// Response: { success: true, message: "..." }Unity integration summary
0) Purchase pending list (on game start)
string apiBase = "https://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();
// Response: { success: true, data: [{ id, payment_id, receipt_number, ... }] }
// If there are unfulfilled items, pass to game server for delivery.1) Purchase prepare
string apiBase = "https://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 load
string siteBase = "https://fetamix.com";
string url = $"{siteBase}/api/webviews/purchase";
// Example using your Unity WebView plugin header-capable load API (see plugin docs)
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) Message receive
// Send message from WebView to Unity
window.Unity.call(JSON.stringify({
type: 'purchase_completed',
payment_id: paymentId,
receipt_number: receiptNumber
}));
// Receive and process in Unity
webView.OnMessage.AddListener((message) => {
var data = JsonUtility.FromJson<PurchaseCompleteData>(message);
// Call verification API
});4) Receipt verification
string apiBase = "https://api.fetamix.com";
string url = $"{apiBase}/api/v1/game/games/purchases/verify-completion";
// No token required, pass payment_id and receipt_number only.
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) Consume purchased item
string apiBase = "https://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();
// Response: { success: true, message: "..." }WebView header specification
X-Payment-ID: Purchase identifier (required)X-Point-Use-Token: One-time point use token (required)X-Access-Token: User JWT (optional)X-Item-ID: Item identifier (optional)X-User-Balance: User balance (optional)
Notes
- All calls use HTTPS.
- Sensitive data is sent via headers, not URL parameters.