1. 程式人生 > >一點一滴開始搭建自己的專案框架之支付寶篇 支付寶APP支付

一點一滴開始搭建自己的專案框架之支付寶篇 支付寶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支付

有什麼不對的地方請直接評論 謝謝