Changeset ec288dc in Klonkt for src/services
- Timestamp:
- 07/21/2026 03:11:39 AM (7 weeks ago)
- Branches:
- main
- Children:
- dede82e
- Parents:
- c7a211d
- git-author:
- Robin <roboburr@…> (07/21/2026 03:11:37 AM)
- git-committer:
- Robin <roboburr@…> (07/21/2026 03:11:39 AM)
- File:
-
- 1 edited
-
src/services/PaidPatreonService.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/services/PaidPatreonService.js
rc7a211d rec288dc 164 164 165 165 // Exchange a patron's auth code and read their membership of the owner's 166 // campaign. Returns { status, cents } or null. The patron token is used once 167 // and discarded here: nothing identifying is stored (design decision). 166 // campaign. Returns { status, cents, diag } (status null = not a patron); the 167 // `diag` string is a NON-identifying breadcrumb (campaign ids + status + cents) 168 // so a stuck owner can see why. Returns null only on hard misconfig. The patron 169 // token is used once and discarded here: nothing identifying is stored. 168 170 export async function verifyPatron(siteId, code, redirectUri, fetchImpl = fetch) { 169 171 const c = getOwnerConfig(siteId); 170 172 if (!c || !c.clientId || !c.clientSecret || !c.campaignId) return null; 171 const tokenRes = await fetchImpl(TOKEN_URL, { 172 method: 'POST', 173 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 174 body: new URLSearchParams({ 175 grant_type: 'authorization_code', code, 176 client_id: c.clientId, client_secret: c.clientSecret, redirect_uri: redirectUri, 177 }).toString(), 178 }); 179 if (!tokenRes.ok) return null; 173 const none = (diag) => ({ status: null, cents: 0, diag }); 174 let tokenRes; 175 try { 176 tokenRes = await fetchImpl(TOKEN_URL, { 177 method: 'POST', 178 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 179 body: new URLSearchParams({ 180 grant_type: 'authorization_code', code, 181 client_id: c.clientId, client_secret: c.clientSecret, redirect_uri: redirectUri, 182 }).toString(), 183 }); 184 } catch { return none('token_fetch_error'); } 185 if (!tokenRes.ok) return none(`token_http_${tokenRes.status}`); 180 186 const tok = await tokenRes.json(); 181 if (!tok || !tok.access_token) return n ull;187 if (!tok || !tok.access_token) return none('no_access_token'); 182 188 const url = 'https://www.patreon.com/api/oauth2/v2/identity' 183 189 + '?include=memberships.campaign' 184 190 + '&fields%5Bmember%5D=patron_status,currently_entitled_amount_cents'; 185 191 const idRes = await fetchImpl(url, { headers: { Authorization: `Bearer ${tok.access_token}` } }); 186 if (!idRes.ok) return n ull;192 if (!idRes.ok) return none(`identity_http_${idRes.status}`); 187 193 const identity = await idRes.json(); 188 194 const membership = pickCampaignMembership(identity, c.campaignId); // token goes out of scope, discarded 189 // Diagnostic (no identity stored, only shapes): why did a real supporter get 190 // rejected? Logs the memberships Patreon returned vs the configured campaign. 191 // Nothing here is persisted; it's a one-line server log for the owner. 192 if (!membership || membership.status !== 'active_patron') { 193 const seen = ((identity && identity.included) || []) 194 .filter((it) => it.type === 'member') 195 .map((it) => { 196 const camp = it.relationships && it.relationships.campaign && it.relationships.campaign.data; 197 const a = it.attributes || {}; 198 return `${camp ? camp.id : '?'}:${a.patron_status || 'null'}:${a.currently_entitled_amount_cents || 0}c`; 199 }); 200 console.warn(`[paid] verifyPatron: config campaign=${c.campaignId} → memberships seen=[${seen.join(', ') || 'none'}] picked=${membership ? membership.status + '/' + membership.cents + 'c' : 'null'}`); 201 } 202 return membership; 195 const seen = ((identity && identity.included) || []) 196 .filter((it) => it.type === 'member') 197 .map((it) => { 198 const camp = it.relationships && it.relationships.campaign && it.relationships.campaign.data; 199 const a = it.attributes || {}; 200 return `${camp ? camp.id : '?'}:${a.patron_status || 'null'}:${a.currently_entitled_amount_cents || 0}c`; 201 }); 202 const diag = `campaign=${c.campaignId} seen=[${seen.join(', ') || 'none'}] picked=${membership ? membership.status + '/' + membership.cents + 'c' : 'null'}`; 203 if (!membership || membership.status !== 'active_patron') console.warn(`[paid] verifyPatron: ${diag}`); 204 return { status: membership ? membership.status : null, cents: membership ? membership.cents : 0, diag }; 203 205 } 204 206
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)