add: sign in with chatgpt credits (#974)
This commit is contained in:
@@ -3,6 +3,7 @@ import type { Request, Response } from "express";
|
|||||||
|
|
||||||
import { ApiKeyPrompt, WaitingForAuth } from "./get-api-key-components";
|
import { ApiKeyPrompt, WaitingForAuth } from "./get-api-key-components";
|
||||||
import { clearTerminal } from "./terminal";
|
import { clearTerminal } from "./terminal";
|
||||||
|
import chalk from "chalk";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import { render } from "ink";
|
import { render } from "ink";
|
||||||
@@ -189,10 +190,10 @@ async function handleCallback(
|
|||||||
);
|
);
|
||||||
const chatgptPlanType =
|
const chatgptPlanType =
|
||||||
accessTokenClaims["https://api.openai.com/auth"]?.chatgpt_plan_type;
|
accessTokenClaims["https://api.openai.com/auth"]?.chatgpt_plan_type;
|
||||||
let needsSetup = false;
|
const isOrgOwner = Boolean(
|
||||||
if (chatgptPlanType === "plus" || chatgptPlanType === "pro") {
|
idTokenClaims["https://api.openai.com/auth"]?.is_org_owner,
|
||||||
needsSetup = !completedOnboarding;
|
);
|
||||||
}
|
const needsSetup = !completedOnboarding && isOrgOwner;
|
||||||
|
|
||||||
// Build the success URL on the same host/port as the callback and
|
// Build the success URL on the same host/port as the callback and
|
||||||
// include the required query parameters for the front-end page.
|
// include the required query parameters for the front-end page.
|
||||||
@@ -230,6 +231,58 @@ async function handleCallback(
|
|||||||
console.warn("Unable to save auth file:", err);
|
console.warn("Unable to save auth file:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!needsSetup &&
|
||||||
|
(chatgptPlanType === "plus" || chatgptPlanType === "pro")
|
||||||
|
) {
|
||||||
|
const apiHost =
|
||||||
|
issuer === "https://auth.openai.com"
|
||||||
|
? "https://api.openai.com"
|
||||||
|
: "https://api.openai.org";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const redeemRes = await fetch(`${apiHost}/v1/billing/redeem_credits`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id_token: tokenData.id_token }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!redeemRes.ok) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`Credit redemption request failed: ${redeemRes.status} ${redeemRes.statusText}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Attempt to parse the JSON response and surface a success message
|
||||||
|
try {
|
||||||
|
const redeemData = (await redeemRes.json()) as {
|
||||||
|
granted_chatgpt_subscriber_api_credits?: number;
|
||||||
|
};
|
||||||
|
const granted =
|
||||||
|
redeemData?.granted_chatgpt_subscriber_api_credits ?? 0;
|
||||||
|
if (granted > 0) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`\u2728 Granted ${chatgptPlanType === "plus" ? "$5" : "$50"} in API credits for being a ChatGPT ${
|
||||||
|
chatgptPlanType === "plus" ? "Plus" : "Pro"
|
||||||
|
} subscriber!`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (parseErr) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("Unable to parse credit redemption response:", parseErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("Unable to redeem ChatGPT subscriber API credits:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access_token: exchanged.access_token,
|
access_token: exchanged.access_token,
|
||||||
success_url: successUrl.toString(),
|
success_url: successUrl.toString(),
|
||||||
@@ -363,11 +416,62 @@ const LOGIN_SUCCESS_HTML = String.raw`
|
|||||||
</div>
|
</div>
|
||||||
<div class="title">Signed in to Codex CLI</div>
|
<div class="title">Signed in to Codex CLI</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-box">
|
<div class="close-box" style="display: none;">
|
||||||
<div class="setup-description">You may now close this page</div>
|
<div class="setup-description">You may now close this page</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setup-box" style="display: none;">
|
||||||
|
<div class="setup-content">
|
||||||
|
<div class="setup-text">
|
||||||
|
<div class="setup-title">Finish setting up your API organization</div>
|
||||||
|
<div class="setup-description">Add a payment method to use your organization.</div>
|
||||||
|
</div>
|
||||||
|
<div class="redirect-box">
|
||||||
|
<div data-hasendicon="false" data-hasstarticon="false" data-ishovered="false" data-isinactive="false" data-ispressed="false" data-size="large" data-type="primary" class="redirect-button">
|
||||||
|
<div class="redirect-text">Redirecting in 3s...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const needsSetup = params.get('needs_setup') === 'true';
|
||||||
|
const platformUrl = params.get('platform_url') || 'https://platform.openai.com';
|
||||||
|
const orgId = params.get('org_id');
|
||||||
|
const projectId = params.get('project_id');
|
||||||
|
const planType = params.get('plan_type');
|
||||||
|
const idToken = params.get('id_token');
|
||||||
|
// Show different message and optional redirect when setup is required
|
||||||
|
if (needsSetup) {
|
||||||
|
const setupBox = document.querySelector('.setup-box');
|
||||||
|
setupBox.style.display = 'flex';
|
||||||
|
const redirectUrlObj = new URL('/org-setup', platformUrl);
|
||||||
|
redirectUrlObj.searchParams.set('p', planType);
|
||||||
|
redirectUrlObj.searchParams.set('t', idToken);
|
||||||
|
redirectUrlObj.searchParams.set('with_org', orgId);
|
||||||
|
redirectUrlObj.searchParams.set('project_id', projectId);
|
||||||
|
const redirectUrl = redirectUrlObj.toString();
|
||||||
|
const message = document.querySelector('.redirect-text');
|
||||||
|
let countdown = 3;
|
||||||
|
function tick() {
|
||||||
|
message.textContent =
|
||||||
|
'Redirecting in ' + countdown + 's…';
|
||||||
|
if (countdown === 0) {
|
||||||
|
window.location.replace(redirectUrl);
|
||||||
|
} else {
|
||||||
|
countdown -= 1;
|
||||||
|
setTimeout(tick, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
} else {
|
||||||
|
const closeBox = document.querySelector('.close-box');
|
||||||
|
closeBox.style.display = 'flex';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user