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

PayPal REST API is not executing payments. Cannot change state of Payment object from 'created' to 'approved' or 'completed'

我正在使用servlet生成HTML页面,这些页面旨在使用新的REST API执行PayPal付款。我尚未能够生成状态为已批准或已创建的付款。我已经在github页面上的PaymentWithPayPalServlet.java示例中尝试了变体,并且在下面添加了最接近的变体。我最不清楚哪个对象应该接收到"已批准"或"已完成"的更新状态以及何时接收。

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

我创建了具有所有适当属性/属性集的Payment对象。

1
2
3
4
5
6
Payment payment = new Payment();
payment.setIntent("sale");
payment.setPayer(payer);
payment.setTransactions(transactions);
payment.setRedirectUrls(redirectUrls);
Payment createdPayment = payment.create(apiContext);

现在,createdPayment对象的状态为" created "。在我的代码末尾,我将用户重定向到payPal页面,在该页面上我假定payPal将在执行之前批准付款。请注意,这与示例文件有所不同,但我不明白为什么这不起作用。

1
2
3
4
5
if (link.getRel().equalsIgnoreCase("approval_url"))
{
         req.setAttribute("redirectURL", link.getHref());
         resp.sendRedirect(link.getHref());
}

由于payPal无法通过HTTP协议更改我的本地变量createdPayment的状态,因此我希望payPal页面重定向回我的returnURL页面,该页面的payingID以及可能附加在URL上的授权令牌。使用这两件事,我希望可以通过某种类型的静态函数调用从payPal服务器检索付款对象,例如:

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

但是,URL没有附加任何PaymentID。而是有一个payerID。此外,我所有从PayPal服务器检索状态为"已批准"或"已完成"的付款对象的尝试均失败了。基本上,我尝试了以下变化,但无济于事:

1
2
3
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类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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()方法的redirectURLs中的cancelURL和returnURL中。然后,请确保将相关的Payment对象附加到用户的HttpSession对象中,以便以后在returnURL servlet中进行检索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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页面时,我将调用以下相关代码片段:

1
2
3
4
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对象中更为有用。但是,旧的"创建的付款"是什么?仍然处于"创建"状态,而不是"已批准"状态或" completeda"。我不知道如何从上面已经概述的对象创建到执行相同的Payment对象的过程。实际上,我什至不了解使用方法付款是否等于paypala?应该使用相同的付款对象创建和执行。正如我在原始帖子中所描述的那样,对我来说应该是没有道理的。我创建了一个付款对象,payPal发回了一个roval_URL,其中包括我,用于将用户重定向到以进行批准。这会将用户带离我的Web应用程序,并进入Pay??Pal服务器。由于PayPal无法修改我的本地" createdPaymenta"?变量,贝宝(PayPal)无法更改其状态。此外,似乎我需要状态为"已批准"的"付款"对象。为了执行付款。因此,我得出结论,贝宝(PayPal)必须向我发送一个新的Payment对象,该对象应具有与我的createdPaymenta相同的信息。对象,但具有已批准状态的更新状态,并且可能具有指示已批准状态的特殊令牌/密码,以防止某人将一大笔付款入侵已批准状态,这很容易做到。我看到我正在按预期方式获得令牌,但是我没有按预期获得付款ID。我正在获取一个payerID。有没有一种方法可以将返回的令牌和payerID转换为状态为a?approveda?的新Payment对象,或者我只是完全丢失了什么?


是的,从PayPal网站页面重定向后," payerId"将被附加为请求参数,而不是付款ID。但是您还需要付款ID才能执行它。这是来自payPal交互工具的代码。

1
2
3
4
5
6
7
8
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:

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

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

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

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

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

以便以后使用它

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

希望这会有所帮助


根据贝宝开发人员门户,返回的对象如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
   "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"
}]}

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

希望对您有帮助。