admin管理员组

文章数量:1415460

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.

本文标签: