I am using Laravel 5.1 trying to set it up with Stripe using Cashier.
I am using a custom button to execute the javascript (using Angular):
$scope.subscribe = function(plan){
var handler = StripeCheckout.configure({
key: 'pk_test_*************',
image: '/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
var data = 'stripeToken=' + token.id;
$http.post("/createSubscription", data).success(function(data, status) {
console.log(data);
});
}
});
handler.open({
name: 'basic',
description: 'basic monthly $100',
currency: "usd",
amount: 10000
});
$(window).on('popstate', function() {
handler.close();
});
};
And in my Laravel code I have:
public function createSubscription(Request $request)
{
$user = JWTAuth::parseToken()->authenticate();
$token = Input::get('stripeToken');
$user->subscription("basic_plan")->create($token);
return 'test';
}
But I keep getting an error saying "this customer has no attached payment source".
The returned token at this line:
token: function(token) {
...
Does contain the users email, card info (+ card token), and stripe token. But when I check my Stripe dashboard, a customer is added without any data (no card, no email and not set up with the subscription).
I am trying to create customers with a subscription plan via this form.
(I do have the Billable trait set up and included in the controller)
Related
Since it's not possible to use the old way to sign in users any more I tried the new way:
function handleCredentialResponse(response) {
console.log(response);
}
window.onload = function () {
google.accounts.id.initialize({
client_id: googleSignInClientId,
callback: handleCredentialResponse
});
google.accounts.id.renderButton(
document.getElementById("gooDiv"),
{ theme: "outline", size: "large" } // customization attributes
);
// google.accounts.id.prompt(); // also display the One Tap dialog
}
response contains all kind of useless nonsense, I used to get the user ID, profile image and the nickname, how to get those with the new way?
response.credential field is the ID token as a base64 encoded JSON Web Token (JWT) string.
This JWT, and contains the claim set from the authencation server about the user who has signed in to your application. Try and take it and put it in https://jwt.io/ and you will see the claims returend.
function handleCredentialResponse(response) {
console.log("Encoded JWT ID token: " + response.credential);
var tokens = response.credential.split(".");
var payload = JSON.parse(atob(tokens[1]));
console.log(`user id ${payload.sub}`)
console.log(`user name ${payload.name}`)
console.log(`user picture ${payload.picture}`)
}
window.onload = function () {
google.accounts.id.initialize({
client_id: "[REDACTED]",
callback: handleCredentialResponse
});
google.accounts.id.renderButton(
document.getElementById("buttonDiv"),
{ theme: "outline", size: "large" } // customization attributes
);
google.accounts.id.prompt(); // also display the One Tap dialog
}
The JWT is in three parts, header, payload and signature. they are joined by a .
My code splits the jwt by the .
var tokens = response.credential.split(".");
Then parses the payload
var payload = JSON.parse(atob(tokens[1]));
Resulting in
console.log(`user id ${payload.sub}`)
console.log(`user name ${payload.name}`)
console.log(`user picture ${payload.picture}`)
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’ve successfully setup the Customers payment methods and am able to retrieve them using the following code:
return stripe.paymentMethods
.list({ customer: customerId, type: 'card' })
.then((cards) => {
if (cards) {
return { cards: cards.data, error: null };
}
return {
error: 'Error creating client intent',
cards: null,
};
})
.catch((e) => {
console.log(e);
return { cards: null, error: 'Error fetching user cards' };
});
I’m now trying to create a direct PaymentIntent that will route the payment to a Stripe Connect connected account.
To do this I’m running this code:
if (cards && cards.cards && cards.cards.length > 0) {
const card = cards.cards[0];
const paymentIntent = await stripe.paymentIntents.create(
{
amount: amount,
customer: card.customer,
receipt_email: userEmail,
currency,
metadata: {
amount,
paymentMode: chargeType,
orderId,
},
description:
'My First Test Charge (created for API docs)',
application_fee_amount: 0,
},
{
stripeAccount: vendorStripeAccount,
}
);
const confirmedPaymentIntent = await stripe.paymentIntents.confirm(
paymentIntent.id,
{ payment_method: card.id }
);
This gives me the error ‘No such customer’, even though the customer ID is defined and I can find the customer in my Stripe dashboard. I also see the customer’s payment methods there.
What am I doing wrong?
The problem is that the Customer exists on your platform account, not the connected account you're trying to create the Payment Intent on.
In your first code snippet you don't specify a stripeAccount, so that API request is being made on your platform account. The Customer exists there, which is why that works as expected.
In your second code snippet you do specify a stripeAccount, which means that API request is being made on the connected account specified, not your platform account. You can read more about making API calls on connected accounts in Stripe's documentation.
To resolve the situation you either need to create the Payment Intent on your platform account as a destination charge, or create the Customer object on the connected account so it can be used there.
Can anyone assist as to why this is not posting to booking/charge upon completion of input to the checkout pop up window?
The simple checkout example posts fine, I am new to js so I don't quite understand the flow of the commands.
<form action="/booking/charge" method="post">
<script src="https://checkout.stripe.com/checkout.js"></script>
<button id="customButton">Purchase</button>
<script>
var handler = StripeCheckout.configure({
key: 'pk_test_xxxxxxxxxxx',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
// You can access the token ID with `token.id`.
// Get the token ID to your server-side code for use.
}
});
document.getElementById('customButton').addEventListener('click', function(e) {
// Open Checkout with further options:
handler.open({
name: 'xxxx',
email: "test#test.com",
description: '2 widgets',
currency: 'gbp',
amount: 350
});
e.preventDefault();
});
// Close Checkout on page navigation:
window.addEventListener('popstate', function() {
handler.close();
});
</script>
</form>
If you use the custom Checkout integration, you need to do a little more work. You write your own code to handle the token returned by Stripe. This is all done within token callback.
Here's a traditional form submission example, it uses JQuery, appends the token and user's email as values to hidden form elements, then submits the form.
function (token) {
// Use the token to create the charge with a server-side script.
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#myForm").submit();
}
Full Example: https://jsfiddle.net/osrLsc8m/
Alternatively, you could submit the data to your backend with an AJAX request.
function (token) {
var myData = {
token: token.id,
email: token.email
};
/*
Make an AJAX post request using JQuery,
change the first parameter to your charge script
*/
$.post("/echo/html/",myData,function(data){ ... });
}
Full Example: http://jsfiddle.net/742tety5/
Code that has worked for me (must include script for jQuery in header not footer)
<script src="https://checkout.stripe.com/checkout.js"></script>
<form id="myForm">
<input type="hidden" id="message" value="Hello, world"/></p>
<input type="hidden" id="amount" value="10.00" /></p>
<p><button type="submit" id="customButton">Pay</button></p>
</form>
<script>
// checkout handler
var handler = StripeCheckout.configure({
key: '<YOUR PUBLIC KEY>',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
token: function(token) {
/* Use the token to create the charge with a server-side script.
You can access the token ID with `token.id`
Pass along various parameters you get from the token response
and your form.*/
var myData = {
token: token.id,
email: token.email,
amount: Math.round($("#amount").val() * 100),
message: $("#message").val()
};
/* Make an AJAX post request using JQuery,
change the first parameter to your charge script*/
$.post("<YOUR ROUTE TO charge.php", myData,function (data) {
// if you get some results back update results
$("#myForm").hide();
$(".results").html("Your charge was successful");
}).fail(function () {
// if things fail, tell us
$(".results").html("I'm sorry something went wrong");
})
}
});
$('#myForm').on('submit', function (e) {
// Open Checkout with further options
handler.open({
name: 'Stripe.com',
email: 'test#test.com',
description: '2 widgets',
amount: Math.round($("#amount").val() * 100)
});
e.preventDefault();
});
// Close Checkout on page navigation
$(window).on('popstate', function () {
handler.close();
});
</script>
Hope this is of help to someone, experiencing same issue.
I'm trying to create a stripe customer using parse but can't seem to get the customer.id value from the response.
var newCustomer;
Stripe.Customers.create(
card: request.params.cardToken,
email: request.params.email
//These values are a success but below is where I have an issue.
).then(function(customer){
newCustomer = customer.id;
//newCustomer never gets set to the id, it's always undefined.
}, function(error){
});
Here is a way to create a new customer in parse cloud code using parse stripe module api.
Parse Reference: http://parse.com/docs/js/symbols/Stripe.Customers.html
Be sure you have stored your publish api key
var Stripe = require('stripe');
Stripe.initialize('sk_test_***************');
Parse.Cloud.define("createCustomer", function(request, response) {
Stripe.Customers.create({
account_balance: 0,
email: request.params.email,
description: 'new stripe user',
metadata: {
name: request.params.name,
userId: request.params.objectId, // e.g PFUser object ID
createWithCard: false
}
}, {
success: function(httpResponse) {
response.success(customerId); // return customerId
},
error: function(httpResponse) {
console.log(httpResponse);
response.error("Cannot create a new customer.");
}
});
});