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_ACCESS_KEY_ID=$PROD_AWS_ACCESS_KEY_ID
|
||||||
AWS_SECRET_ACCESS_KEY=$PROD_AWS_SECRET_ACCESS_KEY
|
AWS_SECRET_ACCESS_KEY=$PROD_AWS_SECRET_ACCESS_KEY
|
||||||
SES_FROM_EMAIL=$PROD_SES_FROM_EMAIL
|
SES_FROM_EMAIL=$PROD_SES_FROM_EMAIL
|
||||||
|
STRIPE_REDIRECT_URI=$PROD_SES_STRIPE_REDIRECT_URI
|
||||||
else
|
else
|
||||||
STRIPE_SECRET_KEY="$DEV_STRIPE_SECRET_KEY"
|
STRIPE_SECRET_KEY="$DEV_STRIPE_SECRET_KEY"
|
||||||
STRIPE_CLIENT_ID="$DEV_STRIPE_CLIENT_ID"
|
STRIPE_CLIENT_ID="$DEV_STRIPE_CLIENT_ID"
|
||||||
|
|
@ -136,6 +137,7 @@ jobs:
|
||||||
AWS_ACCESS_KEY_ID=$DEV_AWS_ACCESS_KEY_ID
|
AWS_ACCESS_KEY_ID=$DEV_AWS_ACCESS_KEY_ID
|
||||||
AWS_SECRET_ACCESS_KEY=$DEV_AWS_SECRET_ACCESS_KEY
|
AWS_SECRET_ACCESS_KEY=$DEV_AWS_SECRET_ACCESS_KEY
|
||||||
SES_FROM_EMAIL=$DEV_SES_FROM_EMAIL
|
SES_FROM_EMAIL=$DEV_SES_FROM_EMAIL
|
||||||
|
STRIPE_REDIRECT_URI=$DEV_SES_STRIPE_REDIRECT_URI
|
||||||
fi
|
fi
|
||||||
: "${STRIPE_SECRET_KEY:?missing STRIPE_SECRET_KEY}"
|
: "${STRIPE_SECRET_KEY:?missing STRIPE_SECRET_KEY}"
|
||||||
: "${STRIPE_CLIENT_ID:?missing STRIPE_CLIENT_ID}"
|
: "${STRIPE_CLIENT_ID:?missing STRIPE_CLIENT_ID}"
|
||||||
|
|
@ -145,8 +147,9 @@ jobs:
|
||||||
: "${AWS_ACCESS_KEY_ID:?missing AWS_ACCESS_KEY_ID}"
|
: "${AWS_ACCESS_KEY_ID:?missing AWS_ACCESS_KEY_ID}"
|
||||||
: "${AWS_SECRET_ACCESS_KEY:?missing AWS_SECRET_ACCESS_KEY}"
|
: "${AWS_SECRET_ACCESS_KEY:?missing AWS_SECRET_ACCESS_KEY}"
|
||||||
: "${SES_FROM_EMAIL:?missing SES_FROM_EMAIL}"
|
: "${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 \
|
envsubst < stripe_to_invoice/deployment/secrets/stripe-secrets.yaml \
|
||||||
| kubectl apply -f -
|
| 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
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="you@company.com"
|
placeholder="enter@email.com"
|
||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,12 @@ spec:
|
||||||
name: stripe-secrets
|
name: stripe-secrets
|
||||||
key: SES_FROM_EMAIL
|
key: SES_FROM_EMAIL
|
||||||
|
|
||||||
|
- name: STRIPE_REDIRECT_URI
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: stripe-secrets
|
||||||
|
key: STRIPE_REDIRECT_URI
|
||||||
|
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: registrypullsecret
|
- name: registrypullsecret
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ DEV_AWS_REGION=eu-west-2
|
||||||
DEV_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
DEV_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
||||||
DEV_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
DEV_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
||||||
DEV_SES_FROM_EMAIL=no-reply@juntekim.com
|
DEV_SES_FROM_EMAIL=no-reply@juntekim.com
|
||||||
|
PROD_STRIPE_REDIRECT_URI=https://stripe-to-invoice.dev.juntekim.com/api/stripe/callback
|
||||||
|
|
||||||
|
|
||||||
# Prod
|
# Prod
|
||||||
|
|
@ -16,4 +17,5 @@ PROD_AWS_REGION=eu-west-2
|
||||||
PROD_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
PROD_AWS_ACCESS_KEY_ID=AKIAQL67W6HI2547OPVG
|
||||||
PROD_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
PROD_AWS_SECRET_ACCESS_KEY=qCTirw/OCdw6P2aVknGlyh8MQVMmOkrm0NrXTz4j
|
||||||
PROD_SES_FROM_EMAIL=no-reply@juntekim.com
|
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