PayPal REST API - 沙盒为 API 请求返回 401 但访问令牌成功

Posted

技术标签:

【中文标题】PayPal REST API - 沙盒为 API 请求返回 401 但访问令牌成功【英文标题】:PayPal REST API - Sandbox returning 401 for API requests but successful access token 【发布时间】:2013-08-21 19:47:32 【问题描述】:

我正在使用 Java 中的 PayPal REST API 向测试(沙盒)服务器发出请求以获取访问令牌,该请求成功,然后将该访问令牌发送回相同的沙盒服务器以进行支付,但失败并显示401 未授权。

我按照此处找到的 PayPal REST API 说明进行操作:https://developer.paypal.com/webapps/developer/docs/api/#authentication--headers 和此处https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/

我尝试使用OAuthTokenCredential 对象来提供我的clientIdclientSecret,但它只返回访问令牌,而不是PayPal 提供的appIDexpiresIn 时间(以分钟为单位)。此外,它还抱怨我的类路径中缺少sdk_conf.properties 文件。

然后我创建了自己的类来使用 Apache 的 HttpClient 进行调用以获得完整的响应,并且有效:

RestEasy 的自定义 PayPalAPI 接口

@Path("v1/oauth2/token")
public interface PayPalOAuthAPI 
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public OAuthInfo requestAccessToken(
        @HeaderParam("Authorization") String basicEncodedToken, 
        String requestBody
    );

自定义 OAuth 令牌请求

private OAuthInfo getOAuthInfo() 
    OAuthInfo info = null;

    PayPalOAuthAPI client = HttpClientFactory.createRestEasyClient(80, 443, 120, 2,
                                          API_ENDPOINT, PayPalOAuthAPI.class, true);
    info = client.requestAccessToken("Basic "+
                                    generateBase64String(PP_API_USER, PP_API_SECRET),
                                    "grant_type=client_credentials");

    if (info == null) 
        throw new RuntimeException(APIResultCodes.REMOTE_UNAUTHORIZED, 
                                   "Could not authenticate with PayPal.");
    
    return info;

这一切正常,并返回一个 200 OK 状态和预期的 JSON 响应,包括访问令牌。

然后我获取该令牌并将其提供给我的 Payment 对象,该对象使用 PayPal 的代码与他们的服务器通信(在后台我相信它是基本的 java.lang.net.HttpConnection 代码)并返回 401 Unauthorized。

使用 PayPal 的 REST API 进行支付调用

// get a new token
String accessToken = getOAuthInfo().getAccessToken();

// configProps is a Properties object pre-populated with sdk_conf.properties
// values except service.EndPoint, clientID, and clientSecret
configProps.setProperty("service.EndPoint", API_ENDPOINT);

// ... transaction objects omitted

try 
    Payment payment = new Payment();
    Payment.initConfig(configProps);
    payment.setIntent(Intent.sale.toString());
    payment.setPayer(payer);
    payment.setTransactions(transactions);

    Payment createdPayment = payment.create(accessToken);
 catch (PayPalRESTException ex) 
    LOG.error("Failed to submit donation", ex);

日志输出

(包括已编辑 ID 的 HttpClient 请求/响应)

2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.impl.conn.DefaultClientConnection Sending request: POST /v1/oauth2/token HTTP/1.1
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "POST /v1/oauth2/token HTTP/1.1[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Content-Type: application/x-www-form-urlencoded[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Accept: application/json[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Authorization: Basic QWM1UmNoQ3g3dHB1bWZSZW9rcUR3MW41bHRXaktQU0xRODIyWUJWWXpEdXpZUGJuc0J0eDZYWGlqX1pROkVJVnQ5eERmM2JnQmw1OG5KYlZ2VmtSR3JCaVZVN1BIWGtSV01mQjVqb3NxRTNkbWxCcF9TV05BdU91eA==[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Accept-Encoding: gzip, deflate[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Content-Length: 29[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Host: api.sandbox.paypal.com[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "Connection: Keep-Alive[\r][\n]"
2013-08-19 16:34:11,077 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "User-Agent: Apache-HttpClient/4.2 (java 1.5)[\r][\n]"
2013-08-19 16:34:11,078 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "[\r][\n]"
2013-08-19 16:34:11,078 [qtp1005310362-44] DEBUG:org.apache.http.wire >> "grant_type=client_credentials"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "HTTP/1.1 200 OK[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "Server: Apache-Coyote/1.1[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "Date: Mon, 19 Aug 2013 20:34:11 GMT[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "PayPal-Debug-Id: d29c41eb8625a[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "Content-Type: application/json[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "Content-Length: 282[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44] DEBUG:org.apache.http.wire << "[\r][\n]"
2013-08-19 16:34:12,690 [qtp1005310362-44]     2013-08-19 16:34:12,691 [qtp1005310362-44] DEBUG:org.apache.http.impl.client.DefaultHttpClient Connection can be kept alive indefinitely
2013-08-19 16:34:12,691 [qtp1005310362-44] DEBUG:org.apache.http.wire << ""scope":"https://api.paypal.com/v1/payments/.* https://api.paypal.com/v1/vault/credit-card https://api.paypal.com/v1/vault/credit-card/.* openid","access_token":"RVATuLOQB0WFX
    keSDZQw4ZnyfIduPCF2j7sMhcfspwo","token_type":"Bearer","app_id":"APP-80W284285Q519543T","expires_in":28800"
2013-08-19 16:34:12,693 [qtp1005310362-44] DEBUG:org.apache.http.impl.conn.PoolingClientConnectionManager Connection [id: 5][route: s->https://api.sandbox.paypal.com] can be kept alive indefinitely
2013-08-19 16:34:12,693 [qtp1005310362-44] DEBUG:org.apache.http.impl.conn.PoolingClientConnectionManager Connection released: [id: 5][route: s->https://api.sandbox.paypal.com][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 2]
2013-08-19 16:34:15,197 [qtp1005310362-44] ERROR:com.example.gateways.paypal.payments.PayPalPaymentsProREST Failed to submit donation
    com.paypal.core.rest.PayPalRESTException: Error code : 401 with response : Server returned HTTP response code: 401 for URL: https://api.sandbox.paypal.com/v1/payments/payment

我注意到 API 发送给我的范围和 URL 是两个不同的东西。也许我缺少一些将 API 调用发送到沙盒 URL 的配置?

在上面的代码中,您可以看到我将service.EndPoint 设置为API_ENDPOINT,在我的测试中设置为https://api.sandbox.paypal.com。我也没有在我的属性对象中提供clientIDclientSecret,但即使提供它们也会给我一个 401。

我还缺少什么?

【问题讨论】:

这里有同样的问题。找到解决方案了吗? 没有官方解决方案。我切换回使用官方 OAuthTokenCredentials 对象并且该对象有效(但我无法访问 APP_ID 或 expires_in 时间。然后我改回我自己的 OAuthUtil 对象并开始进行捐赠。但是,每次捐赠都失败了500 错误、超时或返回 null。我现在正在尝试使用 Classic API。 【参考方案1】:

在尝试为第三方 PayPal 帐户付款时遇到了这个问题并遇到了类似的问题。

不确定您是否遇到了与我相同的情况,但我试图为我未正确请求许可的第三方 paypal 帐户创建付款。原来我需要使用 Permissions API 来请求适当的权限。

查看此链接:https://developer.paypal.com/webapps/developer/docs/classic/permissions-service/integration-guide/PermissionsAbout/

【讨论】:

这看起来像 PayPal 的经典 API。您是否将它与 REST API 一起使用? 我实际上在这里有点失败。事实证明,您不能混合使用 Classic 和 REST,因此您不能创建代表另一个帐户(依赖于经典权限 API)的 REST API 应用程序。这里要强调的要点是,如果您尝试使用 REST api 代表其他人发出请求,您可以获取访问令牌,但在尝试创建付款时会收到 401。 正确。据我所知,REST API 不允许您代表其他用户发出请求,即使您执行了完整的 OAUTH 委托流程。您必须使用经典 API 向另一方发出请求。正如您所指出的,REST 和 Classic 的应用程序域是不同的,需要单独的验证过程。

以上是关于PayPal REST API - 沙盒为 API 请求返回 401 但访问令牌成功的主要内容,如果未能解决你的问题,请参考以下文章

PayPal - 使用 REST API 从沙盒切换到真实账户

PayPal:无法创建沙盒帐户和 REST API 帐户

PayPal Rest API (PHP SDK) webhook 未显示在沙盒事件列表中

PayPal REST API [沙盒] - 执行付款销售时出现 INTERNAL_SERVICE_ERROR

PayPal REST API (PHP SDK) 成功交易未在沙盒模式下显示,在实时模式下显示

在 PayPal REST API 中查找 Braintree 计费协议 ID