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/
Related
I need help with Laravel Cashier.
I'm using laravel cashier on backend and vue on client side.
On client I setup stripe object, elements and card.
In the meanwhile I request to serve a client_secret making a PaymentIntent
$payment_intent = PaymentIntent::create(
['amount' => $request["price"],
'currency' => "eur",
'payment_method_types' => ['card'],
'customer' => $stripe_customer->id,
], [
'api_key' => config('services.stripe.secret'),
]);
When user input credit card data and confirm using the client_secret
I call this function in client side
this.stripe.confirmCardPayment(this.stripe_payment_intent.client_secret, {
payment_method: {
card: this.stripe_card,
billing_details: {
name: 'Test Name'
}
},
setup_future_usage: 'off_session'
})
...
On then I call the server passing the result.
On the server side
$stripeCharge = $request->user()->charge(
$request["paymentIntent"]["amount"], $request["paymentIntent"]["payment_method"]
);
It works fine, withouth sca (3d secure)
But when the test card require 3d secure (4000002500003155 or 4000002760003184) on the client all goes fine, displaying the 3d secure
dialog, the confirmCardPayment succeed, but in last step when calling charge I got
The payment attempt failed because additional action is required before it can be completed.
Why? All the actions is performed on the client side.. why the server not agree?
p.s. I need only single charge without store any payment method
The problem was that confirmCardPayment already charge the user.
So calling again in the server side make the error because double charge on 3d secure card is not possible.
I had to remove charge on the server side and using PaymentIntent::retrieve in order to verify the status and check if already processed in my business logic.
This is my code:
$gateway = GatewayFactory::create('PayPal_Express');
$gateway->setUsername('name');
$gateway->setPassword('pass');
$gateway->setSignature('sig');
$gateway->setTestMode(true);
$resp = $gateway->purchase(
array(
'cancelUrl' => 'mysite/',
'returnUrl' => 'mysite/success',
'description' => "mysite item",
'amount' => $amount, //final amount
'currency' => 'EUR'
)
)->send();
if ($resp->isSuccessful()) {
// payment was successful: update database
// never reached
$a = new database('database');
$a->write database;
$dbp = null; die;
print_r($resp);
} elseif ($resp->isRedirect()) {
// redirect to offsite payment gateway
$resp->redirect();
} else {
echo $resp->getMessage();
header('Location: mysite/error');
exit;
}
How can I retrive informations about the transaction after the redirect?
How to adapt this code for credit card on paypal?
From your comments, I would assume that you're not getting to the successful payment stage.
With this in mind, let's take a look at the express checkout flow and compare it to a DoDirectPayment api call
===============
SetExpressCheckout API Call - the Shopping Cart announces the checkout details and gets back an "Express Checkout Token". The token identifies the checkout session that the shop has requested from PayPal
The buyer is redirected to the Express Checkout URL and the token gets appended to the redirect URL - https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-6C677292NP7377708
(if you skip step 5, you have to add a useraction=commit parameter to the redirect URL)
The buyer logs in and approves the payment
The buyer is redirected back to the Return URL announced in the SetExpressCheckout API call with the PayerID and the Token added to the return URL as HTTP GET Parameters
5. The integration initiates a GetExpressCheckoutDetails API call, retrieves the shipping info and displays one last overview and the added shipping charges to the buyer
Once the buyer decides to complete the payment, he initiates the payment through your integration on your website. The website then calls the DoExpressCheckoutPayment API
===============
For Credit Card Payments via the DoDirectPayment API, the API NVP Request and Response would look like this - it should be pretty straight forward.
Request:
VERSION = 109.0
METHOD = DoDirectPayment
PAYMENTACTION = Sale
IPADDRESS = 192.168.0.1
AMT = 0.01
CREDITCARDTYPE = Visa
ACCT = xxxxxxxx6840
EXPDATE = 102020
CVV2 = 123
FIRSTNAME = John
LASTNAME = Test
STREET = 12312 Port Grace Blvd
CITY = La Vista
STATE = NE
COUNTRYCODE = US
ZIP = 68128
CURRENCYCODE = USD
Response:
TIMESTAMP=2015-01-20T16:00:27Z
CORRELATIONID=180513f33ad52
ACK=Success
VERSION=109.0
BUILD=14726230
AMT=0.01
CURRENCYCODE=USD
AVSCODE=X
CVV2MATCH=M
TRANSACTIONID=0PA61839GE498951M}
===============
Judging by your comments in the code, you're assuming that the transaction completes once step 4 is complete.
As I do not see any code to initiate a DoExpressCheckoutPayment API call, you will need to make sure at least step 6 is being done.
https://devtools-paypal.com/integrationwizard/ has standalone sample code that might help.
However, using an SDK based integration will relieve much of the pain:
https://devtools-paypal.com/guide/expresscheckout/php?interactive=ON&env=sandbox
...
If you want to retrieve Payment Details at a later stage, you can run a GetTransactionDetails API call against the Transaction ID returned in the DoDirectPayment API call or the DoExpressCheckoutPayment calls.
See: https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/GetTransactionDetails_API_Operation_NVP/
You can use Instant Payment Notification to receive notifications about the just completed transaction. IPN is the recommended way to go.
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 integrate Pin.net.au CC processing into my site. I am using Omnipay library to make the calls.
To not store CC details in my server, I am using the Pin.js token method.
On form submit page (after user fills in personal and CC details) javascript does a 'prevent default' and sends the data from forms (browser) straight to pin.net.au servers. Server sends a card_token in response and resubmits the form to my server.
This token is recieved successfully and I can output it in my tests.
I get into trouble when I take that token and send a purchase request to pin.net.au. According to the API docs, I need not send user and card details when I send the token (the entire point of the token, really). I send this token along with other compulsory bits like email, amount, description etc.
This works when I cURL on my terminal and I get a charge success.
However, sending this purchase/charge request using the Omnipay library, each time I get a 422 (invalid resource) that asks for the user details and CC information. It should have populated this stuff from the token I sent.
I have scoured the API docs of both Omnipay and Pin.net.au. I don't seem to be doing anything wrong. What am I missing?
Here's my charge request:
$gateway = GatewayFactory::create('Pin');
$gateway->setSecretKey('MY_SECRET_KEY');
$response = $gateway->purchase([
'email' => 'user#email.com',
'description' => 'Package',
'amount' => '99',
'currency' => 'AUD',
'card_token' => Input::get('card_token'),
'ip_address' => Input::get('ip_address')
])->send();
Finally, it shouldn't really matter but if you'd like to know, I'm using Laravel 4.
Your example request has an amount of 99, the minimum amount for a Pin Payments charge is $1 (amount = 100).
I don't think this is the problem you are referring to though, it looks like Omnipay does not support using the card_token gear. If you go look over here - https://github.com/adrianmacneil/omnipay/blob/master/src/Omnipay/Pin/Message/PurchaseRequest.php#L34 - you can see Omnipay isn't sending the card_token field with it's request, it only tries to send card details, which obviously aren't present from your example!
Perhaps you could get in touch with the Omnipay developers or write a pull request yourself!
This is fixed in Omnipay v1.0.4 - you should be able to use the token like this:
$gateway = GatewayFactory::create('Pin');
$gateway->setSecretKey('MY_SECRET_KEY');
$response = $gateway->purchase([
'description' => 'Package',
'amount' => '99.00',
'currency' => 'AUD',
'token' => Input::get('token'),
'ip_address' => Input::get('ip_address'),
'card' => ['email' => 'user#email.com'],
])->send();
Working with the PayPal API and using the Name-Value Pair Interface PHP source codes from SDKs and Downloads: Simplify Integrations with Downloads and SDKs.
My question is similar to "Removing (or prefilling) the address details for PayPal Express Checkout" but I don't want shipping cost/address or anything related about shipping at all.
I keep all shipping details on my system (even sometimes shipping doesn't even apply and there is no charge for it) and I just want user to pay through PayPal without shipping address and shipping cost.
How can I disable shipping part of checkout?
If you're using the newer API, you could also pass NOSHIPPING=1 (not no_shipping)
Further details about all possible parameters to the SetExpressCheckout here:
https://developer.paypal.com/docs/nvp-soap-api/nvp/
Or lookup for Payment experience in new REST API
For others looking for this, because PayPals documentation is so GREAT (cough, cough).
NO web experience profile REQUIRED!
Using REST API V2 with Javascript/JQuery and turning "Ship To" off for an ORDER, here is the correct code example:
createOrder: function(data, actions) {
$('#paypalmsg').html('<b>' + 'WAITING ON AUTHORIZATION TO RETURN...' + '</b>');
$('#chkoutmsg').hide()
return actions.order.create({
purchase_units: [{
description: 'GnG Order',
amount: {
value: cartTotal
}
}],
application_context: {
shipping_preference: 'NO_SHIPPING'
}
});
},
Hey Ergec, just pass along the no_shipping parameter with a value of 1.
From PayPal's documentation:
no_shipping
Do not prompt payers for shipping address. Allowable values:
0 – prompt for an address, but do not require one
1 – do not prompt for an address
2 – prompt for an address, and require one
The default is 0.
The current right answer is depracated. To fix the issue in new API we should create Payment web experience profile resource with needed parameters and attach it to request Payment .
Example in PHP:
/** Note: Define some variables yourself. */
$inputFields = new InputFields();
$inputFields->setAllowNote(true)
->setNoShipping(1) // Important step
->setAddressOverride(0);
$webProfile = new WebProfile();
$webProfile->setName(uniqid())
->setInputFields($inputFields)
->setTemporary(true);
$createProfile = $webProfile->create($apiContext);
$payment = new Payment();
$payment->setPayer($payer);
$payment->setIntent($intent);
$payment->setRedirectUrls($redirectUrls)
$payment->setTransactions(array($transaction));
$payment->setExperienceProfileId($createProfile->getId()); // Important step.
$payment->create($apiContext);
if ($payment->getState() === "created") {
$approvalLink = $payment->getApprovalLink()
header("Location: $approvalLink"); // Redirects user to PayPal page.
}
Note: You can find all above used classes by link: https://github.com/paypal/PayPal-PHP-SDK/tree/master/lib/PayPal/Api
To solve for this from current (2019) web client .JS, add the application_context block to the request body.
Below is an example for createSubscription() call; and I'm thinking this will work with createOrder() as well
paypal.Buttons({
createSubscription: function (data, actions) {
return actions.subscription.create({
'plan_id' : 'P-123',
'application_context': {
'shipping_preference': 'NO_SHIPPING'
}
});
},
onApprove: function (data, actions) {
// ...
}
})
.render('#paypal-button-container');
Thanks to the example code here:
https://developer.paypal.com/docs/subscriptions/integrate/#4-create-a-subscription
Here's where the field enums are listed:
https://developer.paypal.com/docs/classic/api/merchant/SetExpressCheckout-API-Operation-NVP/
Create a web profile based on the example found in the API: CreateWebProfile.php.
$createProfileResponse = require __DIR__ . '/CreateWebProfile.php';
$payment = new Payment();
$payment->setExperienceProfileId($createProfileResponse->getId());
File path: paypal/rest-api-sdk-php/sample/payment-experience/CreateWebProfile.php
2022 and beyond
The documentation for V2 of the PayPal API, currently the latest version, and which seems to have been cleaned up a lot over the past year or so, states the following:
shipping_preference
Displays the shipping address to the customer.
Enables the customer to choose an address on the PayPal site.
Restricts the customer from changing the address during the payment-approval process.
The possible values are:
GET_FROM_FILE. Use the customer-provided shipping address on the PayPal site.
NO_SHIPPING. Redact the shipping address from the PayPal site. Recommended for digital goods.
SET_PROVIDED_ADDRESS. Use the merchant-provided address. The customer cannot change this address on the PayPal site.
Therefore, simply adding:
"application_context" => [
"shipping_preference" => "NO_SHIPPING"
]
or if using JavaScript:
"application_context": {
"shipping_preference" => "NO_SHIPPING"
}
...to your order creation should disable any shipping options.
As an example, my PHP code to create an order using PayPal's Standard Checkout Integration (which in turn makes use of the Smart Buttons) now looks like this:
$order = $paypal->createOrder([
"intent"=> "CAPTURE",
"purchase_units"=> [
[
"amount"=> [
"currency_code" => "GBP",
"value"=> 4.99
],
'description' => 'My product description',
],
],
"application_context" => [
"shipping_preference" => "NO_SHIPPING"
]
]);
#Ergec : I tried this:
$nvpstr = "&ADDRESSOVERRIDE=1".$shiptoAddress."&L_NAME0=".$L_NAME0."&L_NAME1=".$L_NAME1."&L_AMT0=".$L_AMT0."&L_AMT1=".$L_AMT1."&L_QTY0=".$L_QTY0."&L_QTY1=".$L_QTY1."&MAXAMT=".(string)$maxamt."&ITEMAMT=".(string)$itemamt."&AMT=".$itemamt."&ReturnUrl=".$returnURL."&CANCELURL=".$cancelURL."&CURRENCYCODE=".$currencyCodeType;
It works. Here we can also use shipping address even though we are not charging any amount.