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.
- The seller account is created just fine and seller is verified from Stripe platform.
- 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: '[email protected]', 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¤cy=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: [email protected], 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":"[email protected]","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] =>
How do i fix Stripe Payment for Connected Accounts =>
[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