I currently have an Opayo (SagePay) Server integration where I'm apparently spoilt by an ability to simply respond to a payment notification callback with an ERROR status to reject/void a transaction if there is a problem fulfilling an order on my side. I think, there is some sort of automatic authorise then capture happening behind the scenes, where a payment is only captured if a success/OK status is returned by my notification handler.
I'm looking at how I can achieve the same with a Stripe checkout integration (Pre-built Checkout, PHP with webhooks) and it seems that I need to to use authorisation and manual capturing:
$session = \Stripe\Checkout\Session::create([
...
'payment_intent_data' => [
'capture_method' => 'manual',
],
]);
In the sample code and documentation (which is generally excellent, but I feel falls short here), it shows how to create a webhook to handle the completion of the checkout session:
// Handle the checkout.session.completed event
if ($event->type == 'checkout.session.completed') {
$session = $event->data->object;
// Fulfill the purchase...
fulfill_order($session);
}
Curiously, the payment_status of the transaction is not checked and the example suggests you would simply fulfil your order here. I can only assume, if you are not using a delayed payment method then you can assume the payment is successful at this point, although it seems a dangerous assumption to make.
Further along in the documentation, under "Handle delayed notification payment methods Server-side", we have a fuller example, where the payment status is actually checked before fulfilling the order:
switch ($event->type) {
case 'checkout.session.completed':
$session = $event->data->object;
// Save an order in your database, marked as 'awaiting payment'
create_order($session);
// Check if the order is paid (e.g., from a card payment)
//
// A delayed notification payment will have an `unpaid` status, as
// you're still waiting for funds to be transferred from the customer's
// account.
if ($session->payment_status == 'paid') {
// Fulfill the purchase
fulfill_order($session);
}
...
If I am using the manual capture method, how exactly should the code above be used/adapted? Presumably the payment_status will not be paid so what do I check for and how do I access the PaymentIntent to be able to capture it? Should this all happen in the checkout.session.completed event?
A code example would be great; unfortunately there is only this footnote in the documentation:
To capture an uncaptured payment, you can use either the Dashboard or the capture endpoint. Programmatically capturing payments requires access to the PaymentIntent created during the Checkout Session, which you can get from the Session object.
I have checked the online code samples but this exact scenario isn't covered.
EDIT: Adding below my understanding of the flow I need with questions:
switch ($event->type) {
case 'checkout.session.completed':
$session = $event->data->object;
if ($session->payment_status == 'unpaid') {
// We need to capture the payment if we have arrived here?
// Is this the only scenario where we would end up here?
// How do we get the PaymentIntent, what status if any should we check on it?
// Attempt to fulfil the purchase here, if all good then
// capture the payment
} else if ($session->payment_status == 'paid') {
// Assume this was not an auth/capture type payment?
// There is no other scenario where we would end up here?
}
...
If you receive the checkout.session.completed event, then the synchronous payment method completed successfully.
For async payment methods, there are other events:
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
I'm not sure why you'd use auth and capture here, since the payment is successful either upon receipt of checkout.session.completed or checkout.session.async_payment_succeeded, but if you need to, you'd set the option to do so here and then you'd need to capture the Payment Intent associated with the Checkout Session.
If you need to determine if the Charge was captured or not, you'd need to retrieve the Payment Intent and look a the Charge's captured value: https://stripe.com/docs/api/charges/object#charge_object-captured
For my shopping site I have a fairy simple stripe implementation on the frontend where user hits "pay through stripe" button fills in CC details in a popup and hits proceed. If all goes well I get a token as $_POST['stripeToken'] which I process like below (for web payments)
try
{
$stripe = new Stripe();
$stripe = Stripe::make();
$customer = $stripe->customers()->create([
'email' => $_POST['email_id'],
'source' => $_POST['stripeToken']
]);
$charge = $stripe->charges()->create([
'customer' => $customer['id'],
'amount' => $total_amount, // note this is calculated again on server to prevent fraud
'currency' => 'sgd'
]);
}
catch (\Cartalyst\Stripe\Exception\CardErrorException $e)
{
return view('front.payment_error')->with('message', $e->getMessage());
}
Thing is we cannot rely on $total_amount coming from frontend form submission as user can easily spoof this amount and pay just $1 in stripe and get a token and spoof $total_amount to 1 thus getting products at whatever price he wants ,that's why I need to calculate his `$total_amount again on the server (from his cart) and use that to process stripe on the server.If that amount doesn't match what the token stands for , stripe would automatically raise an exception and prevent fraud.
So far good.. but the problem comes when dealing with API , mobile app will process stripe using their own libs. on the client side they would just send the final (for recording in db) but obviously I cannot use it to charge since that is already done on the mobile.Since these days it's very easy to change app behaviour (by patching apks) or craft custom HTTP request (postman) , server check is a must in case of payments.
So my question is how can I verify from the token the actual amount user paid
ie. reverse convert stripeToken => actual paid amount
Update:
This is what I am looking in case of Stripe
https://developer.paypal.com/docs/integration/mobile/verify-mobile-payment/
Introduction
I´ve been looking at integrating a somewhat big project of mine with PayPal Express Checkout, and use that as the main system to handle payments on my site. Although their documentation is quite extensive, I really can´t find too good a place to start. Over the past few days, I´ve been presented with headache upon headache, in attempt to figure out how on Earth to integrate my site, as well as its database, with PayPal Express Checkout.
Summary
The JavaScript bit is pretty straight forward, and charging money using the client-side integration is extremely easy. But I also need a way to update fields in my database upon a payment going through (being successful), and from what I´ve understood so far, that can only be done by using the advanced server integration.
Problem
The problem, given the aforementioned reasons, is somehow managing to implement the advanced server integration solution, into my platform, in order to make way for changes in the database to occur upon a transaction being successful.
As mentioned before, the PayPal documentation is fairly extensive--problem is, I´ve never really went over using REST API´s, and it appears as though integrating what I´m after can only really be done using the PayPal REST API, to verify that the payment(s) have taken place.
Question
With all that being said, what I need help with is finding some place in all of this to start. I´m aware StackOverflow may not be the best place for a question like this, but it appears lots of people are having trouble with this exact question, and it´s an essential part of settings up any business on the web to get this to work.
Any pointers on where to start, or anything relating to the subject other than the PayPal documentation would be extremely helpful.
What follows is a minimal way to use Express Checkout with a server-side Payments REST API integration, including the associated code for client and server in JavaScript. I hope it helps.
You'll need to login to developer.paypal.com and create a REST API app. Your new app will be assigned a Client ID and Secret which you can use to request access tokens, which authorize your use of the PayPal REST API. The only app setting you need for what follows here is "Accept Payments". Sandbox accounts for testing are automatically created when you create an app.
Your (checkout) page loads PayPal's Express Checkout script, and a script you write renders a PayPal button:
// In your script:
paypal.Button.render({
env: 'sandbox', // Or 'production'.
commit: true, // Show 'Pay Now' button.
style: { // Style the button.
size: 'responsive',
color: 'silver',
shape: 'rect'
},
payment: function(data, actions) {
// See step 3.
},
onAuthorize: function(data, actions) {
// See step 7.
}
}, '#paypal-button');
Your customer clicks the rendered PayPal checkout button.
The payment function you define in the argument you give to paypal.Button.render() is called by PayPal's script, which sends a request to your server with your payload. Your payload contains (for example) cart contents and its associated data.
paypal.Button.render({
// ...
payment: function(data, actions) {
return paypal.request(
{
method: 'post',
url: '/your-api/create-payment',
json: {
order: items,
or: whatever
}
}
).then(
function(res) {
// Return the payment id received from your server.
return res.paymentId;
}
).catch(
function(err) {
// Oops, foobared.
}
);
},
// ...
}
Your server sends a request to api.sandbox.paypal.com, with your access token, to create a payment. When your server receives the newly created payment data from PayPal it (perhaps) persists the data on your server, but it must return the payment id to the client.
// HTTP request data for creating a payment.
{
method: 'post',
// Remove '.sandbox' below to use production endpoint.
url: 'https://api.sandbox.paypal.com/v1/payments/payment';
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + 'your access token here'
},
data: {
// Allows immediate payments with PayPal and credit cards in
// the Express Checkout dialog.
intent: 'sale',
// I suppose these were required by other PayPal REST services,
// but they won't be used because, for Express Checkout, you
// handle the confirmation/cancellation flow yourself, on the
// client.
redirect_urls: {
return_url: 'https://...'
cancel_url: 'https://...'
},
payer: {
payment_method: 'paypal',
},
note_to_payer: 'Thanks, you\'re magnificently awesome!',
transactions: [{
amount: {
total: total, // Your computed total.
currency: 'USD',
details: {
subtotal: subtotal, // Your computed subtotal.
tax: tax, // Your computed tax.
shipping: shipping // Your computed shipping.
// Other parameters are available for your use.
}
},
item_list: {
items: [
{
name: 'shinny shirt of mithril mail',
description: 'shinny',
sku: '12345',
quantity: 1,
price: 1.00,
currency: 'USD'
}
],
// Some properties aren't required, like this one.
shipping_method: 'USPS'
},
description: 'Your PayPal payment for a shinny shirt of mithril mail.',
// Not required and can be added later by patching the
// payment: maybe you don't want to add an invoice to and
// order until the payment is approved.
invoice_number: 12345
}]
}
}
The payment function you provided then receives the payment id from your server and uses it as its return value.
// See return value in step 3.
And now the Express Checkout payment dialog pops up for your customer, who must login in and confirm the payment, create an account, or checkout with a credit/debit card.
After the customer confirms payment by clicking the Pay Now button in the Express Checkout dialog, the onAuthorize function you define in the argument you give to paypal.Button.render() is called by PayPal's script. You can use this function's actions parameter to get() payment information from PayPal, which you can use in a confirmation page, etc. Use its data parameter to get the payment id and payer id, which must be sent in a request to your server to execute the payment.
onAuthorize: function(data, actions) {
return actions.payment.get().then(function(paymentDetails) {
// Get at the payment details like this...
// paymentDetails.payer.payer_info.first_name;
// paymentDetails.payer.payer_info.shipping_address.city;
// paymentDetails.payer.payer_info.shipping_address.state;
var payload = {
paymentId: data.paymentID,
payerId: data.payerID
};
return paypal.request(
{
method: 'post',
url: '/your-api/execute-payment',
json: payload {
paymentId: data.paymentID,
payerId: data.payerID
}
}
).then(
function(res) {
// Gotten paid! Show confirmation page.
}
).catch(
function(err) {
// Dang it.
}
);
});
}
Your server receives the payment id and payer id from the client and makes another request to PayPal, this time to execute the payment. The transaction is complete if the execute request returns successfully. And now you can persist (or update) the payment data on your server.
// HTTP request data for executing a payment.
{
method: 'post',
// Remove '.sandbox' below to use production endpoint.
url: 'https://api.sandbox.paypal.com/v1/payments/payment/' + paymentId + '/execute/',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + 'your access token here'
},
data: {
payer_id: payerId
}
}
The client's latest request to your server completes successfully and you show a confirmation page, optionally with payment info from step 7 or from your server.
As for a place to start, here's what I suggest:
Automate requesting and storing your access token on your server. You'll need to store it because PayPal rate-limits token requests. So, either automate keeping it fresh or request a new one whenever you need to. I believe they expire in eight or nine hours; in any case, the expires-in time is given to you with the token. https://developer.paypal.com/docs/api/overview/
Build the /your-api/create-payment server endpoint. Test it with requests from an API tool like Postman using your access token. When everything goes well your server will make a successfull call to PayPal to create a payment, and you can explore the data in the response. It doesn't matter that you don't plan to execute the payment...it's the sandbox after all. Then build in your server-side persistence (if you want) and return the payment id to the client.
Next, load the PayPal script and your checkout-button rendering script on your site, and see if the Express Checkout login appears when you click the checkout button. If an error occurs in the create-payment process you'll see an error rather than the Express Checkout login.
When your system is creating payments and you can log in at the Express Checkout dialog with your sandbox credentials, build the /your-api/execute-payment endpoint. When your server has a working execute-payment endpoint you should be able to log in with Express Checkout and use your sandbox buyer account to complete a payment.
If after all that you think you'll have a go, the docs you want to pay attention to are https://developer.paypal.com/docs/api/ > API Reference > Payments API > Payments. There are five sections: create, execute, show, update and list. Read them. It will help.
I'm using L5 and want to integrate my PayPal purchases into that system. The sandbox is already set up and I can do all my payments using the real PayPal API package, but as I want to try to do it with Omnipay I'm struggling a bit:
When I execute this code:
Route::get('test', function()
{
$gateway = Omnipay::create('PayPal_Rest');
$gateway->setClientId('{my id}');
$gateway->setSecret('{my secret}');
$gateway->setTestMode(true);
$params = array(
'cancelUrl' => 'http://webshop.app',
'returnUrl' => 'http://webshop.app/testresp',
'name' => 'Your Purchase',
'description' => 'Your Description',
'amount' => '15.99',
'currency' => 'EUR'
);
Session::put('params', $params);
Session::save();
$resp = $gateway->purchase($params)->send();
if ($resp->isSuccessful()) {
// payment was successful: update database
print_r($resp);
} elseif ($resp->isRedirect()) {
// redirect to offsite payment gateway
$resp->redirect();
} else {
// payment failed: display message to customer echo
$resp->getMessage();
}
});
I get this:
InvalidRequestException in AbstractRequest.php line 122:
The card parameter is required
Seems like I would have to initiate that purchase with credit card information of the client, which I do not want to gather (hence using PayPal in the first place). Is there any way to use that API without usage of a credit card?
I don't like the usage of the Express API as I don't want my PayPal username and password within my code. For several reasons.
The Card array field is required. it's not required to insert credit card number, but you will need to provide some information.
From the official docs:
Even off-site gateways make use of the CreditCard object, because often you need to pass customer billing or shipping details through to the gateway.
Check out the following branch of my fork of the omnipay-paypal gateway code: https://github.com/delatbabel/omnipay-paypal/tree/accept-paypal-payments
That includes code that allows you not to pass through a credit card and have PayPal do the payment processing.
I have submitted a PR but it hasn't been merged into the main omnipay-paypal repository yet.
I am trying to create a simple payment system for bitcoins using coinbase API and this lovely script I found on github
Everything works smoothly, but I would like to be able to take the users delivery address as well.
I am using the following to create a payment button:
$response = $coinbase->createButton("This is an item", "$amount", "BTC", "Trackcode", array(
"description" => "Item Description here"
));
echo $response->embedHtml;
I noticed that the description of the item is never actually displayed to the end user.
"description" => "Item Description here"
I am thinking this may be used as a reference for the merchant once a transaction has taken place. If this is true, I could take advantage of this by having the users delivery address parsed in to the "item description". This should then be displayed to me in my merchant account over at coinbase.
I know I could quite easily test this myself by making a transaction, but my "wallet" is empty at the moment.
Does anyone with any experience with coinbase know if this is true?
I found the official Coinbase script quite unsuccesfull, so I wrote my own.
Hope this helps...
https://github.com/rjmacarthy/coinbase-api-php
Have you tried using their api library? https://github.com/coinbase/coinbase-php It has a nice documentation and is almost error free. It should be fairly simple if you use this api. Here is the sample provided for payment buttons in the github doc.
$response = $coinbase->createButton("Your Order #1234", "42.95", "EUR", "my custom tracking code for this order", array(
"description" => "1 widget at €42.95"
));
echo $response->button->code;
// '93865b9cae83706ae59220c013bc0afd'
echo $response->embedHtml;
// '<div class=\"coinbase-button\" data-code=\"93865b9cae83706ae59220c013bc0afd\"></div><script src=\"https://coinbase.com/assets/button.js\" type=\"text/javascript\"></script>'
The description parameter is only displayed on Coinbase's Payment Pages. The custom parameter (4th argument in createButton function) is recommended for returning data after payment is made, however, the description parameter will also be returned in the Callback response for Payment Buttons or Payment iFrames.
Not sure if this helps, but Coinbase also allows an include_address parameter (boolean) which prompts the buyer for their shipping address before displaying the payment options.
Parameter Reference: https://coinbase.com/api/doc/1.0/buttons/create.html