Skip to main content

Registration and Authorization (Binding)

Users scan a web QR code with the PalmxApp to associate their Palm account with a user account in your system.

Architecture

The SDK uses an iframe + postMessage architecture, completely avoiding cross-origin issues:

┌─ Customer Website (www.customer.com) ────────────────────┐
│ │
│ <script src="palm-oauth.js"> │
│ ┌─ iframe (open.palmoa.youtu.qq.com/palm/x/h5/oauth/qr/page) ──┐│
│ │ ││
│ │ [QR Code] ← Same-origin request, no CORS ││
│ │ "Please scan with PalmxApp to bind" ││
│ │ ││
│ │ Polling status... ││
│ │ Binding success → postMessage({code, state}) ──── ││──→ SDK receives
│ └──────────────────────────────────────────────────────┘│
│ │
│ SDK returns { code, codeVerifier, state } via Promise │
│ to the caller, who sends it to their backend for binding│
│ │
└──────────────────────────────────────────────────────────┘

Quick Start

1. Import the SDK

<script src="https://open.palmoa.youtu.qq.com/sdk/palm-oauth.js"></script>

That's it — no QR code library is needed (the QR code is rendered inside the iframe).

2. Usage

<div id="bindContainer"></div>

<script>
PalmOAuth.init({
appId: 'your_app_id',
});

PalmOAuth.bind('#bindContainer')
.then(function (result) {
// result = { code, codeVerifier, state }
// Send the result to your backend to complete the account binding
fetch('/api/bindPalm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userToken, // Your system's auth token
},
body: JSON.stringify(result),
}).then(function () {
alert('Binding successful!');
});
})
.catch(function (err) {
console.error('Binding failed:', err.message);
});
</script>

Configuration

PalmOAuth.init({
// Required
appId: 'your_app_id',

// Optional
baseUrl: 'https://open.palmoa.youtu.qq.com', // API domain
width: 300, // iframe width in px
height: 360, // iframe height in px
lang: 'zh-CN', // Language: zh-CN / en

// Event callbacks
onStateChange: function (status) {}, // pending / scanned / confirmed / denied / expired
onSuccess: function (result) {}, // result = { code, codeVerifier, state }
onError: function (err) {}, // Error
});

API

MethodDescription
PalmOAuth.init(options)Initialize configuration
PalmOAuth.bind(container)Display QR code in the specified container, start the binding flow, returns a Promise
PalmOAuth.cancel()Cancel the current binding flow, remove the iframe
PalmOAuth.destroy()Destroy the instance

Backend Integration

After the SDK binding succeeds, the Promise resolves with the following data:

{
code: "f30cb9df-dc28-4a28-858e-0eb7c6f59c67", // Authorization code
codeVerifier: "P25ffL26KVLZozkFdAQ2yxRZADK9Y88NDu8WlIYhdA", // PKCE verifier
state: "state_1700000000_abc123" // Anti-CSRF random string
}

Your frontend sends it to your own backend (must include your system's user auth token so the backend knows which user is binding):

PalmOAuth.bind('#container').then(function (result) {
fetch('/api/bindPalm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userToken, // Your system's auth token (required)
},
body: JSON.stringify({
code: result.code,
code_verifier: result.codeVerifier,
}),
});
});

Your backend receives this and calls the Palm Token API to exchange for the open_id, then establishes the binding with the current logged-in user:

// POST /api/bindPalm
func BindPalmHandler(w http.ResponseWriter, r *http.Request) {
// 1. Verify the current logged-in user (from your own auth state)
currentUser := GetCurrentUser(r)

// 2. Parse the request body
var req struct {
Code string `json:"code"`
CodeVerifier string `json:"code_verifier"`
}
json.NewDecoder(r.Body).Decode(&req)

// 3. Exchange code for Palm open_id
body, _ := json.Marshal(map[string]string{
"GrantType": "authorization_code",
"Code": req.Code,
"CodeVerifier": req.CodeVerifier,
})
httpReq, _ := http.NewRequest("POST",
"https://open.palmoa.youtu.qq.com/palm/openai/x/oauth/token",
bytes.NewReader(body))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+API_KEY)

resp, _ := http.DefaultClient.Do(httpReq)
defer resp.Body.Close()

var result struct {
Data struct {
OpenId string `json:"OpenId"`
AccessToken string `json:"AccessToken"`
RefreshToken string `json:"RefreshToken"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)

// 4. Bind & save tokens
db.BindPalmAccount(currentUser.ID, result.Data.OpenId)
db.SavePalmTokens(currentUser.ID, result.Data.AccessToken, result.Data.RefreshToken)

json.NewEncoder(w).Encode(map[string]interface{}{
"success": true, "message": "Binding successful",
})
}

Complete HTML Example

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bind Palm Account</title>
<style>
body {
display: flex; justify-content: center; align-items: center;
min-height: 100vh; margin: 0;
font-family: -apple-system, sans-serif; background: #f5f7fa;
}
.bind-card {
background: #fff; border-radius: 12px; padding: 32px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08); text-align: center;
}
.bind-card h2 { margin: 0 0 8px; color: #333; }
.bind-card p { margin: 0 0 20px; color: #666; font-size: 14px; }
</style>
</head>
<body>
<div class="bind-card">
<h2>Bind Palm Account</h2>
<p>Scan the QR code below with PalmxApp to link your Palm account to this website</p>
<div id="bindContainer"></div>
</div>

<script src="https://open.palmoa.youtu.qq.com/sdk/palm-oauth.js"></script>
<script>
PalmOAuth.init({
appId: 'your_app_id',
onStateChange: function (s) { console.log('Status:', s); },
});

PalmOAuth.bind('#bindContainer')
.then(function (result) {
// Note: the backend expects the field name code_verifier (underscore), while the SDK returns codeVerifier (camelCase)
fetch('/api/bindPalm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: result.code,
code_verifier: result.codeVerifier,
}),
}).then(function () {
alert('Binding successful!');
window.location.reload();
});
})
.catch(function (e) { alert('Binding failed: ' + e.message); });
</script>
</body>
</html>