PayPal REST API 未执行付款。无法将付款对象的状态从“已创建”更改为“已批准”或“已完成”

Posted

技术标签:

【中文标题】PayPal REST API 未执行付款。无法将付款对象的状态从“已创建”更改为“已批准”或“已完成”【英文标题】:PayPal REST API is not executing payments. Cannot change state of Payment object from 'created' to 'approved' or 'completed' 【发布时间】:2013-12-19 22:38:37 【问题描述】:

我正在使用 servlet 生成 html 页面,这些页面旨在使用新的 REST api 执行 PayPal 付款。我还无法生成状态为已批准或已创建的付款。我已经尝试了来自 github 页面的 PaymentWithPayPalServlet.java 示例的变体,并且我在下面包含了最接近的变体。我最不清楚哪个对象应该收到“已批准”或“已完成”的更新状态以及何时。

虽然我在下面包含了完整的代码,但这里是我的想法的快速分解。也许有人可以纠正我哪里出错了......

我创建了一个设置了所有适当属性/属性的 Payment 对象。

Payment payment = new Payment();
payment.setIntent("sale");
payment.setPayer(payer);
payment.setTransactions(transactions);
payment.setRedirectUrls(redirectUrls);
Payment createdPayment = payment.create(apiContext);

现在,createdPayment 对象的状态为“已创建”。在我的代码结束时,我将用户重定向到 payPal 页面,我假设 payPal 会在执行前批准付款。请注意,这是与示例文件的偏差,但我不明白为什么这不起作用。

if (link.getRel().equalsIgnoreCase("approval_url")) 

         req.setAttribute("redirectURL", link.getHref());
         resp.sendRedirect(link.getHref());

由于 payPal 无法通过 HTTP 协议更改我的本地变量 createdPayment 的状态,我希望 payPal 页面重定向回我的 returnURL 页面,并带有一个 paymentID 和可能附加到 URL 的授权令牌。使用这两件事,我希望通过某种类型的静态函数调用从 payPal 服务器检索支付对象,例如:

String authToken=req.getParameter("token");
String paymentID=req.getParameter("paymentID");
Payment approvedPayment=Payment.getPaymentObject(authToken,paymentID);

但是,该 URL 没有附加任何 paymentID。相反,有一个 payerID。此外,我从贝宝服务器检索状态为“已批准”或“已完成”的支付对象的所有尝试都失败了。基本上,我尝试了以下变化无济于事:

String authToken=req.getParameter("token");
String paymentID=req.getParameter("payerID");
Payment approvedPayment=Payment.get(authToken,payerID);

如果有人能告诉我我的推理出了什么问题,那就太棒了。谢谢!这是我的主 servlet 页面的完整代码。请注意,returnURL 会将您带回到同一页面,在该页面中它会找到包含在来自 payPal 的 HTTP 请求中的 payerID,并正确输入主 if-else 语句的“if”块,这会生成一些完全无用的基本输出。另请注意,我已将一些基本函数调用(例如检索访问令牌和上下文)外包给了其他类,例如 AccessToken 类。

public class PaymentInfoServlet2 extends HttpServlet 

    private static final long serialVersionUID = 1L;

   // private static final Logger LOGGER = Logger
     //               .getLogger(PaymentWithPayPalServlet.class);
    Map<String, String> map = new HashMap<String, String>();

    public void init(ServletConfig servletConfig) throws ServletException 
            // ##Load Configuration
            // Load SDK configuration for
            // the resource. This intialization code can be
            // done as Init Servlet.
            InputStream is = PaymentInfoServlet2.class
                            .getResourceAsStream("/sdk_config.properties");
            try 
                    PayPalResource.initConfig(is);
             catch (PayPalRESTException e) 
                  //  LOGGER.fatal(e.getMessage());
            

    

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                    throws ServletException, IOException 
            doPost(req, resp);
    

    // ##Create
    // Sample showing to create a Payment using PayPal
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                    throws ServletException, IOException 
            // ###AccessToken
            // Retrieve the access token from
            // OAuthTokenCredential by passing in
            // ClientID and ClientSecret
            APIContext apiContext = null;
            String accessToken = null;
            try 
                AccessToken access=new AccessToken(PublicUtils.getUser(),PublicUtils.getPass(),req,resp);

                    accessToken = access.getToken();

                    // ### Api Context
                    // Pass in a `ApiContext` object to authenticate
                    // the call and to send a unique request id
                    // (that ensures idempotency). The SDK generates
                    // a request id if you do not pass one explicitly.
                    apiContext = access.getContext();
                    // Use this variant if you want to pass in a request id
                    // that is meaningful in your application, ideally
                    // a order id.
                    /*
                     * String requestId = Long.toString(System.nanoTime(); APIContext
                     * apiContext = new APIContext(accessToken, requestId ));
                     */
             catch (Exception e) 
                    req.setAttribute("error", e.getMessage());
            
            if (req.getParameter("PayerID") != null) 
                    Payment payment = new Payment();
                    if (req.getParameter("guid") != null) 
                            payment.setId(map.get(req.getParameter("guid")));
                    

                    PaymentExecution paymentExecution = new PaymentExecution();
                    paymentExecution.setPayerId(req.getParameter("PayerID"));
                    try 
                            payment.execute(apiContext, paymentExecution);
                            req.setAttribute("response", Payment.getLastResponse());
                     catch (PayPalRESTException e) 
                            req.setAttribute("error", e.getMessage());
                    
                    PrintWriter out=resp.getWriter();
                    out.println("This is the returnURL page.");
                    out.println("paymentID="+payment.getId());
                    out.println("pamentState="+payment.getState());
                    out.println("executedPayerID="+paymentExecution.getPayerId());
                   // out.println("executedTransaction: "+paymentExecution.getTransactions().get(0).toString());
             else 

                    // ###Details
                    // Let's you specify details of a payment amount.
                    Details details = new Details();
                    details.setShipping("1");
                    details.setSubtotal("5");
                    details.setTax("1");

                    // ###Amount
                    // Let's you specify a payment amount.
                    Amount amount = new Amount();
                    amount.setCurrency("USD");
                    // Total must be equal to sum of shipping, tax and subtotal.
                    amount.setTotal("7");
                    amount.setDetails(details);

                    // ###Transaction
                    // A transaction defines the contract of a
                    // payment - what is the payment for and who
                    // is fulfilling it. Transaction is created with
                    // a `Payee` and `Amount` types
                    Transaction transaction = new Transaction();
                    transaction.setAmount(amount);
                    transaction
                                    .setDescription("This is the payment transaction description.");

                    // The Payment creation API requires a list of
                    // Transaction; add the created `Transaction`
                    // to a List
                    List<Transaction> transactions = new ArrayList<Transaction>();
                    transactions.add(transaction);

                    // ###Payer
                    // A resource representing a Payer that funds a payment
                    // Payment Method
                    // as 'paypal'
                    Payer payer = new Payer();
                    payer.setPaymentMethod("paypal");

                    // ###Payment
                    // A Payment Resource; create one using
                    // the above types and intent as 'sale'
                    Payment payment = new Payment();
                    payment.setIntent("sale");
                    payment.setPayer(payer);
                    payment.setTransactions(transactions);

                    // ###Redirect URLs
                    RedirectUrls redirectUrls = new RedirectUrls();
                    String guid = UUID.randomUUID().toString().replaceAll("-", "");
                    redirectUrls.setCancelUrl(req.getScheme() + "://"
                                    + req.getServerName() + ":" + req.getServerPort()
                                    + req.getContextPath() + "/CancelServlet?guid=" + guid);
                    redirectUrls.setReturnUrl(req.getScheme() + "://"
                                    + req.getServerName() + ":" + req.getServerPort()
                                    + req.getContextPath() + "/PaymentInfoServlet2?guid=" + guid);
                    payment.setRedirectUrls(redirectUrls);

                    // Create a payment by posting to the APIService
                    // using a valid AccessToken
                    // The return object contains the status;
                    try 
                            Payment createdPayment = payment.create(apiContext);
                         //   LOGGER.info("Created payment with id = "
                           //                 + createdPayment.getId() + " and status = "
                             //               + createdPayment.getState());
                            // ###Payment Approval Url
                            Iterator<Links> links = createdPayment.getLinks().iterator();
                            while (links.hasNext()) 
                                    Links link = links.next();
                                    if (link.getRel().equalsIgnoreCase("approval_url")) 
                                            req.setAttribute("redirectURL", link.getHref());
                                            resp.sendRedirect(link.getHref());
                                    
                            
                            req.setAttribute("response", Payment.getLastResponse());
                            map.put(guid, createdPayment.getId());
                     catch (PayPalRESTException e) 
                            req.setAttribute("error", e.getMessage());
                    
            
            req.setAttribute("request", Payment.getLastRequest());
            //req.getRequestDispatcher("response.jsp").forward(req, resp);
    


针对我下面的第一位评论者 Yozha Karlov,我添加了以下内容:

嗯,我想我对此有几个反应。首先,谢谢。其次,我想也许我对我的问题不够清楚。我对如何检索上面名为“createdPayment”的旧支付对象并不感到困惑。在上面复制的完整代码中,我使用了与您引用的完全相同的 guid 代码。这样做的一个问题是,它除了将 createdPayment 对象的 ID 复制到一个新的空白 Payment 对象中之外,什么都不做。新对象的状态仍然是空白的,它的所有其他属性也是如此。它是一个带有 ID 的空白对象,仅此而已。几乎一文不值,所以我要么遗漏了一些东西,要么示例 servlet 完全错误。

实际上,我最初的方法是创建一个静态类,其中包含一个静态映射,以将 sessionID 映射到 HttpSession 对象。我将为用户的浏览器会话生成一个 sessionID,并将该 sessID 附加到 payment.create() 方法的重定向 URL 中的 cancelURL 和 returnURL。然后,我会确保将相关的 Payment 对象附加到用户的 HttpSession 对象中,以便稍后在 returnURL servlet 中检索。

public class SessionStore 
    public static Map<String, HttpSession> map = new HashMap<String,HttpSession>();


and in my main servlet, called PaymentInfoServlet2, here is the relevant code that i execute before the servlet ends and the user is re-directed to the payPal pages:

HttpSession sess=req.getSession();
String sessID=sess.getId();
SessionStore.map.put(sessID, sess);

// append sessID to redirectURLs - the URLs that the payPal pages return back to
approveURL=req.getScheme() + "://"+ req.getServerName() + ":" +req.getServerPort()+ req.getContextPath() +"/ApproveServlet?sessID=" +sess.getId();
cancelURL=req.getScheme() + "://"+ req.getServerName() + ":" + req.getServerPort()+ req.getContextPath() +"/CancelServlet?sessID=" +sess.getId();
redirectUrls.setCancelUrl(cancelURL);
redirectUrls.setReturnUrl(approveURL);
payment.setRedirectUrls(redirectUrls);

// create the payment on the payPal server
Payment createdPayment = payment.create(access.getContext());

//add created Payment object to HttpSession object.
ArrayList<Payment> createdPayments=new ArrayList<Payment>();
createdPayments.add(createdPayment);
sess.setAttribute("createdPayments", createdPayments);

// redirect to payPal pages
Iterator<Links> links = createdPayment.getLinks().iterator();
while (links.hasNext()) 

    Links link = links.next();
    if (link.getRel().equalsIgnoreCase("approval_url")) 
    
            url=link.getHref();
        resp.sendRedirect(url);
    

然后,当payPal页面将我返回到我之前的returnURL页面时,我会调用以下相关代码-sn-ps:

String sessID=req.getParameter("sessID");
HttpSession sess=SessionStore.map.get(sessID);
ArrayList<Payment> cPay=(ArrayList<Payment>)sess.getAttribute("createdPayments");
Payment payment=(Payment)cPay.get(0);

并且已检索到具有所有相同属性和所有内容的旧支付对象。这似乎比仅仅将旧的付款 ID 复制到一个空白的 Payment 对象中要有用得多。但是,旧的“createdPayment”仍然处于“created”状态,而不是“approved”或“completed”。我不知道如何从创建上面概述的对象转变为执行相同的 Payment 对象。事实上,我什至不明白使用 method=‘paypal’ 的支付是否应该使用相同的支付对象来创建和执行。正如我在原始帖子中所描述的,对我来说应该如此是没有意义的。我创建了一个支付对象,payPal 发回一个approval_URL 等,让我将用户重定向到批准。这会将用户带出我的网络应用程序并进入贝宝服务器。由于 payPal 无法修改我的本地“createdPayment”变量,因此 payPal 无法更改其状态。此外,我似乎需要一个状态为“已批准”的 Payment 对象才能执行付款。因此,我得出的结论是,payPal 必须向我发回一个新的 Payment 对象,其中包含许多与我的“createdPayment”对象相同的信息,但更新状态为已批准,可能还有一个特殊的令牌/密码指示已批准状态,以防止某人只需将一堆付款更改为已批准状态,这很容易做到。我看到我得到了一个令牌,正如预期的那样,但我没有得到一个我预期的 paymentID。我正在获得一个 payerID。有没有办法将返回的令牌和 payerID 转换为状态为“已批准”的新支付对象,还是我完全遗漏了什么?

【问题讨论】:

【参考方案1】:

根据 paypal 开发者门户,返回的对象如下所示:


    "id": "PAY-50299450TD463315FK4MDBAI",
    "intent": "sale",
    "state": "created",
    "payer": 
        "payment_method": "paypal"
        ,
"transactions": [

  "amount": 
    "total": "7.00",
    "currency": "USD",
    "details": 
      "subtotal": "5.00",
      "tax": "1.00",
      "shipping": "1.00"
    
  ,
  "description": "This is the payment transaction description.",
  "item_list": 
    "items": [
      
        "name": "Ground Coffee 40 oz",
        "price": "5.00",
        "currency": "USD",
        "quantity": 1
      
    ]
  ,
  "related_resources": []
],
"create_time": "2016-04-21T01:44:32Z",
"links": [

  "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-50299450TD463315FK4MDBAI",
  "rel": "self",
  "method": "GET"
,

  "href": "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-27C19584EL875221E",
  "rel": "approval_url",
  "method": "REDIRECT"
,

  "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-50299450TD463315FK4MDBAI/execute",
  "rel": "execute",
  "method": "POST"
]

因此,您可能会看到“已创建”字段状态。您还有“创建时间”字段。

希望对你有帮助。

【讨论】:

【参考方案2】:

您是对的,从 PayPal 网站页面重定向后,“payerId”将作为请求参数附加,而不是付款 ID。但是您还需要付款 ID 才能执行它。这是来自payPal interactive tool的代码。

String accessToken = "Bearer Jfdd4h4VrmvLeATBNPsGOpp7pMosTppiy.Jq6xpwQ6E";
APIContext apiContext = new APIContext(accessToken);
apiContext.setConfigurationMap(sdkConfig);

Payment payment = new Payment("PAY-4AL22602580048540KKPBSNY");
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId("BKJ78SZZ8KJYY");
payment.execute(apiContext, paymentExecute);

所以棘手的部分是如何在请求之间保存支付 ID,因为 HTTP 不保存我们所知道的任何状态。您可以在您提到的示例中看到它是如何实现的:

https://github.com/paypal/rest-api-sdk-java/blob/master/rest-api-sample/src/main/java/com/paypal/api/payments/servlet/PaymentWithPayPalServlet.java

所以他们生成uuid:

String guid = UUID.randomUUID().toString().replaceAll("-", "");

在重定向url中添加guid参数:

redirectUrls.setReturnUrl(req.getScheme() + "://"
      + req.getServerName() + ":" + req.getServerPort()
      + req.getContextPath() + "/paymentwithpaypal?guid=" + guid);

并将此 guid 参数与已创建付款的 id 相关联:

map.put(guid, createdPayment.getId());

为了以后使用

payment.setId(map.get(req.getParameter("guid")));

希望这会有所帮助

【讨论】:

我的评论太长了,所以我在原始问题的末尾添加了我的回复以解决您的评论。感谢您的帮助,请参阅上面的回复。 在上面的代码中,您调用了用于创建付款的构造函数,我得到一个错误,它需要一个 Payer 对象,以及为什么我会收到这个错误?

以上是关于PayPal REST API 未执行付款。无法将付款对象的状态从“已创建”更改为“已批准”或“已完成”的主要内容,如果未能解决你的问题,请参考以下文章

paypal rest api执行错误

使用 REST api 执行 PayPal 付款

PayPal REST API 未返回 payment_info

如何在使用 PayPal rest API 执行付款之前更改运费

使用 Paypal rest API 未显示付款详细信息

paypal Java Rest API 在付款执行前获取运输信息