paypal支付标准按钮(from表单)集成
1.简介
PayPal是倍受全球亿万用户追捧的国际贸易支付工具,即时支付,即时到账,全中文操作界面,能通过中国的本地银行轻松提现,解决外贸收款难题,助您成功开展海外业务,决胜全球。注册PayPal后就可立即开始接受信用卡付款。作为在线付款服务商,PayPal是您向全世界近2.54亿的用户敞开大门的最快捷的方式。最大的好处是,注册完全免费!集国际流行的信用卡,借记卡,电子支票等支付方式于一身。帮助买卖双方解决各种交易过程中的支付难题。PayPal是名副其实的全球化支付平台, 服务范围超过200个市场, 支持的币种超过100个。 在跨国交易中, 将近70%的在线跨境买家更喜欢用PayPal支付海外购物款项。
PayPal提供了多种支付方式:
- 标准支付
- 快速支付
- 其中标准支付誉为最佳实践。
注意:paypal支付国内账号不能付款给国内账号
2.PayPal的相关URL
沙箱(开发者)网址:https://developer.paypal.com/
沙箱(测试用户)登录地址:https://www.sandbox.paypal.com/
技术支持地址(paypal开发人员):https://www.paypal-support.com/s/account-overview
官方文档:
- 付款按钮资料:https://developer.paypal.com/docs/integration/web/
- IPN(支付回调)资料:https://developer.paypal.com/docs/api-basics/notifications/ipn/ht-ipn/
- PDT(数据传输)资料:https://developer.paypal.com/docs/api-basics/notifications/payment-data-transfer/#get-started
- PDT示例代码:https://github.com/paypal/pdt-code-samples
3.集成步骤
1、在paypal页面创建Buttons(PayPal付款按钮)并复制代码到项目
2、用户点击支付按钮
3、进入PayPal支付页面
4、用户登录后确认支付
5、显示支付成功信息
4.集成步骤实现
(1)注册账号
在PayPal正式网站https://www.paypal.com中注册一个账号,如果公司没有给你相关信息的话,先注册一个个人账号也是一样的。
(2)进入开发者界面创建相关信息
1、在开发者平台https://developer.paypal.com/,登录刚创建的账号
2、登录成功后,选择:SANBOX下的Accounts标签
3、创建个人账号和商家账号用于测试沙箱环境
(3)创建按钮
1、在PayPal沙箱测试账号网站https://www.sandbox.paypal.com/登录刚刚创建的沙箱商家账号
2、创建支付按钮
(4)PDT令牌获取
(5)IPN回调乱码处理
(6)示例代码
控制器代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@Controller @RequestMapping("/paypalC") public class PaypalC { @Autowired private PaypalS paypalS; //TODO 标准付款按钮集成 /**交易取消 */ @RequestMapping(method = RequestMethod.GET, value = "cancel") public String cancel(){ return "b2/paypal/cancel"; } /**获取标准付款页面 */ @RequestMapping(method = RequestMethod.GET, value = "standardPayment", produces="text/html;charset=UTF-8") public String standardPayment(){ return "b2/paypal/standardPayment"; } /**PDT立即回调(由用户在paypal点击返回商家网站时调用,不点击则不会调用,含跳转路径) */ @RequestMapping(value = "pdtWebhooks", produces="text/html;charset=UTF-8") public String pdtWebhooks(HttpServletRequest req, HttpServletResponse resp){ return paypalS.pdtWebhooks(req, resp); } /**IPN回调(由paypal支付成功后调用,最多重复调用15次) */ @ResponseBody @RequestMapping(method = RequestMethod.POST, value = "ipnWebhooks", produces="text/html;charset=UTF-8") public String ipnWebhooks(HttpServletRequest req, HttpServletResponse resp) { return paypalS.ipnWebhooks(req, resp); } }
业务层代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.springframework.stereotype.Service; import com.grt.b2.method.PaypalUtils; import com.grt.v3.method.OUtils; /**Paypal支付service类 */ @Service public class PaypalS { //TODO 标准付款按钮集成 /**PDT立即回调(由用户在paypal点击返回商家网站时调用,不点击则不会调用,含跳转路径) */ public String pdtWebhooks(HttpServletRequest req, HttpServletResponse resp) { String tx = req.getParameter("txn_id");//订单id String stringRes = PaypalUtils.getPDTWebHooks(tx); if(!OUtils.isEmpty(stringRes)){ String[] res = stringRes.split("\\r?\\n"); if("SUCCESS".equals(res[0])){ System.out.println("验证PDT数据成功,进行订单入库"); Map<String, String> data = new HashMap<String, String>(); for (String str : res) { if(str.indexOf("=") > 0){ String[] s = str.split("="); data.put(s[0], s.length > 1 ? s[1] : ""); } } return "b2/paypal/success"; } } return "b2/paypal/failure"; } /**IPN回调(由paypal支付成功后调用,最多重复调用15次) */ @SuppressWarnings("unchecked") public String ipnWebhooks(HttpServletRequest req, HttpServletResponse resp) { //1.获取ipn传递的数据 Map<String, String> data = new HashMap<String, String>(); List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("cmd", "_notify-validate")); Enumeration<String> e = req.getParameterNames(); while(e.hasMoreElements()){ String name = e.nextElement(); String value = req.getParameter(name); params.add(new BasicNameValuePair(name, value)); data.put(name, value); } //2.把ipn传递数据发送给paypal并获取返回数据进行验证 String res = PaypalUtils.getIPNWebHooks(params); System.out.println("IPN数据\n"+res); if ("VERIFIED".equals(res)) {//验证IPN回调数据成功 System.out.println("验证IPN回调数据成功,进行订单入库"); }else if("INVALID".equals(res)) {//验证IPN回调数据失败 System.out.println("验证IPN回调数据失败,进行日志记录"); }else{//验证IPN回调数据异常 System.out.println("验证IPN回调数据异常,进行日志记录"); } return ""; } }
PayPal工具类如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.http.Consts; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import com.grt.michael.method.tigo.HttpsUtils; import com.grt.v3.method.OUtils; /**Paypal支付工具类 */ public class PaypalUtils { //TODO 标准付款按钮集成 private static final String SANDBOX_URL = "https://www.sandbox.paypal.com/cgi-bin/webscr"; private static final String LIVE_URL = "https://www.paypal.com/cgi-bin/webscr"; private static final String pdt_key = "";//PDT密钥,需要自己去paypal拿 /**获取PDT验证数据 */ public static String getPDTWebHooks(String tx){ Map<String, String> headers = new HashMap<String, String>(); List<NameValuePair> params = new ArrayList<NameValuePair>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); params.add(new BasicNameValuePair("cmd", "_notify-synch")); params.add(new BasicNameValuePair("tx", tx)); params.add(new BasicNameValuePair("at", pdt_key)); String result = sendPost(SANDBOX_URL, headers, params); if(!OUtils.isEmpty(result)) return URLDecoderString(result); return result; } /**获取IPN验证数据 */ public static String getIPNWebHooks(List<NameValuePair> params){ Map<String, String> headers = new HashMap<String, String>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); return sendPost(SANDBOX_URL, headers, params); } /**URL编码解码 */ public static String URLDecoderString(String str) { String result = ""; try { result = java.net.URLDecoder.decode(str, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return result; } /**post请求方法 */ private static String sendPost(String url, Map<String, String> headers, List<NameValuePair> params) { String resultData = null; try{ CloseableHttpClient http = HttpsUtils.getHttpClient(); HttpPost httpost = new HttpPost(url); //设置header if (headers != null && headers.size() > 0) { for (Map.Entry<String, String> entry : headers.entrySet()) { httpost.setHeader(entry.getKey(), entry.getValue()); } } //组织请求参数 if (params != null && params.size() > 0) { httpost.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8)); } // 执行结果 for (int i = 0; i < 3; i++) { try { HttpResponse res = http.execute(httpost); if (res.getStatusLine().getStatusCode() != 200) continue; resultData = EntityUtils.toString(res.getEntity()); return resultData; } catch (IOException e) { e.printStackTrace(); continue; } finally { httpost.releaseConnection(); } } }catch (Exception e) { e.printStackTrace(); } return resultData; } }
HttpUtils工具类(httpclient-4.5.jar、httpcore-4.4.1.jar)如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import org.apache.commons.collections.MapUtils; import org.apache.http.*; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; public class HttpsUtils { private static final String HTTP = "http"; private static final String HTTPS = "https"; private static SSLConnectionSocketFactory sslsf = null; private static PoolingHttpClientConnectionManager cm = null; private static SSLContextBuilder builder = null; static { try { builder = new SSLContextBuilder(); // 全部信任 不做身份鉴定 builder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { return true; } }); sslsf = new SSLConnectionSocketFactory(builder.build(), new String[] { "SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE); Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register(HTTP, new PlainConnectionSocketFactory()).register(HTTPS, sslsf).build(); cm = new PoolingHttpClientConnectionManager(registry); cm.setMaxTotal(200);// max connection } catch (Exception e) { e.printStackTrace(); } } /** * httpClient post请求 * * @param url * 请求url * @param header * 头部信息 * @param param * 请求参数 form提交适用 * @param entity * 请求实体 json/xml提交适用 * @return 可能为空 需要处理 * @throws Exception * */ public static String doGet(String url) throws Exception { String result = ""; CloseableHttpClient httpClient = null; try { httpClient = getHttpClient(); HttpGet httpGet = new HttpGet(url); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();// 设置请求和传输超时时间 httpGet.setConfig(requestConfig); HttpResponse httpResponse = httpClient.execute(httpGet); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity resEntity = httpResponse.getEntity(); result = EntityUtils.toString(resEntity); } else { readHttpResponse(httpResponse); } } catch (Exception e) { throw e; } finally { if (httpClient != null) { httpClient.close(); } } return result; } /** * httpClient post请求 * * @param url * 请求url * @param header * 头部信息 * @param param * 请求参数 form提交适用 * @param entity * 请求实体 json/xml提交适用 * @return 可能为空 需要处理 * @throws Exception * */ public static String doPost(String url, Map<String, String> header, Map<String, String> param, HttpEntity entity) throws Exception { String result = ""; CloseableHttpClient httpClient = null; try { httpClient = getHttpClient(); HttpPost httpPost = new HttpPost(url); // 设置头信息 if (MapUtils.isNotEmpty(header)) { for (Map.Entry<String, String> entry : header.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } } // 设置请求参数 if (MapUtils.isNotEmpty(param)) { List<NameValuePair> formparams = new ArrayList<NameValuePair>(); for (Map.Entry<String, String> entry : param.entrySet()) { // 给参数赋值 formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams, Consts.UTF_8); httpPost.setEntity(urlEncodedFormEntity); } // 设置实体 优先级高 if (entity != null) { httpPost.setEntity(entity); } HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse = httpClient.execute(httpGet); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity resEntity = httpResponse.getEntity(); result = EntityUtils.toString(resEntity); } else { readHttpResponse(httpResponse); } } catch (Exception e) { throw e; } finally { if (httpClient != null) { httpClient.close(); } } return result; } public static CloseableHttpClient getHttpClient() throws Exception { CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(cm).setConnectionManagerShared(true).build(); return httpClient; } public static String readHttpResponse(HttpResponse httpResponse) throws ParseException, IOException { StringBuilder builder = new StringBuilder(); // 获取响应消息实体 HttpEntity entity = httpResponse.getEntity(); // 响应状态 builder.append("status:" + httpResponse.getStatusLine()); builder.append("headers:"); HeaderIterator iterator = httpResponse.headerIterator(); while (iterator.hasNext()) { builder.append("\t" + iterator.next()); } // 判断响应实体是否为空 if (entity != null) { String responseString = EntityUtils.toString(entity); builder.append("response length:" + responseString.length()); builder.append("response content:" + responseString.replace("\r\n", "")); } return builder.toString(); } }
paypal支付按钮(standardPayment.html)代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" target="_top"> <input type="hidden" name="cmd" value="_xclick"><%--支付类型--%> <input type="hidden" name="business" value="sb-ux7pa3602231@business.example.com"><%--商家账号--%> <input type="hidden" name="hosted_button_id" value="CWA2QSSG7CDBA"><%--按钮id,由paypal生成--%> <input TYPE="hidden" name="charset" value="utf-8"><%--编码--%> <input type="hidden" name="invoice" value="ABC123"><%--自定义订单编号--%> <input type="hidden" name="item_name" value="Server服务开通"><%--商品名称--%> <input type="hidden" name="item_number" value="1"><%--商品数量--%> <input type="hidden" name="amount" value="5.95"><%--商品价格--%> <input type="hidden" name="currency_code" value="USD"><%--货币--%> <input type="hidden" name="return" value=""><%--支付成功(PDT地址)--%> <input type="hidden" name="cancel_return" value=""><%--支付失败页面--%> <%-- <input type="hidden" name="notify_url" value="">回调路径(IPN地址) --%> <input type='hidden' name="no_shipping" value="1"><%--不要求客户提供收货地址--%> <input type="hidden" name="rm" value="2"><%--return有地址的话,必须指定这个参数--%> <input type="hidden" name="no_note" value="老子不想付款啊。。。。"><%--付款说明--%> <input type="hidden" name="image_url" value="https://www.paypal.com/zh_XC/i/btn/btn_buynow_SM.gif"><%--图片--%> <input type="hidden" name="custom" value="admin"><%--自定义变量传递服务器需要信息--%> <input type="image" src="https://www.sandbox.paypal.com/zh_XC/i/btn/btn_buynow_SM.gif" border="0" name="submit" alt="PayPal——最安全便捷的在线支付方式!"> <img alt="" border="0" src="https://www.sandbox.paypal.com/zh_XC/i/scr/pixel.gif" width="1" height="1"> </form> </body> </html>
cancel.html代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <h1>用户取消支付...</h1> </body> </html>
success.html代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <h1>用户支付成功</h1> </body> </html>
failure.html代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <h1>用户支付失败</h1> </body> </html>