调用Stripe获取银行账户Token遇400错误,求正确实现方案
Hey there, let's walk through fixing that 400 error and cover the best practices for getting a Stripe bank account token. First, let's break down what's wrong with your current code, then show you the correct implementation—including a critical security note you might have missed.
What's Causing the 400 Error?
Your code has two main issues triggering the bad request:
- Incorrect Request Body Format: Stripe's API expects
application/x-www-form-urlencodeddata, but you're passing a raw JSON object. Even with the right Content-Type header, Angular'sHttpClientwon't auto-convert the object to form-encoded data for you. - Incorrect Parameter Structure: Stripe requires bank account details to be nested under a
bank_accountkey (e.g.,bank_account[country]), not top-level keys likecountry. - Bonus: Misplaced Success Check: Stripe's token response doesn't include a
status: "successful"field—instead, a successful response will have anid(the token itself).
Fixed Implementation (Direct API Call)
If you need to stick with a direct API call (though we'll cover a better approach below), here's how to adjust your code:
First, add a helper function to encode your object into form-encoded data:
private encodeFormData(data: Record<string, string>): string { return Object.keys(data) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`) .join('&'); }
Then update your token fetching function:
getTokenOFBankAccount(myObject: any): Observable<any> { this.spinner.show(); // Format data correctly with nested bank_account keys const formData = this.encodeFormData({ 'bank_account[country]': myObject.country, 'bank_account[currency]': myObject.currency, 'bank_account[routing_number]': myObject.routing_number, 'bank_account[account_number]': myObject.account_number, 'bank_account[account_holder_name]': myObject.account_holder_name, 'bank_account[account_holder_type]': myObject.account_holder_type, }); const headers = new HttpHeaders({ Authorization: `Bearer ${environment.stripeToken}`, 'Content-Type': 'application/x-www-form-urlencoded' }); return this.http.post<any>("https://api.stripe.com/v1/tokens", formData, { headers }) .pipe( tap(response => { this.spinner.hide(); // Check for the token ID instead of a "successful" status if (response.id) { console.log('Bank account token generated:', response.id); // Handle successful token retrieval here } }), catchError(error => { this.spinner.hide(); // Stripe returns detailed error info in error.error console.error('Token generation failed:', error.error.message); return throwError(() => new Error(error.error.message)); }) ); }
Critical Security Note: Never Expose Your Stripe Secret Key in Frontend Code!
Wait a second—if environment.stripeToken is your Stripe Secret Key, you're making a huge security mistake. Secret Keys should never be used in client-side code (they can be easily extracted by anyone inspecting your app).
Instead, use Stripe's client-side libraries (Stripe.js/Elements) which only require your Publishable Key, and handle sensitive data securely without exposing your Secret Key.
Best Practice: Use Stripe Elements (Recommended)
Stripe Elements is the safest and easiest way to collect bank account details and generate tokens. It handles PCI compliance, form validation, and avoids exposing sensitive keys. Here's how to implement it:
- Install the necessary packages:
npm install @stripe/stripe-js @stripe/react-stripe-js
- Initialize Stripe with your Publishable Key:
// src/app/stripe.config.ts import { loadStripe } from '@stripe/stripe-js'; export const stripePromise = loadStripe(environment.stripePublishableKey);
- Create a bank account form component using Elements:
import { Elements, BankAccountElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { useState } from 'react'; function BankAccountForm() { const stripe = useStripe(); const elements = useElements(); const [loading, setLoading] = useState(false); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!stripe || !elements) return; setLoading(true); // Create token using Stripe Elements const result = await stripe.createToken(elements.getElement(BankAccountElement), { account_holder_name: 'Jenny Rosen', account_holder_type: 'individual', }); setLoading(false); if (result.error) { console.error('Error:', result.error.message); // Show error message to user } else { console.log('Token:', result.token.id); // Send the token to your backend for further processing } }; return ( <form onSubmit={handleSubmit}> <BankAccountElement options={{ country: 'US', currency: 'usd', style: { base: { fontSize: '16px' } } }} /> <button type="submit" disabled={!stripe || loading}> {loading ? 'Loading...' : 'Generate Token'} </button> </form> ); } // Wrap your form in Elements in your parent component export function App() { return ( <Elements stripe={stripePromise}> <BankAccountForm /> </Elements> ); }
Why This Is Better:
- PCI Compliance: Stripe Elements handles all sensitive data collection, so you don't have to worry about storing or transmitting sensitive bank details.
- Security: No Secret Key exposed in frontend code—only your Publishable Key is used client-side.
- Ease of Use: Built-in validation, error handling, and customizable UI components.
- Less Code: No need to manually encode form data or handle low-level API calls.
Final Tips
- Always check Stripe's error response body (in
error.error) for specific details about why the 400 error occurred—this will help you debug issues quickly. - For testing, use Stripe's official test bank account details: routing number
110000000and account number000123456789(which you're already using, good job!).
内容的提问来源于stack exchange,提问作者Ahmad Awais

