非SDK支付方式,因为paypal 支付的SDK 说是要大客户申请权限了 才让使用,简直恶心,之前不知道,测试都弄完了,切换正式环境,提示权限不足,问客服才知道,简直头疼,经测试以及可以正常使用了的 ,使用的方式为:api 签名方式进行,支付采用的是平行支付,支持同时向多人发起付款或者单人付款(应用于平台,商家,客户这种场景,一次付款,多方收账)
支付一共分三部分 第一部分: 获取token,获取前端专递过来的金额 然后拼接对应格式数据,获取到token,第二部:然后重定向到paypal 这里会跳转到一个网页,用户会看到商品信息以及金额 然后 用户授权支付 第三部分:用户授权后 拿到token 进行最后的确认支付操作
第一、第二部分:
package com.chanxa.monitor.web.recharge;
import java.math.BigDecimal;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.chanxa.monitor.pay.service.PaypalInterface;
@Controller
@RequestMapping("/recharge")
public class RechargeController {
@Resource
private PaypalInterface paypalService;
public Map<String, String> getParamsFromRequest(HttpServletRequest request) {
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
params.put(name, valueStr);
}
return params;
}
/**
* 充值接口
*/
@RequestMapping("/RechargePaypal.do")
public String RechargePaypal(HttpServletRequest request){
//使用RSA 加密了数据 网上有生成公钥私钥的方法 自己拿一套来生成对应的公钥私钥使用就好了
String privateKey="";//私钥
//获取对应的参数
String userId=request.getParameter("userId");
String accountId=request.getParameter("accountId");
String money=request.getParameter("money");
//解析出正确的数据
try {
userId=decrypt(userId,privateKey);
accountId=decrypt(accountId,privateKey);
money=decrypt(money,privateKey);
} catch (Exception e) {
e.printStackTrace();
return "";
}
//一系列参数验证 略...
//调用paypal支付
String token=paypalService.getPaypalPage(new BigDecimal(money),"测试充值");
if(token.indexOf("redirect")!=-1){
return token;
}
//增加数据充值记录 略。。。
//重定向到paypal支付页面
String paypalLog="https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=";//测试环境,正式的为去掉sandbox
return "redirect:"+paypalLog+token;
}
/**
* RSA私钥解密
*
* @param str
* 加密字符串
* @param privateKey
* 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public String decrypt(String str, String privateKey) throws Exception{
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte),"UTF-8");
return outStr;
}
}
package com.chanxa.monitor.pay.service;
import java.math.BigDecimal;
public interface PaypalInterface {
/**
*打开支付页面
*/
public String getPaypalPage(BigDecimal money,String name);
}
package com.chanxa.monitor.pay.service.impl;
import java.math.BigDecimal;
import java.net.URLDecoder;
import javax.annotation.Resource;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.chanxa.monitor.pay.paypal.config.PaypalConfig;
import com.chanxa.monitor.pay.service.PaypalInterface;
import com.chanxa.monitor.service.AccountInterface;
import com.chanxa.monitor.utils.Configuration;
@Service
public class PaypalService implements PaypalInterface{
Logger logger = LoggerFactory.getLogger(PaypalService.class);
@Resource
private AccountInterface accountService;
@Override
public String getPaypalPage( BigDecimal money, String name) {
try {
PostMethod postMethod = null;
//组装数据
postMethod= this.getPostMethod(money, name,"78","SetExpressCheckout");
//模拟post请求
org.apache.commons.httpclient.HttpClient httpClient = new org.apache.commons.httpclient.HttpClient();
int response = httpClient.executeMethod(postMethod); // 执行POST方法
String result = postMethod.getResponseBodyAsString() ;
//解析返回值
result=URLDecoder.decode(result,"UTF-8");
String [] results=result.split("&");
String token=results[0];
if(token.indexOf("TOKEN")==-1){
for(int i=0;i<results.length;i++){
String str=results[i];
String [] strs=str.split("=");
if(strs[0].equals("PAYMENTINFO_0_ERRORCODE")){//余额不足
if(strs[0].equals("10009")){
//这里是失败了返回自己自定义页面
return PaypalConfig.payPalSuccess+"?code=3";
}
}
}
}
String [] tokens=token.split("=");
token=tokens[1];
//返回token
return token;
} catch (Exception e) {
logger.info("请求异常"+e.getMessage(),e);
throw new RuntimeException(e.getMessage());
}
}
/**
*
* @param money 金额
* @param name 标题
* @param version 版本
* @param method 固定为SetExpressCheckout
* @return
*/
public PostMethod getPostMethod(BigDecimal money,String name,String version,String method){
try {
PostMethod postMethod = null;
postMethod = new PostMethod("https://api-3t.sandbox.paypal.com/nvp") ;//paypal 支付请求网关 --sandbox 为测试地址
postMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") ;
//判断 是否为禁用分账 1:是 2:否
String isFenzhang=Configuration.getConfig().getValue("isFenzhang");
if(isFenzhang.equals("1")){
NameValuePair[] data = {
new NameValuePair("USER",""),// api用户名 官网可以查到该API名
new NameValuePair("PWD",""),// api密码 和API用户名一起的
new NameValuePair("SIGNATURE",""),//签名 paypal 签名 该签名可以在官网找到
new NameValuePair("METHOD",method),//请求接口名
new NameValuePair("VERSION",version),//版本号
new NameValuePair("PAYMENTREQUEST_0_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_0_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_0_CURRENCYCODE","HKD"),//交易币种
new NameValuePair("PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID",PaypalConfig.Receivables),//平台账号
new NameValuePair("PAYMENTREQUEST_0_ITEMAMT",""),//与交易金额相同即可
new NameValuePair("PAYMENTREQUEST_0_PAYMENTREQUESTID","CART26488-PAYMENT0"),//付款请求ID
new NameValuePair("L_PAYMENTREQUEST_0_NAME0",name),//交易名称
new NameValuePair("L_PAYMENTREQUEST_0_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_0_AMT0",""),//与交易金额相同即可
new NameValuePair("PAYMENTREQUEST_1_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_1_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_1_CURRENCYCODE","HKD"),
new NameValuePair("PAYMENTREQUEST_1_SELLERPAYPALACCOUNTID",""),//运营商账号
new NameValuePair("PAYMENTREQUEST_1_ITEMAMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_1_PAYMENTREQUESTID","CART26488-PAYMENT1"),
new NameValuePair("L_PAYMENTREQUEST_1_NAME0",name),
new NameValuePair("L_PAYMENTREQUEST_1_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_1_AMT0",""),//交易金额
//....如果有多方 还能继续添加 最多好像是20个来着还是多少
new NameValuePair("cancelUrl",PaypalConfig.cancelUrl),//支付回跳页面
new NameValuePair("returnUrl",PaypalConfig.returnUrl)//支付回调地址--充值
};
postMethod.setRequestBody(data);
}else{
NameValuePair[] data = {
new NameValuePair("USER",""),//api用户名 官网可以查到该API名
new NameValuePair("PWD",""),//api密码 和API用户名一起的
new NameValuePair("SIGNATURE",""),//签名
new NameValuePair("METHOD",method),//请求接口名
new NameValuePair("VERSION",version),//版本号
new NameValuePair("PAYMENTREQUEST_0_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_0_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_0_CURRENCYCODE","HKD"),//交易币种
new NameValuePair("PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID",""),//平台账号
new NameValuePair("PAYMENTREQUEST_0_ITEMAMT",""),
new NameValuePair("PAYMENTREQUEST_0_PAYMENTREQUESTID","CART26488-PAYMENT0"),//付款请求ID
new NameValuePair("L_PAYMENTREQUEST_0_NAME0",name),//交易名称
new NameValuePair("L_PAYMENTREQUEST_0_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_0_AMT0",""),//金额
new NameValuePair("cancelUrl",PaypalConfig.cancelUrl),
new NameValuePair("returnUrl",PaypalConfig.returnUrl)
};
postMethod.setRequestBody(data);
}
return postMethod;
} catch (Exception e) {
logger.info("请求异常"+e.getMessage(),e);
throw new RuntimeException(e.getMessage());
}
}
}
第三部分:最后在回调里进行确认付款操作,
注意:
因为充值是用的平行支付 会出现两个支付订单明细 所以这里不能已单一的一个返回就来确认是成功 (如果只配置了一个收款方 则不受影响 配置多个收款方时 就得判断每个收款方是否都成功了 )
这里存在一个问题 如果是多个收款方 当其中某一方的收款账号是错误的时候,按照业务逻辑 这时应该要进行退款操作,
比如 我有5个收款方 用户A 进行付款 前面4个都交易成功,第五个因为账号不存在等问题,失败了 ,这时候按逻辑是要吧前面4个人的钱都退出来,
但是我和官方人员沟通,他们告诉我 是不能进行退款的 因为paypal退款是需要收款方授权,也就是说 这个时候并不能自主退款,得对接另一套协议才行
package com.chanxa.monitor.web.paypal;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.chanxa.monitor.pay.paypal.config.PaypalConfig;
import com.chanxa.monitor.utils.Configuration;
import com.chanxa.monitor.web.recharge.RechargeController;
@Controller
@RequestMapping("/paypalhttp/")
public class PaypalHttp {
Logger logger = LoggerFactory.getLogger(PaypalHttp.class);
@Resource
private RechargeController rechargeController;
/**
* 买家授权后的回调--充值
*/
@RequestMapping(value = "/notify.do")
public String notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
PrintWriter out = response.getWriter();
// 获取POST过来反馈信息
Map<String, String> params=rechargeController.getParamsFromRequest(request);
//根据 {PayerID=WTTKS42LBXASQ, token=EC-7CF05834S75613304} 这两个值 再去发起付款
String PayerID=params.get("PayerID");
String token=params.get("token");
String name="测试充值";
try {
PostMethod postMethod = null;
postMethod = new PostMethod(PaypalConfig.pathUrl) ;
postMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") ;
//判断 是否为禁用分账 1:是 2:否,
//注意: 这里可以使用前面的组装数据的公共接口 只是需要加个参数 第一个是请求接口变动了(DoExpressCheckoutPayment) 然后是版本(204),还有就是返回过来的token=TOKEN 和交易ID=PAYERID 都要放进去 其余的没什么变化
String isFenzhang=Configuration.getConfig().getValue("isFenzhang");
if(isFenzhang.equals("1")){
NameValuePair[] data = {
new NameValuePair("USER",""),// api用户名 官网可以查到该API名
new NameValuePair("PWD",""),// api密码 和API用户名一起的
new NameValuePair("SIGNATURE",""),//签名 paypal 签名 该签名可以在官网找到
new NameValuePair("METHOD","DoExpressCheckoutPayment"),//请求接口名
new NameValuePair("VERSION","204"),//版本号
new NameValuePair("PAYMENTREQUEST_0_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_0_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_0_CURRENCYCODE","HKD"),//交易币种
new NameValuePair("PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID",PaypalConfig.Receivables),//平台账号
new NameValuePair("PAYMENTREQUEST_0_ITEMAMT",""),//与交易金额相同即可
new NameValuePair("PAYMENTREQUEST_0_PAYMENTREQUESTID","CART26488-PAYMENT0"),//付款请求ID
new NameValuePair("L_PAYMENTREQUEST_0_NAME0",name),//交易名称
new NameValuePair("L_PAYMENTREQUEST_0_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_0_AMT0",""),//与交易金额相同即可
new NameValuePair("PAYMENTREQUEST_1_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_1_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_1_CURRENCYCODE","HKD"),
new NameValuePair("PAYMENTREQUEST_1_SELLERPAYPALACCOUNTID",""),//运营商账号
new NameValuePair("PAYMENTREQUEST_1_ITEMAMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_1_PAYMENTREQUESTID","CART26488-PAYMENT1"),
new NameValuePair("L_PAYMENTREQUEST_1_NAME0",name),
new NameValuePair("L_PAYMENTREQUEST_1_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_1_AMT0",""),//交易金额
new NameValuePair("TOKEN",token),
new NameValuePair("PAYERID",PayerID),
//....如果有多方 还能继续添加 最多好像是20个来着还是多少
new NameValuePair("cancelUrl",PaypalConfig.cancelUrl),//支付回跳页面
new NameValuePair("returnUrl",PaypalConfig.returnUrl)//支付回调地址--充值
};
postMethod.setRequestBody(data);
}else{
NameValuePair[] data = {
new NameValuePair("USER",""),//api用户名 官网可以查到该API名
new NameValuePair("PWD",""),//api密码 和API用户名一起的
new NameValuePair("SIGNATURE",""),//签名
new NameValuePair("METHOD","DoExpressCheckoutPayment"),//请求接口名
new NameValuePair("VERSION","204"),//版本号
new NameValuePair("PAYMENTREQUEST_0_PAYMENTACTION","SALE"),
new NameValuePair("PAYMENTREQUEST_0_AMT",""),//交易金额
new NameValuePair("PAYMENTREQUEST_0_CURRENCYCODE","HKD"),//交易币种
new NameValuePair("PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID",""),//平台账号
new NameValuePair("PAYMENTREQUEST_0_ITEMAMT",""),
new NameValuePair("PAYMENTREQUEST_0_PAYMENTREQUESTID","CART26488-PAYMENT0"),//付款请求ID
new NameValuePair("L_PAYMENTREQUEST_0_NAME0",name),//交易名称
new NameValuePair("L_PAYMENTREQUEST_0_QTY0","1"),
new NameValuePair("L_PAYMENTREQUEST_0_AMT0",""),//金额
new NameValuePair("TOKEN",token),
new NameValuePair("PAYERID",PayerID),
new NameValuePair("cancelUrl",PaypalConfig.cancelUrl),
new NameValuePair("returnUrl",PaypalConfig.returnUrl)
};
postMethod.setRequestBody(data);
}
org.apache.commons.httpclient.HttpClient httpClient = new org.apache.commons.httpclient.HttpClient();
int status = httpClient.executeMethod(postMethod); // 执行POST方法
String result = postMethod.getResponseBodyAsString() ;
result=URLDecoder.decode(result,"UTF-8");
//数据解析
this.setPaypal(result, 2, token);
return PaypalConfig.payPalSuccess+"?code=1";
} catch (Exception e) {
logger.info("请求异常"+e.getMessage(),e);
// throw new RuntimeException(e.getMessage());
return PaypalConfig.payPalSuccess+"?code=4";
}
// out.flush();
// out.close();
}
/**
* /数据解析 公共方法
* @param result 返回字符串
* @param type 类型区分 2:充值 1:缴纳管理费
*/
public void setPaypal(String result,Integer type,String token){
String [] results=result.split("&");
//固定模式 充值时 会返回1+n组数据 这个N是指前面配置了多少个交易对象 这里 用循环来处理
for(int k=0;k<type;k++){
for(int i=0;i<results.length;i++){
String str=results[i];
String [] strs=str.split("=");
if(strs[0].equals("ACK")){
if(strs[1].equals("Success")){//交易成功
}
}
if(strs[0].equals("PAYMENTINFO_"+k+"_FEEAMT")){//交易手续费
}
if(strs[0].equals("PAYMENTINFO_"+k+"_AMT")){//设置金额
}
if(strs[0].equals("PAYMENTINFO_"+k+"_SELLERPAYPALACCOUNTID")){//收款方账号
}
if(strs[0].equals("PAYMENTINFO_"+k+"_CURRENCYCODE")){//币种
}
if(strs[0].equals("PAYMENTINFO_"+k+"_ORDERTIME")){//支付时间
}
if(strs[0].equals("PAYMENTINFO_"+k+"_TRANSACTIONID")){//交易流水号
}
if(strs[0].equals("TOKEN")){//订单ID
}
if(strs[0].equals("PAYMENTINFO_"+k+"_TRANSACTIONTYPE")){//交易类型
}
if(strs[0].equals("PAYMENTINFO_"+k+"_ACK")){//交易状态
if(strs[1].equals("Success")){//交易状态为成功 则进行对应的操作
/**
*因为充值是用的平行支付 会出现两个支付订单明细 所以这里不能已单一的一个返回就来确认是成功 (如果只配置了一个收款方 则不受影响 配置多个收款方时 就得判断每个收款方是否都成功了 )
* 注意 这里存在一个问题 如果是多个收款方 当其中某一方的收款账号是错误的时候,按照业务逻辑 这时应该要进行退款操作,
* 比如 我有5个收款方 用户A 进行付款 前面4个都交易成功,第五个因为账号不存在等问题,失败了 ,这时候按逻辑是要吧前面4个人的钱都退出来,
* 但是我和官方人员沟通,他们告诉我 是不能进行退款的 因为paypal退款是需要收款方授权,也就是说 这个时候并不能自主退款,得对接另一套协议才行
*/
}
}
}
//进行日志记录
}
//执行对应的充值成功逻辑
}
}
好了 收工