I'm trying to add a stripe checkout button to my Leadpages landing page and after somebody completes a successful payment they're supposed to be redirected...but that redirect is not happening and I have no idea why.
Here's my page: http://snapstories.leadpages.co/test/... it's using test keys right now so you can test the checkout with Stripe's demo Visa number: 4242424242424242 and any expiry / security code...you'll see that you don't get redirected anywhere.
The demo-stripe.php script is supposed to send a 'success' response to my front-end code which triggers the redirect but that 'success' response is not being sent.
Here's the demo-stripe.php code:
<?php
require_once('./stripe/init.php');
$stripe = array(
"secret_key" => "sk_test_******",
"publishable_key" => "pk_test_******"
);
\Stripe\Stripe::setApiKey($stripe['secret_key']);
// Get the credit card details submitted by the form
$token = $_GET['stripeToken'];
$email = $_GET['stripeEmail'];
$callback = $_GET['callback'];
try {
$customer = \Stripe\Customer::create(array(
"source" => $token,
"email" => $email
));
$charge = \Stripe\Charge::create(array(
'customer' => $customer->id,
'amount' => 100,
'currency' => 'usd'
));
header('Content-type: application/json');
$response_array['status'] = 'success';
echo $callback.'('.json_encode($response_array).')';
return 1;
}
catch ( \Stripe\Error\Card $e) {
// Since it's a decline, \Stripe\Error\Card will be caught
}
?>
Here's the front-end code:
<script src="https://checkout.stripe.com/checkout.js"></script>
<script>
var handler = StripeCheckout.configure({
key: 'pk_test_*****',
image: 'imagefile.png',
locale: 'auto',
token: function(token) {
$.ajax({
type: "POST",
dataType: 'jsonp',
url: "https://snapstories.co/demo-stripe.php",
data: { stripeToken: token.id, stripeEmail: token.email},
success: function(data) {
window.location.href = "http//www.google.com";
},
});
}
});
document.getElementsByClassName('w-f73e4cf1-859d-e3e4-97af-8efccae7644a')[0].addEventListener('click', function(e) {
// Open Checkout with further options:
handler.open({
name: 'Testing',
description: 'testing',
amount: 100
});
e.preventDefault();
});
// Close Checkout on page navigation:
window.addEventListener('popstate', function() {
handler.close();
});
</script>
I'm guessing your front-end code doesn't get to the success function.
Web console returns:
ReferenceError: $ is not defined
It looks like you're using the jQuery command $.ajax(), but I can't see where you've loaded the jQuery library. Try and load it above the script that uses it and see what happens
Be sure to double check the Stripe Checkout requirements. It seems, based on the link you posted, that you're using the HTTP protocol. Stripe Checkout requires you use the HTTPS protocol. That means if you're not using an ssl certificate on your page using Checkout, your page isn't going to return a token nor will it execute any further.
Related
I am trying to use the Stripe API to create a payment form as detailed here:
https://stripe.com/docs/payments/integration-builder
I would like to send the amout (that the user is charged) from the front-end so have attempted to add it to the fetch request as shown below:
var purchase = {
//items: [{ id: "xl-tshirt", price: 400 }]
amount: 2000
};
// Disable the button until we have Stripe set up on the page
document.querySelector("button").disabled = true;
fetch("/create.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(purchase)
});
However the value (currently hardcoded at 2000) is not pulling through to the POST body successfully and the payment intent is failing. Below is the code I am using:
try {
// retrieve JSON from POST body
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str, false);
$paymentIntent = \Stripe\PaymentIntent::create([
//'amount' => calculateOrderAmount($json_obj->items),
'amount' => $json_obj['amount'],
'currency' => 'usd',
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
Any advice is much appreciated.
You are sending false here, that converts it into an object
$json_obj = json_decode($json_str, false);
Then you are trying to use it as an array here
'amount' => $json_obj['amount'],
Try using
'amount' => $json_obj->amount,
Or
$json_obj = json_decode($json_str, true);
without changing anything else.
I am trying to implement a PayPal subscription flow where user click on a PayPal subscription button that I have created via the dashboard.
In the back-end, I listen to the PAYMENT.SALE.COMPLETED webhook that is triggered when a subscription billing is successful. Unfortunately the webhook doesn't send me much infos so that I can retrieve the user and item in my DB linked to the just billed subscription.
This would allow me to securely show private content to that user.
Here is the webhook content sent by payPal (sorry for the length):
const response = {
id: 'WH-4W487015EX264720U-32N35125TV248784B',
event_version: '1.0',
create_time: '2021-04-26T08:24:41.436Z',
resource_type: 'sale',
event_type: 'PAYMENT.SALE.COMPLETED',
summary: 'Payment completed for EUR 6.9 EUR',
resource: {
billing_agreement_id: 'I-T2HP99MJTS1T',
amount: {
total: '6.90',
currency: 'EUR',
details: {
subtotal: '6.90'
}
},
payment_mode: 'INSTANT_TRANSFER',
update_time: '2021-04-26T08:23:59Z',
create_time: '2021-04-26T08:23:59Z',
protection_eligibility_type: 'ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE',
transaction_fee: {
currency: 'EUR',
value: '0.48'
},
protection_eligibility: 'ELIGIBLE',
links: [
{
method: 'GET',
rel: 'self',
href: 'https://api.sandbox.paypal.com/v1/payments/sale/6R7481343K8159132'
},
{
method: 'POST',
rel: 'refund',
href: 'https://api.sandbox.paypal.com/v1/payments/sale/6R7481343K8159132/refund'
}
],
id: '6R7481343K8159132',
state: 'completed',
invoice_number: ''
},
links: [
{
href: 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4W487015EX264720U-32N35125TV248784B',
rel: 'self',
method: 'GET'
},
{
href: 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4W487015EX264720U-32N35125TV248784B/resend',
rel: 'resend',
method: 'POST'
}
],
}
I have tried to GET the /v1/payments/sale/:id but it didn't bring me much informations.
I have also checked other stack overflow threads on the subject but it wasn't of any help.
I also don't want to use success callbacks provided in the front-end SDK because they are not as secure as a webhook (connection can close before triggering the callback see this gitlab issue)
How can I be aware that a user was billed for his subscription ?
We finally found a workaround to make our back-end retrieve the buyer and the item.
Front-end
On the subscription button code, we noticed after a lot of trial/errors that the createSubscription method accept promises and that we could use it to send the subscriptionId the the back-end before the payment continues:
paypal.Buttons({
style: {...},
createSubscription: function (data, actions) {
return actions.subscription.create({
/* Creates the subscription */
plan_id: 'P-26J60279VA924454WMCBPBSA',
}).then(subscriptionId => { // subscriptionId == I-9DH5L3A3JAEB
return new Promise((res, rej) => {
// here we send the subscriptionId to the back-end
// and create a pending subscription
const body = {subscriptionId, userId, itemId};
apiCall('POST', '/subscription', body,() => {
// allow to return subscriptionId to paypal
resolve(subscriptionId);
})
});
});
},
onApprove: function (data, actions) {
// this function was of NO USE
// it is not safe to call your backend here
// as connexion can close and paypal doesn't
// wait after this function to capture payment
// thus leading to orphaned subscriptions
// (paid but not linked to your backend)
},
}).render('#paypal-button');
Back-end (webhook handler)
The back-end wait for the confirmation webhook where webhookResponse.resource.billing_agreement_id is the subscription id and allow to validate the previously created subscription. I don't exactly understand why billing_agreement_id is not named subscrition_id...
Let me know if it's not clear enougth. I let that as an answer until there is a better way to do it :)
This is my approach to create and verified Paypal subscription payment.
Firstly follow the Integrate Subscriptions steps from Paypal Developer site.
Client Side
html
<script src="https://www.paypal.com/sdk/js?client-id=<YOUR CLIENT ID>&vault=true&intent=subscription"></script>
<div id="paypal-button-container"></div>
You can get data from PayPal using the following snippet:
Javascript
paypal.Buttons({
createSubscription: function( data, actions ) {
return actions.subscription.create({
'plan_id': '<YOUR SUBSCRIPTION PLAN>' // Creates the subscription
});
},
onApprove: function( data, actions ) {
finalize( data, actions );
}
}).render( '#paypal-button-container' ); // Renders the PayPal button
const finalize = async ( data, actions ) => {
const rawResponse = await fetch( '/api/paypal-subscription.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: data, actions: actions })
});
const results = await rawResponse.json();
if ( results.error == 0 ){
console.log( "payment created" );
} else {
console.log( 'Errore creazione agenzia' );
};
};
When the subscription is confirmed an onApprove event is fired. Inside the function you can call another function to finalize the subscription process. The function have two object: data and action.
In data object you have a subscriptionID that refers to the unique id of the subscription. You must save this id with the subscription buyer linked to it ( eg: save to database by calling a php file on server using ajax ) .
Server Side Webhooks
In the server side you can get data from PayPal. You have to setup a webhooks call in the developer dashboard for the following action (you can select more or all event if you need ).
BILLING.SUBSCRIPTION.CREATED, BILLING.SUBSCRIPTION.ACTIVATED and for the recurring payment made PAYMENT.SALE.COMPLETED.
<?php
$data = json_decode( file_get_contents( "php://input" ), true );
$data = $data['resource'];
if ( !array_key_exists( 'billing_agreement_id', $data ) ) {
// Not a payment for a billing agreement
// handle single payments or:
die();
};
?>
Keep in mind that: the webhooks simulator doesn't populate the billing_agreement_id, the key that carry the subscriptioID, referred as id in the other Webhooks calls. I suggest to create in the sandbox a subscription with a daily FREQUENCY with one (1) day interval. With this subscription the PAYMENT.SALE.COMPLETED will be fired immediately. The key to find in PAYMENT.SALE.COMPLETED call is billing_agreement_id.
Verify Paypal webhook notification
You also have to verify the authenticity of the notification:
<php
// get request headers
$headers = apache_request_headers();
// get http payload
$body = file_get_contents( 'php://input' );
// compose signature string: The third part is the ID of the webhook ITSELF(!),
// NOT the ID of the webhook event sent. You find the ID of the webhook
// in Paypal's developer backend where you have created the webhook
$data =
$headers['Paypal-Transmission-Id'] . '|' .
$headers['Paypal-Transmission-Time'] . '|' .
'<WEBHOOK ID FROM THE DEVELOPER DASHBOARD>' . '|' . crc32( $body );
// load certificate and extract public key
$pubKey = openssl_pkey_get_public( file_get_contents( $headers['Paypal-Cert-Url'] ) );
$key = openssl_pkey_get_details( $pubKey )['key'];
// verify data against provided signature
$result = openssl_verify(
$data,
base64_decode( $headers['Paypal-Transmission-Sig'] ),
$key, 'sha256WithRSAEncryption'
);
if ( $result == 1 ) {
// webhook notification is verified
} elseif ( $result == 0 ) {
// webhook notification is NOT verified
} else {
// there was an error verifying this
};
?>
The transmission id, the transmission date, the webhook id and a CRC over the HTTP body. The first two can be found in the header of the request, the webhook id in the developer backend (of course, that id will never change), the CRC is calculated like shown below.
The certificate's location is in the header, too, so we load it and extract the private key.
Last thing to watch out for: The name of the algorithm provided by Paypal (again in a header field) is not exactly the same as understood by PHP. Paypal calls it "sha256WithRSA" but openssl_verify will expect "sha256WithRSAEncryption". You can read more about verification precess here
I'm trying to do the simplest possible thing: sending the user to Stripe's hosted checkout page with 1 product.
None of Stripe's examples seem to work, so far what I've got is:
PHP create-checkout-session.php
require_once 'shared.php';
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain . '/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain . '/canceled.html',
'payment_method_types' => ['card'], //, 'alipay'
'mode' => 'payment',
'line_items' => [[
'amount' => $price,
'currency' => 'usd',
'name' => $product,
'quantity' => 1,
]]
]);
echo json_encode(['sessionId' => $checkout_session['id']]);
That PHP page correctly returns a session ID.
HTML
<html>
<head>
<title>Buy cool new product</title>
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<button id="checkout-button">Checkout</button>
<script type="text/javascript">
// Create an instance of the Stripe object with your publishable API key
var stripe = Stripe('pk_test_key'); // removed for Stackoverflow post
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', function() {
// Create a new Checkout Session using the server-side endpoint you
// created in step 3.
fetch('create-checkout-session.php', {
method: 'POST',
})
.then(function(response) {
return response.json();
})
.then(function(session) {
return stripe.redirectToCheckout({ sessionId: session.id });
})
.then(function(result) {
// If `redirectToCheckout` fails due to a browser or network
// error, you should display the localized error message to your
// customer using `error.message`.
if (result.error) {
alert(result.error.message);
}
})
.catch(function(error) {
console.error('Error:', error);
});
});
</script>
</body>
</html>
When I click the button nothing happens and I get this error on Chrome devtools:
Error: IntegrationError: stripe.redirectToCheckout: You must provide one of lineItems, items, or sessionId.
at new t (https://js.stripe.com/v3/:1:11100)
at Lu (https://js.stripe.com/v3/:1:152624)
at qu (https://js.stripe.com/v3/:1:152923)
at Fu (https://js.stripe.com/v3/:1:153599)
at Bu (https://js.stripe.com/v3/:1:153713)
at e.redirectToCheckout (https://js.stripe.com/v3/:1:154128)
at https://emu.net/stripetest/test.html:24:25
I don't understand this error. It seems like the sessionId is not being passed correctly. The HTML code came directly from the Stripe doc at:
https://stripe.com/docs/payments/checkout/accept-a-payment
To be honest at this point I don't know where I'm supposed to look. None of the Stripe examples seem to work. Anyone have any idea what I'm doing wrong?
Judging by the structure of session you need to pass
{ sessionId: session.sessionId }
not
{ sessionId: session.id }
take a look at the error message:
Error: IntegrationError: stripe.redirectToCheckout: You must provide one of lineItems, items, or sessionId.
at new t (https://js.stripe.com/v3/:1:11100)
enter code here
You need to send back "sessionId: session.sessionId".
I am using the PaymentIntent API to integrate Stripe payments using stripe-php SDK and Stripe.js V3.
Following This guide https://stripe.com/docs/payments/payment-intents/migration#saving-cards-checkout. I am getting what successful payments in my Stripe Dashboard done with test cards which do not require 3d-secure. But The Stripe's new SCA 3d secure Popup(according to their docs.) is not popping up, Which leads payments done with 3dsecure ENABLED cards to "Incomplete Payments" Tab in stripe Dashboard.
I have examine the code thoroughly multiple times and tested. I have noticed that my code skips(somtimes) OR throws an error "Unexpected end of JSON input" in the "Fetch Part" on the client side code.. which leads the 3d-secure cards payments to be incomplete.The JavaScript Fetch function is not fetching the "payment_method_id" from the specified file(url).
My Payment.js File:
var elements = stripe.elements();
var style = {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
var cardNumber = elements.create('cardNumber', {
style: style
});
cardNumber.mount('#cardNumber');
var cardExpiry = elements.create('cardExpiry', {
style: style
});
cardExpiry.mount('#cardExpiry');
var cardCvc = elements.create('cardCvc', {
style: style
});
cardCvc.mount('#cardCVC');
var cardholderName = $('#custName').val();
var amount = $('#amount').val();
$(document).ready(function () {
$("#paymentForm").submit(function (event) {
//event.preventDefault();
stripe.createPaymentMethod('card', cardNumber, {
billing_details: {name: cardholderName.value}
}).then(function (result) {
console.log(result);
if (result.error) {
var errorElement = document.getElementById('card-error');
errorElement.textContent = result.error.massage;
} else {
stripeTokenHandler(result);
fetch('example.com/stripe/index.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
payment_method_id: result.paymentMethod.id
})
}).then(function (result) {
// Handle server response (see Step 3)
result.json().then(function (result) {
handleServerResponse(result);
})
});
}
});
//return false;
});
function stripeTokenHandler(result) {
var payForm = $("#paymentForm");
var paymentMethodID = result.paymentMethod.id;
//set the token into the form hidden input to make payment
payForm.append("<input type='hidden' name='payment_method_id' value='" + paymentMethodID + "' />");
// payForm.submit();
payForm.submit();
}
}
My Index.php File
header('Content-Type: application/json');
if(isset($_POST['submit'])){
//include Stripe PHP library
require_once('stripe-php/init.php');
//set Stripe Secret Key
\Stripe\Stripe::setApiKey('sk_test_key');
//add customer to stripe
$customer = \Stripe\Customer::create(array(
'email' => $custEmail,
));
function generatePaymentResponse($intent) {
if ($intent->status == 'requires_action' &&
$intent->next_action->type == 'use_stripe_sdk') {
# Tell the client to handle the action
echo json_encode([
'requires_action' => true,
'payment_intent_client_secret' => $intent->client_secret
]);
} else if ($intent->status == 'succeeded') {
# The payment didn’t need any additional actions and completed!
# Handle post-payment fulfillment
echo json_encode([
'success' => true
]);
} else {
# Invalid status
http_response_code(500);
echo json_encode(['error' => 'Invalid PaymentIntent status']);
}
}
# retrieve json from POST body
$json_str = file_get_contents('php://input');
$json1 = json_encode($_POST);
$json_obj = json_decode($json1);
$intent = null;
try {
if (isset($json_obj->payment_method_id)) {
$intent = \Stripe\PaymentIntent::create([
'payment_method' => $json_obj->payment_method_id,
'customer' => $customer->id,
'amount' => 1099,
'currency' => 'gbp',
'confirmation_method' => 'manual',
'confirm' => true,
]);
}
generatePaymentResponse($intent);
}catch (\Stripe\Error\Base $e) {
# Display error on client
echo json_encode([
'error' => $e->getMessage()
]);
}
}
?>
As it can be seen my stripeTokenHandler is appending the payment_method.id into HTML form and the process goes on. but the "fetch" section of JS code should get payment_method_id to generate "Response" and to proceed for "next_action" if the payment status is "requires_action".
So, In order to achieve what i wanted to, what i did was
- removed stripeTokenHandler()
Because i was using it in my previous charges API integration and thought it will work with the new PaymentIntent. And i guess many people misunderstood or misguided buy bunch of different methods stripe has in its docs.I saw a lot of questions on internet people complaining that stripe's "poorly managed" Documentation has confused them.
- Learned Fetch Api.
As a newbie i didnt know what it was for.
- removed isset post submit from my php code
reason: payment.js was unable to POST the paymentMethod.id to the server using Fetch Api and get the response body back from the server to proceed the code further.
I MUST say that Stripe needs to improve its Docs about this SCA ready PaymentIntent thing.
I always get the following error when I want to login with PayPal:
Fatal error: Uncaught exception 'PayPal\Exception\PayPalInvalidCredentialException' with message 'Credential not found for default user. Please make sure your configuration/APIContext has credential information' in /home/.sites/137/site1611/web/Website/PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Core/PayPalCredentialManager.php:154
I implemented the PayPal PHP SDK correctly and I already created a Sandbox User account in my PayPal developer dashboard. I also retrieve a correct refresh_token and access_token but I'm not able to retrieve the user information like email, name, etc. What am I doing wrong?
My JS on the 'Login with PayPal' page looks like that:
<span id="myContainer" style="position: absolute;top: 0;left: 0;z-index: 1000;"></span>
<script src="https://www.paypalobjects.com/js/external/api.js"></script>
<script>
paypal.use( ["login"], function(login) {
login.render ({
"appid": "ATAoL...nifIi",
"authend": "sandbox",
"scopes": "profile email address https://uri.paypal.com/services/paypalattributes",
"containerid": "myContainer",
"locale": "en-us",
"returnurl": "http://www.url.com/return.php"
});
});
</script>
And my PHP script at the returnurl looks like that:
error_reporting(E_ALL);
require __DIR__ . '/PayPal-PHP-SDK/autoload.php';
use PayPal\Rest\ApiContext;
use PayPal\Api\OpenIdTokeninfo;
use PayPal\Api\OpenIdUserinfo;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
$code = $_GET['code'];
$clientId = 'ATAoLjBG....AbL4vWj89y89nifIi';
$clientSecret = 'EKoaU4uh....YXwCjlCj6FadrRXAdx';
$apiContext = new ApiContext(new OAuthTokenCredential($clientId, $clientSecret));
try {
$accessToken = OpenIdTokeninfo::createFromAuthorizationCode(array('code' => $code), null, null, $apiContext);
}
catch (PayPalConnectionException $ex) {
print_r('###################### Error'); exit(1);
}
print_r('###################### Success: ' . $accessToken);
$user = OpenIdUserinfo::getUserinfo(array('access_token' => $accessToken, $apiContext));
print_r($user);
In your last line, you are passing apicontext inside array by mistake ! Fix that and you should be fine.
You can see the sample code here: http://paypal.github.io/PayPal-PHP-SDK/sample/doc/lipp/GetUserInfo.html
This is how it should be :
$user = OpenIdUserinfo::getUserinfo(array('access_token' => $accessToken), $apiContext);
print_r($user);