您好,当我尝试使用 Stripe 让 pur 用户进行订阅时,出现错误。
我使用 Ionic 5 Angular 14 Capacitor 5 和 php 作为后端。 Stripe 在 PHP 上实现得很好,因为我在其他场合使用它。
控制台上显示错误
subscription.page.ts:158 Exception during payment confirmation: IntegrationError:
Invalid value for stripe.confirmCardPayment intent secret: value should be a PaymentIntent client secret.
You specified: a SetupIntent client secret.
这是我的前端功能
订阅.ts
import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
import { Router } from "@angular/router";
import { UserData } from 'src/app/services/user-data/user-data';
import { loadStripe, Stripe, StripeElements, StripeCardElement, StripeElementsOptions, PaymentMethod, StripeCardElementChangeEvent } from '@stripe/stripe-js';
// Custom component for Stripe card element handling (optional)
class CustomStripeCardElement {
private stripeCardElement: StripeCardElement;
paymentMethod: string | PaymentMethod;
constructor(stripeCardElement: StripeCardElement) {
this.stripeCardElement = stripeCardElement;
}
on(eventType: 'change' | 'ready' | 'focus' | 'blur' | 'escape' | 'networkschange', handler: (event: StripeCardElementChangeEvent | {}) => any): void {
(this.stripeCardElement.on as any)(eventType, handler);
}
mount(selector: string): void {
this.stripeCardElement.mount(selector);
}
}
@Component({
selector: 'app-subscription',
templateUrl: 'subscription.page.html',
styleUrls: ['subscription.page.scss'],
})
export class SubscriptionPage implements AfterViewInit {
@ViewChild('paymentElement') paymentElementRef: ElementRef;
stripe: Stripe | undefined;
elements: StripeElements | undefined;
paymentElement: StripeCardElement | CustomStripeCardElement | undefined;
clientSecret: string | undefined;
planId = 'price_1OPuphGozbMWFnurmnqgNTEu'; // Replace with your actual plan ID
customerId: string | undefined;
isPaymentElementFilled = false;
user: any = {};
email: string = '';
uid: string = '';
modalOpen: boolean = false;
cardInputFocused: boolean = false;
paymentMethod: string | PaymentMethod;
menuOpenCopyright: boolean = false;
constructor(public router: Router, public userData: UserData) {
this.user = this.userData.getUserData();
this.email = this.user.email;
}
ngAfterViewInit() {
// Fetch the clientSecret before initializing Stripe
this.userData.createCustomer(this.email).subscribe(
(customerResponse: any) => {
if (customerResponse.success) {
this.customerId = customerResponse.customer.id;
console.log('Customer created:', customerResponse.customer);
// Create SetupIntent only if necessary (e.g., for card details collection)
this.userData.createSetupIntent(this.customerId).subscribe(
(setupIntentResponse: any) => {
if (setupIntentResponse.success) {
this.clientSecret = setupIntentResponse.setupIntent.client_secret;
console.log('Setup Intent created:', setupIntentResponse.setupIntent);
// Initialize Stripe after getting clientSecret
this.initializeStripe().then(() => {
if (this.clientSecret) { // Check for valid clientSecret before creating payment element
this.createPaymentElement(this.clientSecret);
} else {
console.error('Missing clientSecret for payment element');
}
}).catch(error => {
console.error('Error initializing Stripe:', error);
});
} else {
console.error('Setup Intent Error:', setupIntentResponse.error);
}
},
(error) => console.error('Error creating SetupIntent:', error)
);
} else {
console.error('Customer Creation Error:', customerResponse.error);
}
},
(error) => console.error('Error creating customer:', error)
);
}
async initializeStripe() {
const stripePromise = loadStripe('pk_test_51HiSUoGozbMWFnurWW3azSXFZV47mcnH8p4MQG6HvbHuszrDPvUFYuq15TbVqZujcJNv4CSHSqkAkFm3pRk7nwod00iHrTzUTS'); // Replace with your publishable key
this.stripe = await stripePromise;
}
createPaymentElement(clientSecret: string) {
if (!this.stripe) {
console.error('Stripe is not initialized.');
return;
}
this.elements = this.stripe.elements({ clientSecret: clientSecret });
const paymentElementOptions = {};
const stripeCardElement = this.elements.create('payment', paymentElementOptions) as unknown as StripeCardElement;
// Choose between using the default StripeCardElement or your custom component (optional)
// this.paymentElement = stripeCardElement;
this.paymentElement = new CustomStripeCardElement(stripeCardElement); // Use custom component for more control (optional)
this.paymentElement.on('change', (event: any) => {
this.isPaymentElementFilled = event.complete;
});
this.paymentElement.mount('#payment-element');
}
async confirmPayment() {
try {
if (!this.clientSecret || !this.paymentElement) {
console.error('Missing clientSecret or payment element');
return;
}
const { paymentIntent, error } = await this.stripe.confirmCardPayment(this.clientSecret, {
payment_method: {
card: this.paymentElement as StripeCardElement, // Cast to StripeCardElement
},
});
if (error) {
console.error('Error during payment confirmation:', error);
return;
}
if (!paymentIntent) {
console.error('No paymentIntent returned');
return;
}
console.log('Payment Intent:', paymentIntent);
// Handle successful payment (e.g., call subscribe function)
this.subscribe(paymentIntent.payment_method); // Pass payment method to subscribe
} catch (err) {
console.error('Exception during payment confirmation:', err);
}
}
subscribe(paymentMethod: string | PaymentMethod | undefined) {
let paymentMethodString: string | undefined;
if (typeof paymentMethod === 'string') {
paymentMethodString = paymentMethod;
} else if (paymentMethod && typeof paymentMethod === 'object' && paymentMethod.id) {
paymentMethodString = paymentMethod.id;
} else {
console.error('Invalid or undefined payment method:', paymentMethod);
return;
}
if (paymentMethodString) {
console.log('Creating subscription with payment method:', paymentMethodString);
this.userData.createSubscription(this.customerId, this.planId, this.clientSecret, paymentMethodString).subscribe(
(response: any) => {
if (response.success) {
console.log('Subscription created successfully:', response.subscription);
// Handle successful subscription creation (e.g., navigate to confirmation page)
} else {
console.error('Error creating subscription:', response.error);
}
},
(error: any) => console.error('HTTP error creating subscription:', error)
);
} else {
console.error('Payment method string is undefined');
}
}
用户数据服务是
createCustomer(email: string) {
const url = this.appData.getApiUrl() + 'createCustomer';
const data = this.jsonToURLEncoded({
api_signature: this.api_signature,
email: email
});
return this.http.post(url, data, { headers: this.options });
}
createSetupIntent(customerId: string) {
const url = this.appData.getApiUrl() + 'createSetupIntent';
const data = this.jsonToURLEncoded({
customerId: customerId
});
return this.http.post(url, data, { headers: this.options });
};
createSubscription(customerId: string, planId: string, clientSecret: string, paymentMethod: string) {
const url = this.appData.getApiUrl() + 'createSubscription';
const data = this.jsonToURLEncoded({
customerId: customerId,
planId: planId,
clientSecret: clientSecret,
paymentMethod: paymentMethod, // Add the paymentMethod parameter here
});
return this.http.post(url, data, { headers: this.options });
}
php 函数是
function createCustomer() {
$request = \Slim\Slim::getInstance()->request();
$response['success'] = true;
$userEmail = $request->post('email');
try {
$customer = \Stripe\Customer::create([
'email' => $userEmail,
]);
$userStripeCustomerId = $customer->id;
$db = getDB();
$sql = "UPDATE users SET customer_id = :customer_id WHERE email = :email";
$stmt = $db->prepare($sql);
$stmt->bindParam(":customer_id", $userStripeCustomerId, PDO::PARAM_STR);
$stmt->bindParam(":email", $userEmail, PDO::PARAM_STR);
$stmt->execute();
$response = [
'customer' => $customer,
'success' => true,
];
} catch (\Stripe\Exception\CardException $e) {
$response = [
'error' => $e->getMessage(),
'success' => false,
];
}
echo json_encode($response);
}
function createSetupIntent() {
$request = \Slim\Slim::getInstance()->request();
$response['success'] = true;
$customerId = $request->post('customerId');
try {
$setupIntent = \Stripe\SetupIntent::create([
'customer' => $customerId,
]);
$clientSecret = $setupIntent->client_secret;
$response = [
'setupIntent' => $setupIntent,
'clientSecret' => $clientSecret,
'success' => true,
];
} catch (\Stripe\Exception\CardException $e) {
$response = [
'error' => $e->getMessage(),
'success' => false,
];
}
echo json_encode($response);
}
function createSubscription() {
$request = \Slim\Slim::getInstance()->request();
$response['success'] = true;
$customerId = $request->post('customerId');
$planId = $request->post('planId');
$paymentMethod = $request->post('paymentMethod');
try {
$subscription = \Stripe\Subscription::create([
'customer' => $customerId,
'items' => [
[
'price' => $planId,
],
],
'default_payment_method' => $paymentMethod,
]);
$response = [
'subscription' => $subscription,
'success' => true,
];
} catch (\Stripe\Exception\CardException $e) {
$response = [
'error' => $e->getMessage(),
'success' => false,
];
}
echo json_encode($response);
}
和 html
<form id="payment-form" (click)="formClick($event)">
<ion-grid>
<ion-row class="ion-align-items-center">
<ion-col size="12" class="ion-text-center">
<div id="payment-element" #paymentElement>
<!-- Elements will create form elements here -->
</div><br /><br /><br /><br />
</ion-col>
<ion-col size="12" class="ion-text-center">
<ion-button color="gold" expand="block" fill="solid" type="submit" (click)="confirmPayment()" id="submit" [disabled]="!isPaymentElementFilled">
<ion-label color="dark">Subscribe for 3,99€/month</ion-label>
</ion-button>
<div id="error-message">
<!-- Display error message to your customers here -->
</div>
</ion-col>
</ion-row>
</ion-grid>
</form>
有什么帮助吗?
您分享的错误消息非常清楚:
stripe.confirmCardPayment 意图密钥的值无效:值应该是 PaymentIntent 客户端密钥。您指定了:SetupIntent 客户端密钥。
如果您要传递SetupIntent客户端密钥,那么您应该使用
stripe.confirmCardSetup()
(link)而不是stripe.confirmCardPayment()
。
另外,看起来您首先创建了一个SetupIntent,然后创建了一个订阅?如果是这样,这不是一个好主意,因为它可能会导致连续出现两个 3DS 流。相反,您应该直接创建订阅,然后使用订阅创建的 PaymentIntent/SetupIntent。 本指南中有详细介绍。