paypal支付成功时会实时的把支付交易信息返回给我们,java会返回一个payment对象,里面有交易的信息包含付款人,订单费用,订单的收货地址,收款人,交易号等信息。我们拿到了这个payment就表示支付成功了,接下来就可以处理我们的逻辑了,比如修改订单状态,记录交易记录等操作了。
IPN其实就是paypal的交易状态的一种推送机制,是由paypal主动推送,只要我们配置好地址就可以了。
官方paypal对IPN的解释:IPN消息通知
消息地址设置
使用卖家账号登录paypal,然后找到用户信息-用户信息与设置,点进去
在点击销售工具,在里面找到即时付款通知项,点击更新
启用这个付款通知,并把自己的接口url填进去。
注意事项:
- 其实这个地址url可以是个restful的接口。
- 接口地址是https开头的,也就是要求使用SSL进行连接,其实Paypal Sandbox可以使用http,但是最后实际的Paypal接口,不支持http协议,paypal会校验是否是https地址。
- 在支付表单中,可以自己设置
notify_url
字段,来指定此次交易的信息应该发送到哪个地方,这样就可以覆盖在Profile中我们的设置,另外,这个字段要进行urlencode
- 我们得到的IPN信息中,
status
对应的便是交易状态,如Complete
表示完成,首字母大写,而验证结果则是VERIFIEY
或者INVALID
,全部大写,具体的内容,可以查看Paypal官方的文档订单管理整合指南。
处理逻辑:
IPN的原理很简单,就是当产生了一个交易之后,交易状态发生变化时,如用户已经付款、或者退款、撤销时,Paypal利用常用的HTTP POST方式,将交易的一些变量提交给网站的某个页面(称之为IPN Handler),当这个页面接受到请求时候,将这些数据原封不动加上一个指示验证的cmd=_notify-validate
,POST回Paypal的接口地址,如果数据正确,那么Paypal返回字符串VERIFIED,否则为INVALID,如果结果为VERIFIED,那么你的程序就可以使用这些数据进行操作。
通知传输的数据:
array (
'act' => 'ipn',
'mc_gross' => '20.00',
'invoice' => '548531d624f59',
'protection_eligibility' => 'Eligible',
'address_status' => 'unconfirmed',
'item_number1' => '',
'tax' => '1.30',
'item_number2' => '',
'payer_id' => 'JARYJK2TES6C6',
'address_street' => 'NO 1 Nan Jin Road',
'payment_date' => '21:04:35 Dec 07, 2014 PST',
'payment_status' => 'Completed',
'charset' => 'gb2312',
'address_zip' => '200000',
'mc_shipping' => '1.20',
'mc_handling' => '0.00',
'first_name' => 'Test',
'mc_fee' => '0.98',
'address_country_code' => 'CN',
'address_name' => 'Buyer Test',
'notify_version' => '3.8',
'custom' => '',
'payer_status' => 'unverified',
'address_country' => 'China',
'num_cart_items' => '2',
'mc_handling1' => '0.00',
'mc_handling2' => '0.00',
'address_city' => 'Shanghai',
'verify_sign' => 'AomRS5l2W2xlt2An.GaSrAzpCl-NACIvh3Pz0HtrSBZzfcIeqDPGrXSk',
'payer_email' => 'yxw.2013.03-buyer@gmail.com',
'mc_shipping1' => '0.00',
'mc_shipping2' => '0.00',
'tax1' => '0.00',
'tax2' => '0.00',
'txn_id' => '5CS19517SJ894934R',
'payment_type' => 'instant',
'last_name' => 'Buyer',
'address_state' => 'Shanghai',
'item_name1' => 'Ground Coffee 40 oz',
'receiver_email' => 'yxw.2013.03@gmail.com',
'item_name2' => 'Granola bars',
'payment_fee' => '0.98',
'quantity1' => '1',
'quantity2' => '5',
'receiver_id' => '937CP9PSMDS2A',
'txn_type' => 'cart',
'mc_gross_1' => '7.50',
'mc_currency' => 'USD',
'mc_gross_2' => '10.00',
'residence_country' => 'CN',
'test_ipn' => '1',
'transaction_subject' => '',
'payment_gross' => '20.00',
'ipn_track_id' => 'a9059421a1dd7',
)
我们接收这些数据使用 cmd=_notify-validate 拼接并返回给paypal告诉paypal我们接收到了。后面我在做的过程中发现通知的数据有时候有parent_txn_id有时候又没有,后面去查文档才发现原来paypal第一次通知的时候没有parent_txn_id,后面交易状态改变后就会有这个parent_txn_id。其实这个parent_txn_id就是原始的交易号,第二次通知回调的时候parent_txn_id是原来的交易号,然后同时又会生成一个新的txn_id推送过来。
所以我这边做的逻辑判断是:如果parent_txn_id为空时则表示是paypal的第一次通知,这个时候我就去检查这个交易号之前实时支付成功的时候有没有存到,如果有则不管,没有则存起来。
parent_txn_id不为空的时候表示这是我们第二次或者多次接受到paypal通知了,这个时候我就去更新支付状态,订单状态。
/** * 获取paypal的通知消息 * @param request * @param response * @throws Exception */ @ApiIgnore @RequestMapping(method = RequestMethod.POST, value = "/receivePaypalStatus") public void receivePaypalStatus(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("receivePaypalStatus >>>>>>>>>> 进入paypal后台支付通知"); PrintWriter out = response.getWriter(); try { /** * 获取paypal请求参数,并拼接验证参数 */ Enumeration<String> en = request.getParameterNames(); String str = "cmd=_notify-validate"; while (en.hasMoreElements()) { String paramName = en.nextElement(); String paramValue = request.getParameter(paramName); //此处的编码一定要和自己的网站编码一致,不然会出现乱码,paypal回复的通知为‘INVALID’ str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "utf-8"); } //建议在此将接受到的信息 str 记录到日志文件中以确认是否收到 IPN 信息 log.info("========================================================================================="); log.info("paypal传递过来的交易信息:"+str); /** * 将信息 POST 回给 PayPal 进行验证 */ //验证地址测试环境和正式环境不一样配置在yml中 URL u = new URL(paypalConfig.getWebscr()); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); uc.setRequestMethod("POST"); uc.setDoOutput(true); uc.setDoInput(true); uc.setUseCaches(false); //设置 HTTP 的头信息 uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); PrintWriter pw = new PrintWriter(uc.getOutputStream()); pw.println(str); pw.close(); /** * 接受 PayPal 对 IPN 回发的回复信息 */ BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream())); String res = in.readLine(); in.close(); /** * 将 POST 信息分配给本地变量,可以根据您的需要添加 */ // 交易状态 Completed 代表交易成功 String paymentStatus = request.getParameter("payment_status"); // 交易时间 String paymentDate = request.getParameter("payment_date"); // 交易id String txnId = request.getParameter("txn_id"); // 父交易id String parentTxnId = request.getParameter("parent_txn_id"); // 收款人email String receiverEmail = request.getParameter("receiver_email"); // 收款人id String receiverId = request.getParameter("receiver_id"); // 付款人email String payerEmail = request.getParameter("payer_email"); // 付款人id String payerId = request.getParameter("payer_id"); // 交易金额 String mcGross = request.getParameter("mc_gross"); // 自定义字段,我们存放的订单ID String custom = request.getParameter("custom"); if (res == null || res == "") { res = "0"; } log.info("res = " + res); log.info("paymentStatus = " + paymentStatus); log.info("txnI = " + txnId); log.info("parentTxnId = " + parentTxnId); log.info("custom = " + custom); /** * 获取 PayPal 对回发信息的回复信息,判断刚才的通知是否为 PayPal 发出的 */ if (VERIFIED.equalsIgnoreCase(res)) { /** * 如果 parentTxnId 不为空我们就认为是通知就不是第一次通知 */ if (StringUtil.isNotEmpty(parentTxnId)) { // 根据父支付交易号查询付款表数据 List<ShopOrderPayment> list = shopOrderPaymentService.getOrderPaymentByParentTxnId(parentTxnId); if (null != list && list.size() > 0) { ShopOrderPayment shopOrderPayment = list.get(0); /** * 更新支付状态 */ ShopOrderPayment updateOrderPayment = new ShopOrderPayment(); updateOrderPayment.setId(shopOrderPayment.getId()); updateOrderPayment.setPaymentStatus(paymentStatus); updateOrderPayment.setTransactionId(txnId); shopOrderPaymentService.updateOrderPaymentById(shopOrderPayment); /** * 保存支付历史记录数据 */ ShopOrderPaymentHistory paymentHistory = new ShopOrderPaymentHistory(); BeanUtils.copyProperties(shopOrderPayment, paymentHistory); paymentHistory.setPaymentStatus(paymentStatus); paymentHistory.setTransactionId(txnId); shopOrderPaymentHistoryService.savePaymentHistory(paymentHistory); /** * 判断状态是complete则更新订单状态为待确认收货 * 如果是refunded则更新订单状态为已完成 */ if (COMPLETED_STATUS.equalsIgnoreCase(paymentStatus)) { paypalService.updateOrderStatus(shopOrderPayment.getOrderId(), com.sunvalley.shop.order.constants.Constants.OrderStatus.STATUS_PRE_REC); } else if (REFUNDED_STATUS.equalsIgnoreCase(paymentStatus)) { paypalService.updateOrderStatus(shopOrderPayment.getOrderId(), com.sunvalley.shop.order.constants.Constants.OrderStatus.STATUS_COMPLETE); } } else { log.error("父支付交易号:" + parentTxnId + " 在支付表中不存在"); log.error("Class: "+this.getClass().getName()+" method: "+ Thread.currentThread().getStackTrace()[1].getMethodName()); } } else { /** * 第一次回调通知,根据 txnId 查询。如果存在则表示支付实时返回结果已经记录了,不存在则表示实时返回结果没有记录到 */ List<ShopOrderPayment> paymentList = shopOrderPaymentService.getOrderPaymentByTxnId(txnId); if (null != paymentList && paymentList.size() > 0) { ShopOrderPayment orderPaymentTmp = paymentList.get(0); if (COMPLETED_STATUS.equalsIgnoreCase(orderPaymentTmp.getPaymentStatus())) { log.info("================ 支付表数据已经是complete了,不需要更新 ================"); } else { // 如果回传的状态不是complete则更新我们的支付数据 /** * 更新支付状态 */ ShopOrderPayment updateOrderPayment = new ShopOrderPayment(); updateOrderPayment.setId(orderPaymentTmp.getId()); updateOrderPayment.setPaymentStatus(paymentStatus); shopOrderPaymentService.updateOrderPaymentById(orderPaymentTmp); /** * 保存支付历史记录数据 */ ShopOrderPaymentHistory paymentHistory = new ShopOrderPaymentHistory(); BeanUtils.copyProperties(orderPaymentTmp, paymentHistory); paymentHistory.setPaymentStatus(paymentStatus); paymentHistory.setTransactionId(txnId); shopOrderPaymentHistoryService.savePaymentHistory(paymentHistory); } } else { /** * 保存支付信息 */ ShopOrderPayment orderPayment = new ShopOrderPayment(); // 从redis中获取订单ID if (StringUtil.isNotEmpty(custom)) { orderPayment.setOrderId(Integer.valueOf(custom)); } else { log.info("***************** paypal回传的订单ID为空 **************"); } // 订单总价 orderPayment.setAmountPaid(new BigDecimal(mcGross)); // 交易号 orderPayment.setTransactionId(txnId); // 支付成功时把交易号同时写进父交易号中 orderPayment.setParentTransationId(txnId); // 收款方ID orderPayment.setReceiveId(receiverId); // 收款方邮箱 orderPayment.setReceiveEmial(receiverEmail); // 收款方状态 orderPayment.setPaymentStatus(paymentStatus.toLowerCase()); // 付款方ID orderPayment.setPayerId(payerId); // 付款方邮箱 orderPayment.setPayerEmail(payerEmail); // 保存支付数据 ShopOrderPayment orderPay = paypalService.savePayment(orderPayment); log.info("================= 支付信息保存成功,订单状态更新成待发货成功 ===================="); } } } else if (INVALID.equalsIgnoreCase(res)) { //非法信息,可以将此记录到您的日志文件中以备调查 log.error("paypal完成支付发送IPN通知返回状态非法,请联系管理员,请求参数:" + str); log.error("Class: "+this.getClass().getName()+" method: "+ Thread.currentThread().getStackTrace()[1].getMethodName()); out.println("confirmError"); } else { //处理其他错误 log.error("paypal完成支付发送IPN通知发生其他异常,请联系管理员,请求参数:" + str); log.error("Class: "+this.getClass().getName()+" method: "+ Thread.currentThread().getStackTrace()[1].getMethodName()); out.println("confirmError"); } } catch (Exception e) { log.error("确认付款信息发生IO异常" + e.getMessage()); log.error("Class: "+this.getClass().getName()+" method: "+ Thread.currentThread().getStackTrace()[1].getMethodName()); out.println("confirmError"); } out.flush(); out.close(); }以上是我个人的代码,可以收到paypal的消息通知,仅供参考,转载需注明,谢谢~