I'm trying to implement PayPal payments in our shopping cart using a combination of the JavaScript SDK and server side REST calls. I render the buttons using the JavaScript SDK and have the createOrder and onApprove methods call endpoints on my server, as the examples given by PayPal do (JavaScript SDK example). These, in turn, call to the PayPal REST API.
All of this works great IF I'm doing a capture. However, our requirements are to do an authorization. This doesn't appear to work. The createOrder call successfully completes and the onApprove method is hit. However it fails when the server side REST call is made with an error:
issue":"AMOUNT_CANNOT_BE_SPECIFIED","description":"An authorization amount can only be specified if an Order has been saved by calling /v2/checkout/orders/{order_id}/save. Please save the order and try again."}],"
There is no mention that I can find of having to call this save method and indeed, if I try to call that method, I get an error indicating the transaction was not yet approved by the customer.
This also works fine when using the pure client side method of doing this, but we would like to avoid that for a number of reasons. The PayPal documentation seems to indicate that this should be possible.
A simplified/sanitized version of my code is below:
<script src="https://www.paypal.com/sdk/js?client-id=KEY¤cy=USD&disable-funding=card&components=funding-eligibility,buttons&intent=authorize&commit=true"></script>
<script>
paypal.Buttons({
createOrder: function (data, actions) {
return fetch("/checkout/paypal/order", {
method: "post",
body: $("#checkoutForm").serialize(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
}).then((order) => {
return order.json();
})
.then((orderData) => {
return orderData.OrderId;
});
},
onApprove: function (data, actions) {
return fetch("/checkout/paypal/authorize", {
method: "post",
body: $('#checkoutForm').serialize(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
})
.then((result) => {
console.log(result.json());
});
}
}).render('#paypal-button-container');
</script>
The backend goes through a controller and a number of services, but ultimately the resulting requests (using the C# SDK package: PayPalCheckoutSdk) looks like this:
Create Order:
public async Task<PayPalOrderResponseDTO> CreateOrder(PayPalOrderRequestDTO orderRequest)
{
OrderRequest ppor = new OrderRequest()
{
CheckoutPaymentIntent = "AUTHORIZE",
PurchaseUnits = new List<PurchaseUnitRequest>()
{
new PurchaseUnitRequest()
{
AmountWithBreakdown = new AmountWithBreakdown()
{
CurrencyCode = "USD",
Value = orderRequest.Total.ToString()
},
ShippingDetail = new ShippingDetail()
{
Name = new Name()
{
FullName = $"{orderRequest.ShippingAddress.FirstName} {orderRequest.ShippingAddress.LastName}"
},
AddressPortable = new AddressPortable()
{
AddressLine1 = orderRequest.ShippingAddress.Address1,
AddressLine2 = orderRequest.ShippingAddress.Address2,
AddressLine3 = orderRequest.ShippingAddress.Address3,
AdminArea2 = orderRequest.ShippingAddress.City,
AdminArea1 = orderRequest.ShippingAddress.State,
PostalCode = orderRequest.ShippingAddress.ZipCode,
CountryCode = orderRequest.ShippingAddress.CountryID
}
}
}
},
ApplicationContext = new ApplicationContext()
{
ShippingPreference = "SET_PROVIDED_ADDRESS",
LandingPage = "LOGIN",
UserAction = "PAY_NOW"
}
};
OrdersCreateRequest request = new OrdersCreateRequest();
request.Prefer("return=minimal");
request.RequestBody(ppor);
PayPalHttp.HttpResponse response = await _ppClient.Execute(request).ConfigureAwait(false);
System.Net.HttpStatusCode statusCode = response.StatusCode;
if (statusCode != System.Net.HttpStatusCode.Created)
{
// HANDLE ERROR
}
Order order = response.Result<Order>();
return new PayPalOrderResponseDTO()
{
Status = response.StatusCode.ToString(),
OrderID = order.Id
};
}
Authorize order:
public async Task<PayPalPaymentResponseDTO> Authorize(PayPalPaymentRequestDTO request)
{
OrdersAuthorizeRequest oar = new OrdersAuthorizeRequest(request.OrderID);
oar.RequestBody(new AuthorizeRequest()
{
Amount = new Money() { CurrencyCode = "USD", Value = request.Total },
ReferenceId = request.refId
});
PayPalHttp.HttpResponse response = await _ppClient.Execute(oar).ConfigureAwait(false);
Order order = response.Result<Order>();
return new PayPalPaymentResponseDTO()
{
StatusCode = (int)response.StatusCode,
ID = order.Id
};
}
As I said, this all works perfectly if I change it to use a "CAPTURE" intent. It's only when I attempt a "AUTHORIZE" that I get this error. I tried doing the final authorization call without the amount, just in case, but got an error indicating that the required payment amount field was missing.
Does any one have any ideas, or is this just not possible without doing an older-style redirect? I'd like to avoid both that and using the purely client side method of handling this using something like, e.g.:
paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [{
amount: {
value: '77.44' // Can also reference a variable or function
}
}]
});
},
// Finalize the transaction after payer approval
onApprove: (data, actions) => {
return actions.order.capture().then(function(orderData) {
// Successful capture! For dev/demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
actions.redirect('thank_you.html');
});
}
}).render('#paypal-button-container');
It is possible. The REST API call to authorize the order should be a POST with no payload body.
Here is an example log, triggered by createOrder on the server:
POST to v2/checkout/orders
{
"intent": "AUTHORIZE",
"purchase_units": [
{
"amount": {
"currency_code": "USD",
"value": "500",
"breakdown": {
"item_total": {
"currency_code": "USD",
"value": "500"
}
}
},
"items": [
{
"name": "Name of Item #1 (can be viewed in the upper-right dropdown during payment approval)",
"description": "Optional description; item details will also be in the completed paypal.com transaction view",
"unit_amount": {
"currency_code": "USD",
"value": "500"
},
"quantity": "1"
}
]
}
]
}
Response data
{
"id": "3J6935353G362625A",
"status": "CREATED",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/3J6935353G362625A",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/checkoutnow?token=3J6935353G362625A",
"rel": "approve",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/3J6935353G362625A",
"rel": "update",
"method": "PATCH"
},
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/3J6935353G362625A/authorize",
"rel": "authorize",
"method": "POST"
}
]
}
Followed by this on the server, triggered after onApprove:
Empty POST to v2/checkout/orders/3J6935353G362625A/authorize
Response data
{
"id": "3J6935353G362625A",
"status": "COMPLETED",
"purchase_units": [
{
"reference_id": "default",
"shipping": {
"name": {
"full_name": "Sandbox Buyer"
},
"address": {
"address_line_1": "123 street name",
"admin_area_2": "Phoenix",
"admin_area_1": "AZ",
"postal_code": "85001",
"country_code": "US"
}
},
"payments": {
"authorizations": [
{
"status": "CREATED",
"id": "9V1555595X9968645",
"amount": {
"currency_code": "USD",
"value": "500.00"
},
"seller_protection": {
"status": "ELIGIBLE",
"dispute_categories": [
"ITEM_NOT_RECEIVED",
"UNAUTHORIZED_TRANSACTION"
]
},
"expiration_time": "2022-05-13T20:14:50Z",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/payments/authorizations/9V1555595X9968645",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v2/payments/authorizations/9V1555595X9968645/capture",
"rel": "capture",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v2/payments/authorizations/9V1555595X9968645/void",
"rel": "void",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v2/payments/authorizations/9V1555595X9968645/reauthorize",
"rel": "reauthorize",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/3J6935353G362625A",
"rel": "up",
"method": "GET"
}
],
"create_time": "2022-04-14T20:14:50Z",
"update_time": "2022-04-14T20:14:50Z"
}
]
}
}
],
"payer": {
"name": {
"given_name": "Sandbox",
"surname": "Buyer"
},
"email_address": "sandboxbuyer#sandbox.com",
"payer_id": "THLKV8VCTCSKL",
"address": {
"country_code": "US"
}
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/3J6935353G362625A",
"rel": "self",
"method": "GET"
}
]
}
A sample to do this with the Checkout-NET-SDK is here.
I am trying to integrate the razorPay payment gateway.
payNow( ) {
var options = {
"key": "rzp_test_dveDexCQKoGszl",
"amount":((this.buyNow==1)?this.shared.totalAmountWithDisocuntBuyNow:this.shared.totalAmountWithDisocunt)*100, // 2000 paise = INR 20
"name": " MARKET",
"description": "Order #",
"handler": function (response){
console.log(response);
alert(response.razorpay_payment_id);
this.paid();
this.shared.addOrder(this.buyNow,response.razorpay_payment_id);
},
"prefill": {
"name": this.shared.orderDetails.billing_firstname + " " + this.shared.orderDetails.billing_lastname,
"email": this.shared.customerData.customers_email_address,
"contact": this.shared.customerData.customers_telephone,
},
"notes": {
"address": this.shared.orderDetails.billing_street_address+', '+this.shared.orderDetails.billing_city+', '+this.shared.orderDetails.billing_state+' '+this.shared.orderDetails.billing_postcode+', '+this.shared.orderDetails.billing_country
},
"theme": {
"color": "blue"
}
};
var rzp1 = new Razorpay(options);
rzp1.open();
// body...
}
paid()
{alert();}
I have defined in handler the callback function. But issue is it successfully logs the response and alerts it too but it does not calls
this.paid()
or
this.shared.addOrder(this.buyNow,response.razorpay_payment_id);
There are no console errors nothing. It just does not calls any of above methioned functions.
Call the handler function by binding it to function using following syntax
"handler":this.shared.addOrder.bind(this,this.buyNow) ,
This worked.
I'm trying to send an array of json objects to a local server by an HTTP 'POST' request. I've been using an npm module found here: https://www.npmjs.com/package/request to do the request. I've done this before and for the most part my code works fine, but I have an issue I've been unable to resolve.
var request = require('request');
const obj = require('./bugs.json');
obj.forEach(element => {
// Set the headers
var headers = {
'Content-Type': "stuff",
'x-authorization': "stuff"
}
// Configure the request
var options = {
url: 'http://localhost:8080/bugreport/import',
method: 'POST',
headers: headers,
form: {
'subject': element.subject,
'description': element.description,
'platform': element.platform,
'date': element.date,
'author': element.author,
'isSolved': element.isSolved,
'createdAt': element.createdAt,
'updatedAt': element.updatedAt,
'id': element.id
}
}
//
// Start the request
request(options, function(error, response, body){
if (!error && response.statusCode == 200) {
console.log("No error detected.");
console.log(body)
}
else {
console.log("Error: " + error);
}
})
});
The problematic line is 'author': element.author, While every other attribute is a basic data type, author is itself a nested json. Here is what the json file looks like:
[
{
"subject": "Bug",
"description": "Testing brscript",
"platform": "A",
"date": "2/15/2018",
"author": [
{
"username": "testuser1",
"firstName": "test",
"lastName": "user1",
"password": "password",
"admin": false,
"isVisible": true,
"isDeleted": false,
"createdAt": "2018-02-08T18:42:58.473Z",
"updatedAt": "2018-02-13T15:51:17.954Z",
"id": 170
}
],
"isSolved": false,
"createdAt": "2/15/2018",
"updatedAt": "2/15/2018",
"id": 999
}
]
As I said, I've done this before, but not with a nested json as one of my attributes. Everything but the author json will send. So, there must be something wrong with my syntax. I've tried several different approaches to get it to recognize the json, but none have worked.
try using JSON.stringify(body) in your console.log()
also application/json in your header Content-Type
I'm trying to add a document to my DocumentDB with the incoming payloads to an HttpTriggered Azure Function. This is the function.json content:
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "documentDB",
"name": "email",
"databaseName": "crawler",
"collectionName": "SendGrid",
"createIfNotExists": true,
"connection": "noho_DOCUMENTDB",
"direction": "out"
}
],
"disabled": false
}
And this is my function:
module.exports = function (context, req) {
try{
if (req.body) {
context.bindings.email = req.body;
context.res = {
status: 200,
body: req.body
};
}
else {
context.res = {
status: 400,
body: "Please pass a name on the query string or in the request body"
};
}
} catch(e) {
context.log(e);
context.res = {
status: 500,
body: e
};
}
context.done();
return req.body;
};
It fails and throws an exception while processing an input POST request with the following message:
Exception while executing function: Functions.SendGridJs.
Microsoft.Azure.Documents.Client: Value cannot be null.
Parameter name: document.
I've setup the integration through the Azure Function integration wizard and not through code deployment. So, I'm assuming that Azure is taking care of the setup code.
Both of your function.json and code of index.js are OK. You need to feed valid JSON string into req.body.
Here is my testing screenshot.
If I specify invalid JSON in the request body, I will get the same error as yours.
I am New to Integrate Payment Gateway.
How to Redirect a URL after Success or Failure Payments in Razorpay. I want Js Code. I thing handler function can use that redirect. How to Use them
var options = {
"key": "rzp_test_aIfnpuWEudWnID",
"amount": "35000", // 2000 paise = INR 20
"name": "Vanakkam Chennai",
"description": "I Run.. Becasue .. I Care",
"image": "http://vanakkamchennai.com/images/about/chennai_2017.jpg",
"callback_url": 'https://www.google.com',
"handler": function (response){
alert(response.razorpay_payment_id);
},
"prefill": {
"name": "Harshil Mathur",
"email": "harshil#razorpay.com"
},
"notes": {
"address": "Hello World"
},
"theme": {
"color": "#F37254"
}
};
The way to redirect a user is to alter the value of location.href. Remove alert(response.razorpay_payment_id); and redirect the user based on if the payment id is defined or not:
// alert(response.razorpay_payment_id);
if (typeof response.razorpay_payment_id == 'undefined' || response.razorpay_payment_id < 1) {
redirect_url = '/you-owe-money.html';
} else {
redirect_url = '/thnx-you-paid.html';
}
location.href = redirect_url;
My example urls are humorous; replace them with your actual urls. Also, you can read more detailed information on location.href and location redirection here.