使用服务器端 REST 调用时,PayPal Javascript SDK 是否支持 AUTHORIZE 意图?
Does the PayPal Javascript SDK support the AUTHORIZE intent when using server side REST calls?
我正在尝试结合使用 JavaScript SDK 和服务器端 REST 调用在我们的购物车中实施 PayPal 支付。我使用 JavaScript SDK 呈现按钮,并让 createOrder
和 onApprove
方法调用我的服务器上的端点,就像 PayPal 给出的示例那样 (JavaScript SDK example)。这些依次调用 PayPal REST API.
如果我正在捕获,所有这些都非常有用。但是,我们的要求是做一个授权。这似乎不起作用。 createOrder
调用成功完成并命中了 onApprove
方法。但是,当服务器端 REST 调用出现错误时失败:
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."}],"
没有提到我必须调用此 save
方法,事实上,如果我尝试调用该方法,我会收到一条错误消息,指示该交易尚未得到客户的批准。
这在使用纯客户端方法执行此操作时也能正常工作,但出于多种原因我们希望避免这种情况。贝宝文档似乎表明这应该是可能的。
我的代码的 simplified/sanitized 版本如下:
<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>
后端通过控制器和一些服务,但最终生成的请求(使用 C# SDK 包:PayPalCheckoutSdk)如下所示:
创建订单:
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
};
}
授权订单:
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
};
}
正如我所说,如果我将其更改为使用“CAPTURE”意图,这一切都将完美运行。只有当我尝试“授权”时,我才会收到此错误。我尝试在没有金额的情况下进行最终授权调用,以防万一,但收到错误消息,指出缺少所需的付款金额字段。
有没有人有任何想法,或者如果不进行旧式重定向就不可能做到这一点?我想避免这两种情况,并使用纯粹的客户端方法来处理这个问题,例如:
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');
有可能。用于授权订单的 REST API 调用应该是一个 POST, 没有负载主体 。
这是一个示例日志,由服务器上的 createOrder 触发:
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"
}
]
}
在服务器上跟这个,在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"
}
]
}
使用 Checkout-NET-SDK is here 执行此操作的示例。
我正在尝试结合使用 JavaScript SDK 和服务器端 REST 调用在我们的购物车中实施 PayPal 支付。我使用 JavaScript SDK 呈现按钮,并让 createOrder
和 onApprove
方法调用我的服务器上的端点,就像 PayPal 给出的示例那样 (JavaScript SDK example)。这些依次调用 PayPal REST API.
如果我正在捕获,所有这些都非常有用。但是,我们的要求是做一个授权。这似乎不起作用。 createOrder
调用成功完成并命中了 onApprove
方法。但是,当服务器端 REST 调用出现错误时失败:
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."}],"
没有提到我必须调用此 save
方法,事实上,如果我尝试调用该方法,我会收到一条错误消息,指示该交易尚未得到客户的批准。
这在使用纯客户端方法执行此操作时也能正常工作,但出于多种原因我们希望避免这种情况。贝宝文档似乎表明这应该是可能的。
我的代码的 simplified/sanitized 版本如下:
<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>
后端通过控制器和一些服务,但最终生成的请求(使用 C# SDK 包:PayPalCheckoutSdk)如下所示:
创建订单:
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
};
}
授权订单:
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
};
}
正如我所说,如果我将其更改为使用“CAPTURE”意图,这一切都将完美运行。只有当我尝试“授权”时,我才会收到此错误。我尝试在没有金额的情况下进行最终授权调用,以防万一,但收到错误消息,指出缺少所需的付款金额字段。
有没有人有任何想法,或者如果不进行旧式重定向就不可能做到这一点?我想避免这两种情况,并使用纯粹的客户端方法来处理这个问题,例如:
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');
有可能。用于授权订单的 REST API 调用应该是一个 POST, 没有负载主体 。
这是一个示例日志,由服务器上的 createOrder 触发:
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"
}
]
}
在服务器上跟这个,在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"
}
]
}
使用 Checkout-NET-SDK is here 执行此操作的示例。