在 Stripe 上创建 PaymentIntent 客户端密钥

问题描述 投票:0回答:1

您好,当我尝试使用 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>

有什么帮助吗?

php angular stripe-payments
1个回答
0
投票

您分享的错误消息非常清楚:

stripe.confirmCardPayment 意图密钥的值无效:值应该是 PaymentIntent 客户端密钥。您指定了:SetupIntent 客户端密钥。

如果您要传递SetupIntent客户端密钥,那么您应该使用

stripe.confirmCardSetup()
link)而不是
stripe.confirmCardPayment()

另外,看起来您首先创建了一个SetupIntent,然后创建了一个订阅?如果是这样,这不是一个好主意,因为它可能会导致连续出现两个 3DS 流。相反,您应该直接创建订阅,然后使用订阅创建的 PaymentIntent/SetupIntent。 本指南中有详细介绍。

© www.soinside.com 2019 - 2024. All rights reserved.