一點一滴開始搭建自己的專案框架之支付寶篇 支付寶APP支付
專案框架: 專案框架
最近在封裝傳值方式 準備把json、fromData等傳值方式封裝成一個體系 可惜封裝了半天還是接不到 可能是對HTTP的協議瞭解的好不夠清楚 今天就先把APP支付寶支付的原始碼放出來吧
好的首先開始支付寶的文件閱讀
https://docs.open.alipay.com/58/103584/
之後就是支付寶的工具包準備了
這個是我的整體專案架構 支付寶工具被我放到了一個包裡面 介面和實現類 被我封裝到了三層中 接下里上原始碼
com.bing.aliPay.pojo.Product.java(這個是支付VO類)
package com.bing.aliPay.pojo; import java.io.Serializable; /** * @ClassName: Product * @Description: TODO(支付寶支付 pojo) * @date 2018-11-6 */ public class Product implements Serializable { private static final long serialVersionUID = 1L; private String productId;// 商品ID private String subject;// 訂單名稱 private String body;// 商品描述 private String totalAmount;// 總金額(單位是元) private String outTradeNo;// 訂單號(唯一) private String spbillCreateIp;// 發起人IP地址 private String attach;// 附件資料主要用於商戶攜帶訂單的自定義資料 private String notifyUrl;// 支付寶回撥地址 private String return_url;// 頁面同步通知地址 public Product() { super(); } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getOutTradeNo() { return outTradeNo; } public void setOutTradeNo(String outTradeNo) { this.outTradeNo = outTradeNo; } public String getSpbillCreateIp() { return spbillCreateIp; } public void setSpbillCreateIp(String spbillCreateIp) { this.spbillCreateIp = spbillCreateIp; } public String getAttach() { return attach; } public void setAttach(String attach) { this.attach = attach; } public String getNotifyUrl() { return notifyUrl; } public void setNotifyUrl(String notifyUrl) { this.notifyUrl = notifyUrl; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getTotalAmount() { return totalAmount; } public void setTotalAmount(String totalAmount) { this.totalAmount = totalAmount; } public String getReturn_url() { return return_url; } public void setReturn_url(String return_url) { this.return_url = return_url; } }
com.bing.aliPay.util.Constants.java(這個是支付寶常量類)
package com.bing.aliPay.util; import org.springframework.util.ClassUtils; /** * @ClassName: Constants * @Description: TODO(常量) * @date 2018-11-6 */ public class Constants { public static final String SF_FILE_SEPARATOR = System.getProperty("file.separator");//檔案分隔符 public static final String SF_LINE_SEPARATOR = System.getProperty("line.separator");//行分隔符 public static final String SF_PATH_SEPARATOR = System.getProperty("path.separator");//路徑分隔符 public static final String QRCODE_PATH = ClassUtils.getDefaultClassLoader().getResource("static").getPath()+SF_FILE_SEPARATOR+"qrcode"; //微信賬單 相關欄位 用於load文字到資料庫 public static final String WEIXIN_BILL = "tradetime, ghid, mchid, submch, deviceid, wxorder, bzorder, openid, tradetype, tradestatus, bank, currency, totalmoney, redpacketmoney, wxrefund, bzrefund, refundmoney, redpacketrefund, refundtype, refundstatus, productname, bzdatapacket, fee, rate"; public static final String PATH_BASE_INFO_XML = SF_FILE_SEPARATOR+"WEB-INF"+SF_FILE_SEPARATOR+"xmlConfig"+SF_FILE_SEPARATOR; public static final String CURRENT_USER = "UserInfo"; public static final String SUCCESS = "success"; public static final String FAIL = "fail"; }
com.bing.aliPay.util.AliPayConfig.java(支付寶公共變數)
package com.bing.aliPay.util;
import com.bing.model.PayModelVo;
import com.bing.util.PropertiesUtil;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
/**
* @ClassName: AliPayConfig
* @Description: TODO(配置公共引數)
* @date 2017-11-6
*/
public final class AliPayConfig {
/**
* 私有的預設構造子,保證外界無法直接例項化
*/
private AliPayConfig(){};
// 應用ID,您的APPID,收款賬號既是您的APPID對應支付寶賬號
public static String app_id = "";
// 商戶私鑰,您的PKCS8格式RSA2私鑰
public static String merchant_private_key = "";
// 支付寶公鑰,檢視地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。
public static String alipay_public_key = "";
//非同步回撥地址
public static final String NOTIFY_URL = PropertiesUtil.getPropertiesValue("sys","envPrefix")+"";//測試
//PCreturn地址
public static final String RETURN_URL ="www.baidu.com";//測試
public static final String PRODUCT_CODE = "QUICK_MSECURITY_PAY";
// 簽名方式
public static String sign_type = "RSA2";
// 字元編碼格式
public static String charset = "UTF-8";
// 字元編碼格式
public static String time_out = "30m";
private static String method = "alipay.trade.refund";
// 支付寶閘道器
public static String gatewayUrl = "https://openapi.alipay.com/gateway.do";
/**
* 引數型別
*/
public static String PARAM_TYPE = "json";
/**
* 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項
* 沒有繫結關係,而且只有被呼叫到才會裝載,從而實現了延遲載入
*/
private static class SingletonHolder{
/**
* 靜態初始化器,由JVM來保證執行緒安全
*/
private static AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, app_id,
merchant_private_key, PARAM_TYPE, charset,
alipay_public_key,sign_type);
}
/**
* @Title: getAlipayClient
* @Description: TODO(支付寶APP請求客戶端例項)
* @return
*/
public static AlipayClient getAlipayClient(){
return SingletonHolder.alipayClient;
}
public static String getResponseBody(PayModelVo payModelVo, String strBody) throws AlipayApiException {
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("支付寶支付");
model.setOutTradeNo(payModelVo.getOutTradeNo()); //商戶訂單id
model.setTotalAmount(payModelVo.getMoney());// 訂單金額:元
model.setSubject(payModelVo.getSubject());// 訂單標題
model.setTimeoutExpress("30m");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setEnablePayChannels("moneyFund,debitCardExpress");
request.setBizModel(model);
request.setNotifyUrl(AliPayConfig.NOTIFY_URL);
String orderString="";
try {
//這裡和普通的介面呼叫不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = AliPayConfig.getAlipayClient().sdkExecute(request);
orderString=response.getBody();
System.err.println("orderString : "+orderString);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return orderString;
}
public static String aliPayPc(PayModelVo payModelVo) {
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(payModelVo.getReturnUrl());// 前臺通知
alipayRequest.setNotifyUrl(payModelVo.getNotifyUrl());// 後臺回撥
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", payModelVo.getOutTradeNo());
bizContent.put("total_amount", payModelVo.getMoney());// 訂單金額:元
bizContent.put("subject", payModelVo.getSubject());// 訂單標題
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
bizContent.put("body", payModelVo.getBody());
bizContent.put("enable_pay_channels", "moneyFund,debitCardExpress");
String biz = bizContent.toString().replaceAll("\"", "'");
alipayRequest.setBizContent(biz);
String form = Constants.FAIL;
AlipayClient alipayClient = getAlipayClient();
try {
form = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
}
return form;
}
}
在這裡回撥函式是被抽取出來了
PropertiesUtil.getPropertiesValue("sys","envPrefix")
這個方法是我的讀取xml配置檔案的方法 在後面的部落格工具類篇中我會提到
好了 這些就是支付寶的配置檔案 接下來就是最重要的 預下單與回撥函式的編寫了
預下單
/**
* @Title: trans
* @Description: (支付寶交易)
* @param request
* @return
*/
@RequestMapping(value = "trans", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody
public String trans(HttpServletRequest request, final PayModelVo payModelVo, final String addressId, final String cartIds, String mergeId, final String isDispatch, final String isMerge, final String logisticsId, String ProductList) {
log.debug("開始支付寶呼叫");
ResultModel resultModel = new ResultModel();
try {
String token = request.getHeader("token");
String money = payModelVo.getMoney(); /// 變動金額
String type = payModelVo.getType(); // 消費型別 使用者充值 使用者提現 商品出售 會員購買會員升級 詢價單購買 待支付訂單的支付(此類支付不需要預訂單了)
String out_trade_no = payModelVo.getOutTradeNo();
//回撥地址
payModelVo.setToken(token);
payModelVo.setNotifyUrl(AliPayConfig.NOTIFY_URL);
payModelVo.setBody("支付用途");
payModelVo.setMoney(money);
//將payModelVo中所有資訊存入redis
RedisTool.set("notify_"+out_trade_no,JSON.toJSONString(payModelVo));
//返回支付寶請求
Product product = new Product();
String subject = "支付寶支付";
product.setOutTradeNo(payModelVo.getOutTradeNo());
product.setSubject(subject);
product.setTotalAmount(payModelVo.getMoney());
product.setNotifyUrl(AliPayConfig.NOTIFY_URL);
String orderString = aliPayService.aliPayMobile(product);
resultModel = new ResultModel(200,"10000","執行成功");
Map<String,String> map = new HashMap<>();
map.put("orderString",orderString);
resultModel.setResult(map);
} catch (Exception e) {
log.error("支付寶支付異常", e);
e.printStackTrace();
resultModel.setError(500);
resultModel.setErrorCode("10008");
resultModel.setMsg("預下單異常");
}
log.info("支付寶支付app:" + JsonUtil.toJson(resultModel));
return JsonUtil.toJson(resultModel);
}
支付寶支付實現類
aliPayService.aliPayMobile
public String aliPayMobile(Product product) {
//例項化客戶端
//例項化具體API對應的request類,類名稱和介面名稱對應,當前呼叫介面名稱:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已經封裝掉了公共引數,這裡只需要傳入業務引數。以下方法為sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("支付寶支付");
model.setOutTradeNo(product.getOutTradeNo()); //商戶訂單id
model.setTotalAmount(product.getTotalAmount());// 訂單金額:元
model.setSubject(product.getSubject());// 訂單標題
model.setTimeoutExpress("30m");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setEnablePayChannels("moneyFund,debitCardExpress");
request.setBizModel(model);
request.setNotifyUrl(product.getNotifyUrl());
String orderString="";
try {
//這裡和普通的介面呼叫不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = AliPayConfig.getAlipayClient().sdkExecute(request);
orderString=response.getBody();
logger.info("orderString", orderString);//就是orderString 可以直接給客戶端請求,無需再做處理。
} catch (AlipayApiException e) {
logger.error("支付寶構造表單失敗", e);
}
return orderString;
}
APP呼叫預下單之後可以直接拿到字串去進行支付,支付完成之後 支付寶會進行支付成功的回撥 一般我們的業務都是在成功的回撥中去進行編寫的
支付回撥 ,當然 如果要是想測試回撥 在本機的情況下 你需要外網的對映 這些東西網上不少 但是一般都是需要花錢的 一般也不貴 我用的net123 ,30塊錢用一年了都
支付回撥
/**
* @Title: notify
* @Description: (充值支付寶支付回撥)
* @param request
* @param response
* @throws Exception
*/
@Transactional(readOnly = false)
@RequestMapping(value = "alipay_notify", produces = "application/json;charset=UTF-8")
public void alipay_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("支付寶支付回撥!");
String message = "failure";
String outtradeno = "";
//如果失敗,應存入redis,便於出問題時候檢視原因(預設儲存兩天)
try {
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] + ",";
}
// 亂碼解決,這段程式碼在出現亂碼時使用
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(params, AliPayConfig.alipay_public_key,
AliPayConfig.charset, AliPayConfig.sign_type); // 呼叫SDK驗證簽名
outtradeno = params.get("out_trade_no");
//將支付資料存入redis中 使用out_tran_no與支付字首來進行區分
PayModelVo paymodel = (PayModelVo) new Gson().fromJson(RedisTool.get("notify_" + outtradeno), PayModelVo.class);
if (signVerified) {//支付寶驗證簽名成功
// 若引數中的appid和填入的appid不相同,則為異常通知
if (!AliPayConfig.app_id.equals(params.get("app_id"))) {
log.info("與付款時的appid不同,此為異常通知,應忽略!");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"與付款時的appid不同");
} else {
String receiptAmount = params.get("receipt_amount");
String status = params.get("trade_status");
if (status.equals("TRADE_SUCCESS") || status.equals("TRADE_FINISHED")) { // 如果狀態是已經支付成功
/**
* 需要判斷redis中是否有out_trade_no
* 如果沒有,說明有兩種情況:一種是資料出錯(包括等待時間過長,redis自動清除),一種是已經回撥過一次(預設成功完成後刪除redis),需要去資料庫查詢單子狀態,進一步確認
* 如果有,那就正常執行業務
*/
//redis中沒有out_trade_no
if (!RedisTool.exists("notify_" + outtradeno)) {
}else{
RedisTool.del("notify_" + outtradeno);
// 校驗金額
if(!receiptAmount.equals(paymodel.getMoney())){
//金額數目不對
return;
}
ResultModel resultModel = new ResultModel();
/**
* TODO 業務處理
*/
// payService.business(paymodel,resultModel);
if(resultModel.getError()==200){
message="success";
if(paymodel.getPaytype().equals("7")){//通聯微信購買,需要向pc推送
try {
// WxPayController.goeasy(paymodel,"true");//GoEasy通知前端
} catch (Exception e) {
e.printStackTrace();
}
}
}else{
log.info("notify_" + outtradeno+":業務處理失敗!");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"業務處理失敗");
}
}
} else{
log.info("notify_" + outtradeno+":支付寶回撥failure");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"支付寶處理出錯");
}
}
}else { // 如果驗證簽名沒有通過
log.info("notify_" + outtradeno+":驗證簽名失敗!");
RedisTool.setex("notify_" + outtradeno,DateTools.getDatelinetoInt(),"驗證簽名失敗");
}
} catch (Exception e) {
log.error("notify_" + outtradeno+"alipay_notify出現異常", e);
RedisTool.setex("notify_" + outtradeno, 1,"其他異常");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} finally {
writeMessageToResponse(response, message);
}
}
返回給支付寶是否成功的資訊
/**
* @Title: writeMessageToResponse
* @Description: (向支付寶發起支付結果通知)
* @param response
* @param message
*/
protected void writeMessageToResponse(HttpServletResponse response, String message) {
PrintWriter writer = null;
try {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
writer = response.getWriter();
writer.print(message);
writer.flush();
} catch (IOException e) {
log.error("io出現異常", e);
} finally {
if (writer != null)
writer.close();
}
}
支付寶的回撥 最關鍵的就是驗證 其實你看那麼多程式碼 幾乎全部都是驗證用的 首先取出引數 進行簽名 驗證支付寶的簽名的合法性 這個是用來保證支付的安全性 之後就是驗證 是否是二次回撥 當你的業務沒有處理成功的時候 你沒有給支付寶返回正確的返回值 支付寶會進行多次的回撥的 如果是二次回撥 你需要直接略過 最後是驗證你的支付金額和賣出產品的金額是否一致 這個很重要 比如前臺直接將加個修改成0.01那直接支付你不去驗證 那業務也是可以成立的 回撥依然後進行
好的 支付寶APP支付的業務就只有這些了 下一篇是支付寶的
PC支付 :PC支付
有什麼不對的地方請直接評論 謝謝