reactjs - Embed Stripe Identity Verification instead of opening a lightbox with Stripe's designflow - Stack Overflow

I’m working on a Next.js (15.1.6) app with React (19.0.0) and trying to integrate Stripe Identity Verif

I’m working on a Next.js (15.1.6) app with React (19.0.0) and trying to integrate Stripe Identity Verification fully embedded within my UI—without redirecting to Stripe’s hosted page or opening a lightbox/modal via stripe.verifyIdentity. My goal is to capture photos (front ID, back ID, selfie) in my custom UI and submit them to Stripe seamlessly.

What I’ve Tried

Initially, I used [email protected] and uploaded files client-side via with purpose: 'identity_document'. After creating a VerificationSession server-side, I called stripe.verifyIdentity(clientSecret) from @stripe/stripe-js, but it opens a modal that restarts the photo capture process, ignoring my uploads.

I downgraded to [email protected] (and tested 10.17.0) hoping to use a server-side submit method to finalize the session with my uploaded files, but submit isn’t available in VerificationSessionsResource (only create, retrieve, update, list, cancel, redact). I also tried update with a hypothetical provided_documents param to link file IDs, but it’s not a valid property in VerificationSessionUpdateParams.

Here’s a simplified version of my current code with 16.12.0:

// src/lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-08-01' });

// src/app/api/identity/create-verification-session/route.ts
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(req: Request) {
  const { userId } = await req.json();
  const session = await stripe.identity.verificationSessions.create({
    type: 'document',
    options: { document: { require_matching_selfie: true } },
    metadata: { user_id: userId },
  });
  return NextResponse.json({ id: session.id, client_secret: session.client_secret });
}

// src/components/IdentityVerification.tsx
'use client';
import { useState, useRef, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export default function IdentityVerification({ userId }: { userId: string }) {
  const [session, setSession] = useState<{ id: string; client_secret: string } | null>(null);
  const [step, setStep] = useState<'id_front' | 'id_back' | 'selfie'>('id_front');
  const videoRef = useRef<HTMLVideoElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    fetch('/api/identity/create-verification-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId }),
    })
      .then((res) => res.json())
      .then(setSession);
  }, [userId]);

  const capturePhoto = async () => {
    const context = canvasRef.current?.getContext('2d');
    if (context && videoRef.current) {
      context.drawImage(videoRef.current, 0, 0, 300, 225);
      canvasRef.current?.toBlob(async (blob) => {
        if (!blob) return;
        const formData = new FormData();
        formData.append('file', blob, `${step}.jpg`);
        formData.append('purpose', 'identity_document');
        
        const res = await fetch('', {
          method: 'POST',
          headers: { Authorization: `Bearer ${process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}` },
          body: formData,
        });
        const data = await res.json();

        if (step === 'id_front') setStep('id_back');
        else if (step === 'id_back') setStep('selfie');
        else if (step === 'selfie') submitVerification();
      }, 'image/jpeg');
    }
  };

  const submitVerification = async () => {
    if (!session) return;
    const stripe = await stripePromise;
    await stripe?.verifyIdentity(session.client_secret); // Triggers modal
  };

  return (
    <div>
      <video ref={videoRef} autoPlay />
      <canvas ref={canvasRef} width="300" height="225" className="hidden" />
      <button onClick={capturePhoto}>Capture Photo</button>
    </div>
  );
}

The Problem

  • verifyIdentity opens a modal, duplicating the photo capture process.

  • No server-side submit or way to link files to the session without the modal.

  • The Stripe docs () suggest create and verifyIdentity, but nothing about embedding without a lightbox.

Questions

  1. Is it possible to embed Stripe Identity Verification fully in my Next.js app without a modal/lightbox, using my own photo capture UI?

  2. If submit was removed, is there an alternative server-side method in 16.12.0 (or any version) to finalize a session with custom-uploaded files?

  3. Has anyone successfully bypassed verifyIdentity’s modal while still verifying files?

Any insights, workarounds, or confirmation that this isn’t possible would be greatly appreciated! I’d rather not downgrade further unless necessary, but I’m open to suggestions.

I’m working on a Next.js (15.1.6) app with React (19.0.0) and trying to integrate Stripe Identity Verification fully embedded within my UI—without redirecting to Stripe’s hosted page or opening a lightbox/modal via stripe.verifyIdentity. My goal is to capture photos (front ID, back ID, selfie) in my custom UI and submit them to Stripe seamlessly.

What I’ve Tried

Initially, I used [email protected] and uploaded files client-side via https://files.stripe/v1/files with purpose: 'identity_document'. After creating a VerificationSession server-side, I called stripe.verifyIdentity(clientSecret) from @stripe/stripe-js, but it opens a modal that restarts the photo capture process, ignoring my uploads.

I downgraded to [email protected] (and tested 10.17.0) hoping to use a server-side submit method to finalize the session with my uploaded files, but submit isn’t available in VerificationSessionsResource (only create, retrieve, update, list, cancel, redact). I also tried update with a hypothetical provided_documents param to link file IDs, but it’s not a valid property in VerificationSessionUpdateParams.

Here’s a simplified version of my current code with 16.12.0:

// src/lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-08-01' });

// src/app/api/identity/create-verification-session/route.ts
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(req: Request) {
  const { userId } = await req.json();
  const session = await stripe.identity.verificationSessions.create({
    type: 'document',
    options: { document: { require_matching_selfie: true } },
    metadata: { user_id: userId },
  });
  return NextResponse.json({ id: session.id, client_secret: session.client_secret });
}

// src/components/IdentityVerification.tsx
'use client';
import { useState, useRef, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export default function IdentityVerification({ userId }: { userId: string }) {
  const [session, setSession] = useState<{ id: string; client_secret: string } | null>(null);
  const [step, setStep] = useState<'id_front' | 'id_back' | 'selfie'>('id_front');
  const videoRef = useRef<HTMLVideoElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    fetch('/api/identity/create-verification-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId }),
    })
      .then((res) => res.json())
      .then(setSession);
  }, [userId]);

  const capturePhoto = async () => {
    const context = canvasRef.current?.getContext('2d');
    if (context && videoRef.current) {
      context.drawImage(videoRef.current, 0, 0, 300, 225);
      canvasRef.current?.toBlob(async (blob) => {
        if (!blob) return;
        const formData = new FormData();
        formData.append('file', blob, `${step}.jpg`);
        formData.append('purpose', 'identity_document');
        
        const res = await fetch('https://files.stripe/v1/files', {
          method: 'POST',
          headers: { Authorization: `Bearer ${process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}` },
          body: formData,
        });
        const data = await res.json();

        if (step === 'id_front') setStep('id_back');
        else if (step === 'id_back') setStep('selfie');
        else if (step === 'selfie') submitVerification();
      }, 'image/jpeg');
    }
  };

  const submitVerification = async () => {
    if (!session) return;
    const stripe = await stripePromise;
    await stripe?.verifyIdentity(session.client_secret); // Triggers modal
  };

  return (
    <div>
      <video ref={videoRef} autoPlay />
      <canvas ref={canvasRef} width="300" height="225" className="hidden" />
      <button onClick={capturePhoto}>Capture Photo</button>
    </div>
  );
}

The Problem

  • verifyIdentity opens a modal, duplicating the photo capture process.

  • No server-side submit or way to link files to the session without the modal.

  • The Stripe docs (https://docs.stripe/identity/verification-sessions) suggest create and verifyIdentity, but nothing about embedding without a lightbox.

Questions

  1. Is it possible to embed Stripe Identity Verification fully in my Next.js app without a modal/lightbox, using my own photo capture UI?

  2. If submit was removed, is there an alternative server-side method in 16.12.0 (or any version) to finalize a session with custom-uploaded files?

  3. Has anyone successfully bypassed verifyIdentity’s modal while still verifying files?

Any insights, workarounds, or confirmation that this isn’t possible would be greatly appreciated! I’d rather not downgrade further unless necessary, but I’m open to suggestions.

Share Improve this question asked Feb 21 at 3:52 William HartmanWilliam Hartman 431 silver badge3 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Stripe Identity does not allow you to use your own photo capture UI, or pass in your own files via the API to be verified.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745167678a4614732.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信