最近公司要集成paypal支付,由于我们有自己的网站,所以选择了最简单的网站标准付款方式(IPN及时付款)。中间遇到了很多大小问题,现在终于解决了,还是靠查阅大家的分享,花点时间写个小结。
首先,paypal官网https://www.paypal.com,首先要注册一个企业账号。
或者去开发者平台注册一个开发者账号 https://developer.paypal.com/developer/applications/
网站标准版支付支付按钮添加详细的介绍,请看网址
https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/buynow_buttons/
并且下面有前端页面的支付代码,由于我是后端开发者,所以我把表单提交复制到html文件中打开即可跳到支付页面。
我们网站需要实现paypal购买虚拟币的功能,所以商品信息比较简单,只需要传入价格,我用到的立即购买表单如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
</head>
<!-- paypal沙盒支付测试地址 -->
<form id="pay_form" name="pay_form" action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">
<!-- 支付金额-->
<input type="hidden" name="amount" id="amount" value="0.5">
<!-- 自己的参数 商品条目-->
<input type="hidden" name="item_number" id="item_number" value="xiu90 coins:50">
<!-- 表示立即支付-->
<input type="hidden" name="cmd" id="cmd" value="_xclick">
<!-- 商品名称-->
<input type="hidden" name="item_name" id="item_name" value="buy xiu90 coins">
<!-- 商户订单唯一id 不可重复 -->
<!-- <input type="hidden" name="invoice" id="invoice" value="201604291655176"> -->
<!--支付成功后台通知地址-->
<input type="hidden" name="notify_url" id="notify_url" value="http://www.igo19.com/accounting/servlet/paypalNotify">
<!--支付成功返回地址-->
<input type="hidden" name="return" id="return" value="http://www.igo19.com/">
<input type="hidden" name="lc" id="lc" value="China">
<!--支付取消返回地址-->
<input type="hidden" name="cancel_return" id="cancel_return" value="http://www.igo19.com/">
<input type="hidden" name="currency_code" id="currency_code" value="USD">
<!--商户邮件-->
<input type="hidden" name="business" id="business" value="[email protected]">
</form>
<script language="javascript">document.pay_form.submit();</script>
</html>
上面表单中提到的商户测试账号,在注册企业帐号的时候,就会默认生成一个测试商户号和一个测试购买账号,如我注册的是[email protected],在测试平台下,sandbox下面的account中可以看到一下两个账号信息:
一个是商户账号,一个是个人账户。当然也可以自己添加账号如:
其实我添加的两个是不规范的,会在支付的时候有问题,可以支付成功,但是在后台通知里面payer_status=Pending而导致验证不通过。可添加类似原有形式的,如[email protected]、[email protected].如果仍有问题,建议用系统给的两个。
打开上面的支付页面后,输入paypal支付sandbox的账号,支付,即支付成功,可以查看paypal账户,也可以返回原有商户网站。支付成功页面:
查看账户的页面,查看详情:
支付成功后,paypal会异步通知到提交请求时设置的notify_url,后台收到通知后,需要将收到信息用&拼接符加上字符串cmd=_notify-validate发送到paypal验证消息的可靠性,即IPN验证,若返回VERIFIED表明验证成功,再判断支付状态,Complete为支付成功。后台通知代码如下
/** * Copyright (c) 2014-2015 BrdInfo Technology Company LTD. * All rights reserved. * * Created on 2016年4月11日 * Id: PaypalNotify.java,v 1.0 2016年4月11日 上午10:04:10 Administrator */
package com.brdinfo.account.paypal.utils;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.brdinfo.framework.client.HTTPWeb;
/** * @ClassName PaypalNotify * @author Administrator * @date 2016年4月11日 上午10:04:10 * @Description TODO(这里用一句话描述这个类的作用) */
@WebServlet("/servlet/paypalNotify")
public class PaypalNotify extends HttpServlet{
private static final long serialVersionUID = 1L;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
service(request, response);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
service(request, response);
}
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
logger.info("PaypalNotify>>>>>>>>>进入paypal后台支付通知");
PrintWriter out = response.getWriter();
try {
// 从 PayPal 出读取 POST 信息同时添加变量„cmd‟
Enumeration en = request.getParameterNames();
String str = "cmd=_notify-validate";
while(en.hasMoreElements()){
String paramName = (String) en.nextElement();
String paramValue = request.getParameter(paramName);
str = str + "&" +paramName + "=" + URLEncoder.encode(paramValue,PayConfig.charset);
}
logger.info("paypal支付请求验证参数,验证是否来自paypal消息:" + str);
// 将信息 POST 回给 PayPal 进行验证 测试环境先省略这一步
//HTTPWEB是我自己的类 网上有很多HTTP请求的方法
String result = HTTPWeb.postLocalSSL(PayConfig.requestUrl, str);
logger.info("paypal支付确认结果result="+result);
// 将 POST 信息分配给本地变量,可以根据您的需要添加
// 该付款明细所有变量可参考:
// https://www.paypal.com/IntegrationCenter/ic_ipn-pdt-variable-reference.html
String paymentStatus = request.getParameter("payment_status");
//付款金额
String paymentAmount = request.getParameter("mc_gross");
//订单id
String orderId = request.getParameter("invoice");
if ("VERIFIED".equals(result)) {
if("Completed".equals(paymentStatus)){
//根据项目实际需要处理其业务逻辑
}
}else if ("INVALID".equals(result)) {
logger.info("paypal完成支付发送IPN通知返回状态非法,请联系管理员,请求参数:" + str);
out.println("confirmError");
} else {
logger.info("paypal完成支付发送IPN通知发生其他异常,请联系管理员,请求参数:" + str);
out.println("confirmError");
}
} catch (IOException e){
logger.info("确认付款信息发生IO异常" + e.getMessage());
out.println("confirmError");
}
out.flush();
out.close();
}
}
在提交的表单中设置return_url,生产平台可以在商户信息中设置,商户返回地址设置:
登录商户账户->用户信息->销售工具->网站习惯设定->更新->开启并设定url
解决方案;
sandbox环境下提交支付请求时是否配置了notify_url,这个地址必须是外网地址,外网可以访问的到。
生产环境可以在提交表单中配置inotify_url,或者在商户工具里面配置。配置方法;
登录商户账户->用户信息->销售工具->及时付款通知->更新->开启及时通知并设置url
配置的地址可以在IPN Simulator 模拟测试。
测试地址https://developer.paypal.com/developer/ipnSimulator/
该模拟器发送IPN消息到后台,后台日志打印出来可以查看收到的消息
我在做的过程中,遇到设置了一个外网地址,IPN测试可以收到,到支付成功就是收不到,最终联系客服,paypal客服说他们的服务有点问题,可能没及时发消息。真是晕啊!!!!大家遇到了也别着急,等等说不定服务就好了。
paypal测试环境下支付,先登录sandbox账号,再发起支付。
paypal支付成功,返回结果里面payer_status=Pending,因而验证失败。
解决方案一:
先检查一下自己设置的商户邮箱和用户支付邮箱,是否正确,最好是sandbox自带的两个测试账号。自己添加的测试账号有时会出问题。
解决方案二:
检查sandbox下account设置
1)Go to PayPal Developer Website
2)Log in to your developer account
3)Click Applications
4)Click Sandbox accounts
5)Click on to the email address that you would like to turn off the Payment Review option and click Profile after it expand
6)Click Settings
7)And select Off for the Payment review.
8)Click Close
后台服务编码需要与paypal保持一直才能验证用过,生产平台上面设置如下;
登录商户账户->用户信息->销售工具->更多销售工具->paypal按钮语言编码->与网站统一设为UTF-8
支付成功后收到paypal后台通知,加上cmd=_notify-validate通过HTTP发送到paypal时报错,错误信息如下:
Paypal Sandbox API: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1959)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1077)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1300)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
at com.paypal.core.HttpConnection.execute(HttpConnection.java:93)
at com.paypal.core.rest.PayPalResource.execute(PayPalResource.java:367)
... 36 more
这个错误的原因是paypal不再支持TLS1,看下面的说应该更准确:
Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and refuse to talk to TLS 1.1 or TLS 1.2 clients. For interoperability, SunJSSE does not enable TLS 1.1 or TLS 1.2 by default for client connections.
To enable TLS 1.2 you can use the following setting of the VM: -Dhttps.protocols=TLSv1.1,TLSv1.2 Then it will work.
If you want to see more details about the handshake you can use this VM option: -Djavax.net.debug=all
Then you may confirm that TLS 1.2 is disabled if you see something like this:
*** ClientHello, TLSv1
After the server response is read the exception will be thrown. Java 8 has no such problem because TLS 1.2 is enabled by default.
Currently only the sandbox is affected. You can read on PayPal's site:
PayPal is updating its services to require TLS v1.2 for all HTTPS connections on June 17, 2016. After that date, all TLS v1.0 and TLS v1.1 API connections will be refused.
解决方案:
1、换成jdk1.8,默认支持TLS1.2,我试了下,换成jdk1.8就能验证成功
2、修改运行环境的配置
Eclipse的VM环境参数设置加上-Dhttps.protocols=TLSv1.1,TLSv1.2
-Xms256M -Xmx512M -XX:PermSize=256M -XX:MaxPermSize=256M -Dhttps.protocols=TLSv1.1,TLSv1.2
Tomcat运行环境中修改,修改tomcat的catalina.sh,在JAVA_OPTS后面加上-Dhttps.protocols=TLSv1.1,TLSv1.2
参考:http://stackoverflow.com/questions/34963083/paypal-sandbox-api-javax-net-ssl-sslhandshakeexception-received-fatal-alert-h/34964207