added stripe callback
This commit is contained in:
parent
c1ddbee66a
commit
3511fabc51
6 changed files with 127 additions and 2 deletions
5
.github/workflows/stripe-to-invoice.yml
vendored
5
.github/workflows/stripe-to-invoice.yml
vendored
|
|
@ -128,6 +128,7 @@ jobs:
|
|||
AWS_ACCESS_KEY_ID=$PROD_AWS_ACCESS_KEY_ID
|
||||
AWS_SECRET_ACCESS_KEY=$PROD_AWS_SECRET_ACCESS_KEY
|
||||
SES_FROM_EMAIL=$PROD_SES_FROM_EMAIL
|
||||
STRIPE_REDIRECT_URI=$PROD_SES_STRIPE_REDIRECT_URI
|
||||
else
|
||||
STRIPE_SECRET_KEY="$DEV_STRIPE_SECRET_KEY"
|
||||
STRIPE_CLIENT_ID="$DEV_STRIPE_CLIENT_ID"
|
||||
|
|
@ -136,6 +137,7 @@ jobs:
|
|||
AWS_ACCESS_KEY_ID=$DEV_AWS_ACCESS_KEY_ID
|
||||
AWS_SECRET_ACCESS_KEY=$DEV_AWS_SECRET_ACCESS_KEY
|
||||
SES_FROM_EMAIL=$DEV_SES_FROM_EMAIL
|
||||
STRIPE_REDIRECT_URI=$DEV_SES_STRIPE_REDIRECT_URI
|
||||
fi
|
||||
: "${STRIPE_SECRET_KEY:?missing STRIPE_SECRET_KEY}"
|
||||
: "${STRIPE_CLIENT_ID:?missing STRIPE_CLIENT_ID}"
|
||||
|
|
@ -145,8 +147,9 @@ jobs:
|
|||
: "${AWS_ACCESS_KEY_ID:?missing AWS_ACCESS_KEY_ID}"
|
||||
: "${AWS_SECRET_ACCESS_KEY:?missing AWS_SECRET_ACCESS_KEY}"
|
||||
: "${SES_FROM_EMAIL:?missing SES_FROM_EMAIL}"
|
||||
: "${STRIPE_REDIRECT_URI:?missing STRIPE_REDIRECT_URI}"
|
||||
|
||||
export STRIPE_SECRET_KEY STRIPE_CLIENT_ID NAMESPACE APP_URL AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY SES_FROM_EMAIL
|
||||
export STRIPE_SECRET_KEY STRIPE_CLIENT_ID NAMESPACE APP_URL AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY SES_FROM_EMAIL STRIPE_REDIRECT_URI
|
||||
|
||||
envsubst < stripe_to_invoice/deployment/secrets/stripe-secrets.yaml \
|
||||
| kubectl apply -f -
|
||||
|
|
|
|||
88
stripe_to_invoice/app/api/stripe/callback/route.ts
Normal file
88
stripe_to_invoice/app/api/stripe/callback/route.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
type StripeOAuthResponse = {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
stripe_user_id: string;
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const cookieStore = await cookies();
|
||||
const session = cookieStore.get("session");
|
||||
|
||||
// Safety: user must still be logged in
|
||||
if (!session) {
|
||||
return NextResponse.redirect(
|
||||
new URL("/login", process.env.NEXT_PUBLIC_BASE_URL)
|
||||
);
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const code = searchParams.get("code");
|
||||
const error = searchParams.get("error");
|
||||
|
||||
if (error) {
|
||||
console.error("Stripe OAuth error:", error);
|
||||
return NextResponse.redirect(
|
||||
new URL("/connect/stripe?error=oauth_failed", process.env.NEXT_PUBLIC_BASE_URL)
|
||||
);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing OAuth code" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Exchange code for access token
|
||||
const tokenRes = await fetch("https://connect.stripe.com/oauth/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
client_secret: process.env.STRIPE_SECRET_KEY!,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!tokenRes.ok) {
|
||||
const text = await tokenRes.text();
|
||||
console.error("Stripe token exchange failed:", text);
|
||||
|
||||
return NextResponse.redirect(
|
||||
new URL("/connect/stripe?error=token_exchange_failed", process.env.NEXT_PUBLIC_BASE_URL)
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await tokenRes.json()) as StripeOAuthResponse;
|
||||
|
||||
/**
|
||||
* TODO (NEXT STEP):
|
||||
* - Encrypt tokens
|
||||
* - Persist to DB against the current user
|
||||
*
|
||||
* Required fields:
|
||||
* - data.stripe_user_id (acct_...)
|
||||
* - data.access_token
|
||||
* - data.refresh_token
|
||||
* - mode: "test"
|
||||
*/
|
||||
|
||||
console.log("Stripe OAuth success", {
|
||||
stripe_account_id: data.stripe_user_id,
|
||||
scope: data.scope,
|
||||
has_access_token: Boolean(data.access_token),
|
||||
has_refresh_token: Boolean(data.refresh_token),
|
||||
access_token_preview: data.access_token?.slice(0, 8) + "...",
|
||||
});
|
||||
|
||||
// MVP success redirect
|
||||
return NextResponse.redirect(
|
||||
new URL("/connect/stripe/success", process.env.NEXT_PUBLIC_BASE_URL)
|
||||
);
|
||||
}
|
||||
26
stripe_to_invoice/app/api/stripe/connect/route.ts
Normal file
26
stripe_to_invoice/app/api/stripe/connect/route.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
const cookieStore = await cookies();
|
||||
const session = cookieStore.get("session");
|
||||
|
||||
// Safety: must be logged in
|
||||
if (!session) {
|
||||
return NextResponse.redirect(
|
||||
new URL("/login", process.env.NEXT_PUBLIC_BASE_URL)
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
response_type: "code",
|
||||
client_id: process.env.STRIPE_CLIENT_ID!,
|
||||
scope: "read_only",
|
||||
redirect_uri: process.env.STRIPE_REDIRECT_URI!,
|
||||
});
|
||||
|
||||
const stripeAuthUrl =
|
||||
`https://connect.stripe.com/oauth/authorize?${params.toString()}`;
|
||||
|
||||
return NextResponse.redirect(stripeAuthUrl);
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ export default function LoginPage() {
|
|||
|
||||
<input
|
||||
type="email"
|
||||
placeholder="you@company.com"
|
||||
placeholder="enter@email.com"
|
||||
className="w-full border rounded p-2"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ spec:
|
|||
name: stripe-secrets
|
||||
key: SES_FROM_EMAIL
|
||||
|
||||
- name: STRIPE_REDIRECT_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: stripe-secrets
|
||||
key: STRIPE_REDIRECT_URI
|
||||
|
||||
imagePullSecrets:
|
||||
- name: registrypullsecret
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ DEV_AWS_REGION=eu-west-2
|
|||
DEV_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
||||
DEV_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
||||
DEV_SES_FROM_EMAIL=no-reply@juntekim.com
|
||||
PROD_STRIPE_REDIRECT_URI=https://stripe-to-invoice.dev.juntekim.com/api/stripe/callback
|
||||
|
||||
|
||||
# Prod
|
||||
|
|
@ -16,4 +17,5 @@ PROD_AWS_REGION=eu-west-2
|
|||
PROD_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
||||
PROD_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
||||
PROD_SES_FROM_EMAIL=no-reply@juntekim.com
|
||||
PROD_STRIPE_REDIRECT_URI=https://stripe-to-invoice.dev.juntekim.com/api/stripe/callback
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue