How do i fix Stripe Payment for Connected Accounts

Discussão em 'Angular' iniciado por Pella Post, Janeiro 13, 2025.

  1. Pella Post

    Pella Post Guest

    Hello i am having a problem with Stripe Payment from buyer. I want the buyer to be able to buy digital content from my platform and we keep a fee from the buyer. I want the purchase to be done in the backend. I use stripe in another occasion and it is implemented ok.

    1. The seller account is created just fine and seller is verified from Stripe platform.
    2. The payment from buyer is succeed at first and then failed. As i show you from my stripe test dashboard in the photo i shared i think the payment intent returns incomplete. [![enter image description here][1]][1]

    THE CODE FRONT END

    async initializePaymentElement() {
    try {
    // Collect payment details
    const paymentDetails = {
    amount: this.fractionsToPurchase * this.pricePerFraction * 100, // Amount in smallest currency unit (e.g., cents)
    currency: 'eur',
    email: this.user.email, // User email
    seller_connected_account_id: this.seller_connected_account_id, // Seller's Stripe account ID
    };

    console.log('Payment details being sent to backend:', paymentDetails);

    // Call backend to create Payment Intent
    const response: any = await this.userData.createPaymentIntent(paymentDetails).toPromise();

    console.log('Response from backend (createPaymentIntent):', response);

    if (response && response.clientSecret) {
    this.clientSecret = response.clientSecret;
    this.buyer_customer_id = response.customerId;

    console.log('Client secret received:', this.clientSecret);
    console.log('Buyer customer ID:', this.buyer_customer_id);

    // Load Stripe.js and initialize payment element
    if (!this.stripe) {
    this.stripe = await loadStripe('your-publishable-key');
    }

    if (!this.stripe) {
    console.error('Stripe.js failed to load');
    return;
    }

    this.elements = this.stripe.elements({ clientSecret: this.clientSecret });
    this.paymentElement = this.elements.create('payment');

    // Mount Payment Element
    const paymentElementContainer = document.getElementById('payment-element');
    if (paymentElementContainer) {
    this.paymentElement.mount(paymentElementContainer);
    }

    // Handle changes in the Payment Element
    this.paymentElement.on('change', (event: any) => {
    this.isPaymentElementFilled = event.complete;
    this.paymentError = event.error ? event.error.message : null;
    });
    } else {
    console.error('Failed to retrieve client secret or initialize Stripe.');
    this.paymentError = 'Failed to retrieve payment details.';
    }
    } catch (error) {
    console.error('Error during initializePaymentElement:', error);
    this.paymentError = 'Failed to initialize payment. Please try again.';
    }
    }

    async purchaseMediaFractions() {
    console.log("Starting purchaseMediaFractions...");

    // Validate `fractionsToPurchase`
    if (
    this.fractionsToPurchase > this.priceDetails?.fractions ||
    this.fractionsToPurchase < 1
    ) {
    console.error("Invalid fractions:", this.fractionsToPurchase);
    const toast = await this.toastController.create({
    message: "Please enter a valid number of fractions to purchase.",
    duration: 2000,
    color: "danger",
    });
    await toast.present();
    return;
    }

    const totalPrice = this.fractionsToPurchase * this.pricePerFraction;
    const platformFee = totalPrice * 0.1; // 10% platform fee
    const sellerEarnings = totalPrice - platformFee;

    if (!this.stripe) {
    console.error("Stripe instance is not initialized.");
    return;
    }

    const elements = this.elements;
    if (!elements) {
    console.error("Stripe Elements are not initialized.");
    return;
    }

    const paymentElement = this.paymentElement;
    if (!paymentElement) {
    console.error("Payment element is not mounted.");
    return;
    }

    try {
    // Confirm the payment with Stripe
    const { error, paymentIntent } = await this.stripe.confirmPayment({
    elements: this.elements,
    confirmParams: {
    payment_method_data: {
    billing_details: {
    email: this.user?.email, // Provide the buyer's email
    },
    },
    },
    redirect: "if_required", // Handle the redirect manually
    });

    if (error) {
    console.error("Payment confirmation error:", error.message);
    const toast = await this.toastController.create({
    message: `Payment failed: ${error.message}`,
    duration: 3000,
    color: "danger",
    });
    await toast.present();
    return;
    }

    if (paymentIntent?.status === "succeeded") {
    console.log("Payment successful:", paymentIntent);

    const toast = await this.toastController.create({
    message: "Payment successful!",
    duration: 3000,
    color: "success",
    });
    await toast.present();

    // Prepare purchase details for backend
    const purchaseDetails = {
    userId: this.user?.uid,
    mediaId: this.media?.msg_id,
    fractionsToPurchase: this.fractionsToPurchase,
    pricePerFraction: this.pricePerFraction,
    totalPrice,
    platformFee,
    sellerEarnings,
    sellerAccountId: this.seller_connected_account_id,
    buyerCustomerId: this.buyer_customer_id,
    };

    console.log("Purchase details:", purchaseDetails);

    // Call the backend
    this.userData.purchaseMediaFractions(purchaseDetails).subscribe(
    async (response: any) => {
    console.log("Backend response for purchaseMediaFractions:", response);

    const toast = await this.toastController.create({
    message: response.success ? response.message : response.error,
    duration: 2000,
    color: response.success ? "success" : "danger",
    });
    await toast.present();

    if (response.success) {
    console.log("Purchase completed successfully.");
    this.router.navigate(["/success"]);
    } else {
    console.error("Purchase failed:", response.error);
    }
    },
    async (error) => {
    console.error("HTTP error in purchaseMediaFractions:", error);
    const toast = await this.toastController.create({
    message: "An error occurred. Please try again later.",
    duration: 2000,
    color: "danger",
    });
    await toast.present();
    }
    );
    } else {
    console.error("Payment not completed:", paymentIntent?.status);
    const toast = await this.toastController.create({
    message: "Payment not completed.",
    duration: 3000,
    color: "warning",
    });
    await toast.present();
    }
    } catch (error) {
    console.error("Error during payment process:", error);
    const toast = await this.toastController.create({
    message: "An error occurred during payment. Please try again later.",
    duration: 3000,
    color: "danger",
    });
    await toast.present();
    }
    }


    Services userData

    createPaymentIntent(paymentDetails: any) {
    const url = this.appData.getApiUrl() + 'createPaymentIntent';
    const data = this.jsonToURLEncoded({
    api_signature: this.api_signature,
    ...paymentDetails, // Spread payment details into the request body
    });

    console.log('Calling createPaymentIntent API:', url);
    console.log('Request data:', data);

    return this.http.post(url, data, { headers: this.options }).pipe(
    tap((response: any) => {
    console.log('createPaymentIntent API response:', response);
    }),
    catchError((error) => {
    console.error('Error calling createPaymentIntent API:', error);
    throw error;
    })
    );
    }


    purchaseMediaFractions(purchaseDetails: any) {
    const url = this.appData.getApiUrl() + 'insertMediaPurchaseDetails';
    const data = {
    api_signature: this.api_signature,
    purchaseDetails: purchaseDetails // Send as a plain object
    };

    return this.http.post(url, JSON.stringify(data), {
    headers: this.options.set('Content-Type', 'application/json'),
    });
    }


    AND PHP FUNCTIONS

    function createPaymentIntent() {
    $request = \Slim\Slim::getInstance()->request();
    $response = ['success' => false];

    // Extract parameters sent from the frontend
    $apiSignature = $request->post('api_signature');
    $amount = intval($request->post('amount')); // Ensure amount is an integer
    $currency = $request->post('currency');
    $email = $request->post('email');
    $sellerAccountId = $request->post('seller_connected_account_id'); // Seller's connected account ID

    error_log("Received API Signature: $apiSignature, Amount: $amount, Currency: $currency, Email: $email, Seller Account ID: $sellerAccountId");

    try {
    // Validate parameters
    if (!$amount || $amount <= 0) {
    throw new Exception("Invalid amount: Amount must be greater than 0.");
    }

    if (empty($email)) {
    throw new Exception("Invalid email: Email address is required.");
    }

    if (empty($sellerAccountId)) {
    throw new Exception("Invalid seller account ID: This is required.");
    }

    // Create a new Stripe Customer
    $customer = \Stripe\Customer::create([
    'email' => $email,
    'description' => 'One-time customer for purchase',
    ]);
    error_log("Stripe Customer Created: " . json_encode($customer));

    $buyerCustomerId = $customer->id;
    if (empty($buyerCustomerId)) {
    throw new Exception("Failed to create a Stripe customer.");
    }

    // Calculate Platform Fee (e.g., 10%)
    $applicationFeeAmount = intval($amount * 0.10); // Platform fee

    // Create the PaymentIntent
    $paymentIntentParams = [
    'amount' => $amount, // Amount in smallest currency unit (e.g., cents)
    'currency' => $currency,
    'customer' => $buyerCustomerId,
    'description' => 'Purchase',
    'transfer_data' => [
    'destination' => $sellerAccountId, // Connected seller account
    ],
    'application_fee_amount' => $applicationFeeAmount,
    ];
    error_log("PaymentIntent Parameters: " . json_encode($paymentIntentParams));

    $paymentIntent = \Stripe\PaymentIntent::create($paymentIntentParams);
    error_log("PaymentIntent Created: " . json_encode($paymentIntent));

    // Build the response with PaymentIntent details
    $response = [
    'success' => true,
    'paymentIntentId' => $paymentIntent->id,
    'clientSecret' => $paymentIntent->client_secret,
    'customerId' => $buyerCustomerId,
    'amount' => $amount,
    'currency' => $currency,
    ];
    } catch (\Stripe\Exception\ApiErrorException $e) {
    // Stripe-specific error
    error_log("Stripe API Error: " . $e->getMessage());
    $response = [
    'success' => false,
    'error' => $e->getMessage(),
    ];
    } catch (Exception $e) {
    // General error
    error_log("General Error: " . $e->getMessage());
    $response = [
    'success' => false,
    'error' => $e->getMessage(),
    ];
    }

    // Return response as JSON
    echo json_encode($response);
    }


    function insertMediaPurchaseDetails() {
    error_log('Function Called: insertMediaPurchaseDetails');

    $request = \Slim\Slim::getInstance()->request();
    $data = json_decode($request->getBody(), true);
    $purchaseDetails = $data['purchaseDetails'] ?? null;

    error_log('Request Body: ' . $request->getBody());

    // Validate purchase details
    if (!$purchaseDetails || !isset($purchaseDetails['userId'], $purchaseDetails['mediaId'], $purchaseDetails['fractionsToPurchase'], $purchaseDetails['pricePerFraction'], $purchaseDetails['totalPrice'], $purchaseDetails['platformFee'], $purchaseDetails['sellerEarnings'])) {
    error_log('Invalid Purchase Details: ' . print_r($purchaseDetails, true));
    echo json_encode(['error' => 'Invalid purchase details provided']);
    return;
    }

    // Log extracted values
    error_log('Extracted Purchase Details: ' . print_r($purchaseDetails, true));

    // Set Stripe API key
    Stripe::setApiKey('sk_test_51HiSUoGozbMWFnurBqY9URXX7pEVd0Rwnm9kyyyXuOr9pKNluCdpNp522HiGN65djoplcuJcCKjiXqtFBgZoM4f000XfvRgSgi');

    try {
    // Create the PaymentIntent
    $paymentIntent = \Stripe\PaymentIntent::create([
    'amount' => intval($purchaseDetails['totalPrice'] * 100), // Amount in cents
    'currency' => 'eur',
    'payment_method_types' => ['card'],
    'transfer_data' => [
    'destination' => $purchaseDetails['sellerAccountId'],
    ],
    ]);
    error_log('PaymentIntent Created: ' . json_encode($paymentIntent));

    // Log PaymentIntent status
    if ($paymentIntent->status !== 'succeeded') {
    error_log('PaymentIntent Status: ' . $paymentIntent->status);
    error_log('PaymentIntent Full Response: ' . print_r($paymentIntent, true));
    echo json_encode(['error' => 'Payment failed']);
    return;
    }

    // Proceed with database operations
    $db = getDB();
    $db->beginTransaction();
    error_log('Database Transaction Started');

    // Insert purchase details
    $insertSql = "INSERT INTO media_purchases (msg_id_fk, buyer_uid_fk, fraction_count, purchase_price, total_price, platform_fee, seller_earnings, purchase_date)
    VALUES (?, ?, ?, ?, ?, ?, ?, NOW())";
    $stmt = $db->prepare($insertSql);
    if (!$stmt->execute([$purchaseDetails['mediaId'], $purchaseDetails['userId'], $purchaseDetails['fractionsToPurchase'], $purchaseDetails['pricePerFraction'], $purchaseDetails['totalPrice'], $purchaseDetails['platformFee'], $purchaseDetails['sellerEarnings']])) {
    error_log('Failed to Insert Media Purchase: ' . json_encode($stmt->errorInfo()));
    throw new Exception('Failed to insert purchase details');
    }
    error_log('Media Purchase Inserted Successfully');

    // Commit transaction
    $db->commit();
    error_log('Database Transaction Committed Successfully');

    echo json_encode(['success' => true, 'message' => 'Purchase completed successfully']);
    } catch (\Stripe\Exception\ApiErrorException $e) {
    error_log('Stripe API Error: ' . $e->getMessage());
    echo json_encode(['error' => 'Transaction failed']);
    } catch (Exception $e) {
    error_log('General Error: ' . $e->getMessage());
    if (isset($db) && $db->inTransaction()) {
    $db->rollBack();
    error_log('Database Transaction Rolled Back');
    }
    echo json_encode(['error' => 'Transaction failed']);
    }
    }


    THE CONSOLE RETURNS THESE

    Payment details being sent to backend: {amount: 300, currency: 'eur', email: 'pellapost@outlook.com', seller_connected_account_id: 'acct_1Qevz92eCjM0J1d3'}
    user-data.ts:541 Calling createPaymentIntent API: https://project.com/api/api/createPaymentIntent
    user-data.ts:542 Request data: api_signature=bcbf2fd292fa27b76d509742cdc007e2&amount=300&currency=eur&email=pellapost%40outlook.com&seller_connected_account_id=acct_1Qevz92eCjM0J1d3
    user-data.ts:546 createPaymentIntent API response: {success: true, paymentIntentId: 'pi_3QgjzmGozbMWFnur0N7W0dLL', clientSecret: 'pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A', customerId: 'cus_RZtnd76eH7eIVl', amount: 300, …}
    sell-details.page.ts:610 Response from backend (createPaymentIntent): {success: true, paymentIntentId: 'pi_3QgjzmGozbMWFnur0N7W0dLL', clientSecret: 'pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A', customerId: 'cus_RZtnd76eH7eIVl', amount: 300, …}
    sell-details.page.ts:616 Client secret received: pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A
    sell-details.page.ts:617 Buyer customer ID: cus_RZtnd76eH7eIVl

    sell-details.page.ts:654 Starting purchaseMediaFractions...
    sell-details.page.ts:718 Payment successful: {id: 'pi_3QgjzmGozbMWFnur0N7W0dLL', object: 'payment_intent', amount: 300, amount_details: {…}, automatic_payment_methods: {…}, …}

    sell-details.page.ts:740 Purchase details: {userId: '1122', mediaId: '815', fractionsToPurchase: 3, pricePerFraction: '1.00', totalPrice: 3, …}
    sell-details.page.ts:745 Backend response for purchaseMediaFractions: {error: 'Payment failed'}
    sell-details.page.ts:758 Purchase failed: Payment failed


    and the error logs from php functions are these

    [13-Jan-2025 11:31:42 Europe/Athens] Received API Signature: bcbf2fd292fa27b76d509742cdc007e2, Amount: 300, Currency: eur, Email: pellapost@outlook.com, Seller Account ID: acct_1Qevz92eCjM0J1d3
    [13-Jan-2025 11:31:43 Europe/Athens] Stripe Customer Created: {"id":"cus_RZtnd76eH7eIVl","object":"customer","address":null,"balance":0,"created":1736760702,"currency":null,"default_source":null,"delinquent":false,"description":"One-time customer for purchase","discount":null,"email":"pellapost@outlook.com","invoice_prefix":"2C322EAE","invoice_settings":{"custom_fields":null,"default_payment_method":null,"footer":null,"rendering_options":null},"livemode":false,"metadata":[],"name":null,"phone":null,"preferred_locales":[],"shipping":null,"tax_exempt":"none","test_clock":null}
    [13-Jan-2025 11:31:43 Europe/Athens] PaymentIntent Parameters: {"amount":300,"currency":"eur","customer":"cus_RZtnd76eH7eIVl","description":"Purchase","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"application_fee_amount":30}
    [13-Jan-2025 11:31:43 Europe/Athens] PaymentIntent Created: {"id":"pi_3QgjzmGozbMWFnur0N7W0dLL","object":"payment_intent","amount":300,"amount_capturable":0,"amount_details":{"tip":[]},"amount_received":0,"application":null,"application_fee_amount":30,"automatic_payment_methods":{"allow_redirects":"always","enabled":true},"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","client_secret":"pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A","confirmation_method":"automatic","created":1736760702,"currency":"eur","customer":"cus_RZtnd76eH7eIVl","description":"Purchase","invoice":null,"last_payment_error":null,"latest_charge":null,"livemode":false,"metadata":[],"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_configuration_details":{"id":"pmc_1PgT4IGozbMWFnurJXRBOp2V","parent":null},"payment_method_options":{"bancontact":{"preferred_language":"en"},"card":{"installments":null,"mandate_options":null,"network":null,"request_three_d_secure":"automatic"},"eps":[],"giropay":[],"ideal":[],"klarna":{"preferred_locale":null},"link":{"persistent_token":null}},"payment_method_types":["card","bancontact","eps","giropay","ideal","klarna","link"],"processing":null,"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"requires_payment_method","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"transfer_group":null}
    [13-Jan-2025 11:32:14 Europe/Athens] Function Called: insertMediaPurchaseDetails
    [13-Jan-2025 11:32:14 Europe/Athens] Request Body: {"api_signature":"bcbf2fd292fa27b76d509742cdc007e2","purchaseDetails":{"userId":"1122","mediaId":"815","fractionsToPurchase":3,"pricePerFraction":"1.00","totalPrice":3,"platformFee":0.30000000000000004,"sellerEarnings":2.7,"sellerAccountId":"acct_1Qevz92eCjM0J1d3","buyerCustomerId":"cus_RZtnd76eH7eIVl"}}
    [13-Jan-2025 11:32:14 Europe/Athens] Extracted Purchase Details: Array
    (
    [userId] => 1122
    [mediaId] => 815
    [fractionsToPurchase] => 3
    [pricePerFraction] => 1.00
    [totalPrice] => 3
    [platformFee] => 0.3
    [sellerEarnings] => 2.7
    [sellerAccountId] => acct_1Qevz92eCjM0J1d3
    [buyerCustomerId] => cus_RZtnd76eH7eIVl
    )

    [13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Created: {"id":"pi_3Qgk0HGozbMWFnur0JDICtzQ","object":"payment_intent","amount":300,"amount_capturable":0,"amount_details":{"tip":[]},"amount_received":0,"application":null,"application_fee_amount":null,"automatic_payment_methods":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","client_secret":"pi_3Qgk0HGozbMWFnur0JDICtzQ_secret_y4F4woUVuodAF2F4WANJl96Vm","confirmation_method":"automatic","created":1736760733,"currency":"eur","customer":null,"description":null,"invoice":null,"last_payment_error":null,"latest_charge":null,"livemode":false,"metadata":[],"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_configuration_details":null,"payment_method_options":{"card":{"installments":null,"mandate_options":null,"network":null,"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"processing":null,"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"requires_payment_method","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"transfer_group":null}
    [13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Status: requires_payment_method
    [13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Full Response: Stripe\PaymentIntent Object
    (
    [id] => pi_3Qgk0HGozbMWFnur0JDICtzQ
    [object] => payment_intent
    [amount] => 300
    [amount_capturable] => 0
    [amount_details] => Stripe\StripeObject Object
    (
    [tip] => Array
    (
    )

    )

    [amount_received] => 0
    [application] =>
    [application_fee_amount] =>
    [automatic_payment_methods] =>
    [canceled_at] =>
    [cancellation_reason] =>
    [capture_method] => automatic
    [client_secret] => pi_3Qgk0HGozbMWFnur0JDICtzQ_secret_y4F4woUVuodAF2F4WANJl96Vm
    [confirmation_method] => automatic
    [created] => 1736760733
    [currency] => eur
    [customer] =>
    [description] =>
    [invoice] =>
    [last_payment_error] =>
    [latest_charge] =>
    [livemode] =>
    [metadata] => Stripe\StripeObject Object
    (
    )

    [next_action] =>
    [on_behalf_of] =>
    [payment_method] =>
    [payment_method_configuration_details] =>
    [payment_method_options] => Stripe\StripeObject Object
    (
    [card] => Stripe\StripeObject Object
    (
    [installments] =>
    [mandate_options] =>
    [network] =>
    [request_three_d_secure] => automatic
    )

    )

    [payment_method_types] => Array
    (
    [0] => card
    )

    [processing] =>
    [receipt_email] =>
    [review] =>
    [setup_future_usage] =>
    [shipping] =>
    [source] =>
    [statement_descriptor] =>
    [statement_descriptor_suffix] =>
    [status] => requires_payment_method
    [transfer_data] => Stripe\StripeObject Object
    (
    [destination] => acct_1Qevz92eCjM0J1d3
    )

    [transfer_group] =>
    )


    What am i doing wrong? Thanks in advance [1]: https://i.sstatic.net/DdkrgZS4.png

    Continue reading...

Compartilhe esta Página