支付寶支付對接DEMO

對接支付寶流程:
一、步驟分析
❶以post方式進行提交參數請求到支付寶接口進行支付
❷支付寶收到支付後進行通知給你安全、支付回調等問題(兩種通知方式:1.同步通知,2.異步通知)
①同步通知
支付寶同步通知本地瀏覽器進行重定向操作,主要給用戶展示支付寶支付結果是否成功,不會處理訂單狀態
②異步通知
支付寶服務器使用httpclient技術調用你的接口進行通知,支付寶把支付結果以報文方式給你,你進行解析是支付成功還是失敗,根據結果修改訂單狀態,可能由於網絡原因,你沒有響應支付寶,這時候支付寶可能會進行補償機制,解決支付回調冪等使用全局ID

二、圖解DEMO配置步驟

公鑰加密,私鑰解密

  1. 將下載的alipay.trade.page.pay-JAVA-UTF-8項目導入到eclipse中,可以通過如下鏈接下載
    鏈接:https://pan.baidu.com/s/1g-uOZ_HJ3qj2QiWe3-qmqw
    提取碼:f3er

  2. 通過如下鏈接進入支付寶沙箱登錄頁面
    https://openhome.alipay.com/platform/appDaily.htm
    在這裏插入圖片描述

  3. 進入到如下界面
    在這裏插入圖片描述

  4. 使用支付寶生成公鑰應用代碼生成公鑰和私鑰,可通過如下鏈接下載
    鏈接:https://pan.baidu.com/s/1DCCxrzFGll_A7jg3OceVMg
    提取碼:ezft

  5. 生成公鑰和私鑰之後,複製公鑰配置到如下處
    在這裏插入圖片描述

  6. 將生成的私鑰粘貼到項目中的如下處
    在這裏插入圖片描述

  7. 在步驟5中的圖片中查看支付寶公鑰並複製
    在這裏插入圖片描述

  8. 將複製的支付寶公鑰粘貼到項目中的如下處
    在這裏插入圖片描述

  9. 複製APPID,並粘貼
    在這裏插入圖片描述
    在這裏插入圖片描述

  10. 複製支付寶網關,並粘貼
    在這裏插入圖片描述
    在這裏插入圖片描述

  11. 配置下面的兩個參數要使用外網映射工具(如natapp)
    在natapp中可以購買一個隧道,然後將下載的客戶端啓動獲取複製它的地址並粘貼
    在這裏插入圖片描述
    其它參數選擇默認

  12. 將該項目添加到tomcat中,如下配置
    在這裏插入圖片描述

  13. 在瀏覽器中進行訪問
    在這裏插入圖片描述

  14. 進入測試賬號
    在這裏插入圖片描述

  15. 使用測試賬號進行測試付款
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    三、將DEMO整合到項目中

  16. 創建表

CREATE TABLE `payment_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` int(11) DEFAULT NULL,
  `typeid` int(2) DEFAULT NULL,
  `orderid` varchar(50) DEFAULT NULL,
  `price` decimal(10,0) DEFAULT NULL,
  `source` varchar(10) DEFAULT NULL,
  `state` int(2) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `platformorderid` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
  1. 支付流程
1.向請求支付支付表創建一條支付信息
2.生成支付token,存入redis,key爲支付token,value爲支付表id
3.返回支付token給客戶端
4.使用支付token,向redis查找對應支付表的id
5.使用支付表的id,獲取支付信息
6.封裝支付寶from表單提交參數 
  1. 進行項目整合

創建自己的實體類這塊省略不寫
在Payapi接口的服務PayService類中創建接口

    //創建支付令牌
	@RequestMapping("/createPayToken")
	public ResponseBase createToken(@RequestBody PaymentInfo paymentInfo);
	
	// 使用支付令牌查找支付信息
	@RequestMapping("/findPayToken")
	public ResponseBase findPayToken (@RequestParam("payToken") String  payToken);

創建DAO層

    @Select("select * from payment_info where  id=#{id}")
	public PaymentInfo getPaymentInfo(@Param("id") Long id);

	@Insert("insert into payment_info ( id,userid,typeid,orderid,platformorderid,price,source,state,created,updated) value(null,#{userId},#{typeId},#{orderId},#{platformorderId},#{price},#{source},#{state},#{created},#{updated})")
	@Options(useGeneratedKeys = true, keyProperty = "id") 
	public Integer savePaymentType(PaymentInfo paymentInfo);

	@Select("select * from payment_info where  orderId=#{orderId}")
	public PaymentInfo getByOrderIdPayInfo(@Param("orderId") String orderId);

	@Update("update payment_info set state =#{state},payMessage=#{payMessage},platformorderId=#{platformorderId},updated=#{updated} where orderId=#{orderId} ")
	public void updatePayInfo(PaymentInfo paymentInfo);

將上面指的DEMO中的AlipayConfig類複製到實現類的項目中
引入如下jar

        <dependency>
			<groupId>com.github.1991wangliang</groupId>
			<artifactId>alipay-sdk</artifactId>
			<version>1.0.0</version>
		</dependency>

書寫實現類

    @Autowired
	private PaymentInfoDao paymentInfoDao;

	/**
	 * 創建支付令牌
	 */
	@RequestMapping(value = "/createPayToken", method = RequestMethod.POST)
	public ResponseBase createToken(@RequestBody PaymentInfo paymentInfo) {
		// 1.創建支付請求信息
		Integer savePaymentType = paymentInfoDao.savePaymentType(paymentInfo);
		if (savePaymentType <= 0) {
			return setResultError("創建支付訂單支付失敗");
		}
		// 2.生成對應的token
		String payToken = TokenUtils.getMemberToken();
		// 3.存放在redis中,key爲token,value爲支付id
		baseRedisService.setString(payToken, paymentInfo.getId() + "", Constants.PAY_TOKEN_MEMBER_TIME);
		// 4.返回token
		JSONObject data = new JSONObject();
		data.put("payToken", payToken);
		return setResultSuccess(data);
	}

	/**
	 * 使用支付令牌查找支付信息
	 */
	@RequestMapping(value="/findPayToken")
	public ResponseBase findPayToken(String payToken) {
		// 1.參數驗證
		if (StringUtils.isEmpty(payToken)) {
			return setResultError("token不能爲空");
		}
		// 2.判斷token有效期
		// 3.使用token查找redis,找到對應支付id
		String payId = (String) baseRedisService.getString(payToken);
		if (StringUtils.isEmpty(payId)) {
			return setResultError("支付請求已經超時");
		}
		// 4.使用支付id,進行下單
		Long payID = Long.parseLong(payId);
		// 5.使用支付id查詢支付信息
		PaymentInfo paymentInfo = paymentInfoDao.getByOrderIdPayInfo(payId);
		if (paymentInfo == null) {
			return setResultError("未找到支付信息");
		}
		// 6.對接支付代碼,返回提交支付from表單元素給客戶端
		// 獲得初始化的AlipayClient
		AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
				AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
				AlipayConfig.sign_type);

		// 設置請求參數
		AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
		alipayRequest.setReturnUrl(AlipayConfig.return_url);
		alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

		// 商戶訂單號,商戶網站訂單系統中唯一訂單號,必填
		String out_trade_no = paymentInfo.getOrderId();
		// 付款金額,必填
		String total_amount = paymentInfo.getPrice() + "";
		// 訂單名稱,必填
		String subject = "vm商城充值";
		// 商品描述,可空
		// String body = new
		// String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");

		alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\"" + total_amount
				+ "\"," + "\"subject\":\"" + subject + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

		// 若想給BizContent增加其他可選請求參數,以增加自定義超時時間參數timeout_express來舉例說明
		// alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no
		// +"\","
		// + "\"total_amount\":\""+ total_amount +"\","
		// + "\"subject\":\""+ subject +"\","
		// + "\"body\":\""+ body +"\","
		// + "\"timeout_express\":\"10m\","
		// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
		// 請求參數可查閱【電腦網站支付的API文檔-alipay.trade.page.pay-請求參數】章節
		// 請求
		String result;
		try {
			result = alipayClient.pageExecute(alipayRequest).getBody();
			JSONObject data = new JSONObject();
			data.put("payHtml", result);
			return setResultSuccess(data);
		} catch (AlipayApiException e) {
			return setResultError("支付異常");
		}
	}

上面代碼中生成token類和封裝的響應類以及redis類就不展示了
使用postman進行測試,首先測試創建支付令牌方法
在這裏插入圖片描述
測試使用支付令牌查找支付信息方法
在這裏插入圖片描述
19. 整合到web層
將實現類引入到web層服務的pom中
寫PayServiceFegin接口

@Component
@FeignClient("pay")
public interface PayServiceFegin extends PayService {
}

在web層服務中寫Controller類

    @Autowired
	private PayServiceFegin payServiceFegin;
	
	/**
	 * 使用token進行支付
	 * @throws IOException 
	 */
	@RequestMapping("/aliPay")
	public void aliPay(String payToken,HttpServletResponse response) throws IOException{
		response.setContentType("text/html;charset=utf-8");
		PrintWriter writer = response.getWriter();
		//1.參數驗證
		if (StringUtils.isEmpty(payToken)) {
			return;
		}
		//2.調用支付服務接口,獲取支付寶html元素
		ResponseBase payTokenResult = payServiceFegin.findPayToken(payToken);
		if (!payTokenResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
			String msg = payTokenResult.getMsg();
			writer.println(msg);
			return;
		}
		//3.返回可以執行的html元素給客戶端
		LinkedHashMap data = (LinkedHashMap)payTokenResult.getData();
		String payHtml = (String)data.get("payHtml");
		log.info("###PayControllerpayHtml{}",payHtml);
		//4.頁面上進行渲染
		writer.println(payHtml);
		writer.close();
	}

在瀏覽器中進行測試,要傳入token參數,如下界面
在這裏插入圖片描述
20. 同步和異步回調
同步回調
在Payapi接口的服務CallBackService類中創建接口

    // 同步通知
	@RequestMapping("/synCallBack")
	public ResponseBase synCallBack(@RequestParam Map<String, String> params);

	// 異步通知
	@RequestMapping("/asynCallBack")
	public String asynCallBack(@RequestParam Map<String, String> params);

在實現payapi的服務中寫實現類

    /**
     * 同步回調
     */
    @Override
	public ResponseBase synCallBack(Map<String, String> params) {
		//1.日誌記錄
		log.info("#####支付寶同步通知synCallBack#####開始,params:{}", params);
		//2.驗籤操作
		try {
			//調用SDK驗證簽名
			boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); 
			log.info("#####支付寶同步通知signVerified:{}######", signVerified);
			//——請在這裏編寫您的程序(以下代碼僅作參考)——
			if(!signVerified) {
				return setResultError("驗籤失敗");
			}
			//商戶訂單號
			String outTradeNo = params.get("out_trade_no");
			//支付寶交易號
			String tradeNo = params.get("trade_no");
			//付款金額
			String totalAmount = params.get("total_amount");
			JSONObject data = new JSONObject();
			data.put("outTradeNo", outTradeNo);
			data.put("tradeNo", tradeNo);
			data.put("totalAmount", totalAmount);
			return setResultSuccess(data);
		} catch (Exception e) {
			log.error("####支付寶同步通知出現異常,ERROR:", e);
			return setResultError("系統錯誤");
		}finally {
			log.info("#####支付寶同步通知synCallBack#####結束,params:{}", params);
		}

   /**
	 * 異步回調
	 */
	@Override
	public String asynCallBack(@RequestParam Map<String, String> params) {
		// 1.日誌記錄
		log.info("#####支付寶異步通知synCallBack#####開始,params:{}", params);
		// 2.驗籤操作
		try {
			// 調用SDK驗證簽名
			boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
					AlipayConfig.charset, AlipayConfig.sign_type);
			log.info("#####支付寶異步通知signVerified:{}######", signVerified);
			// ——請在這裏編寫您的程序(以下代碼僅作參考)——
			if (!signVerified) {
				return Constants.PAY_FAIL;
			}
			// 商戶訂單號
			String outTradeNo = params.get("out_trade_no");
			PaymentInfo paymentInfo = paymentInfoDao.getByOrderIdPayInfo(outTradeNo);
			if (paymentInfo == null) {
				return Constants.PAY_FAIL;
			}
			// 支付寶重試機制
			Integer state = paymentInfo.getState();
			if (state == 1) {
				return Constants.PAY_SUCCESS;
			}
			// 支付寶交易號
			String tradeNo = params.get("trade_no");
			// 付款金額
			// String totalAmount = params.get("total_amount");
			// 判斷實際付款金額與商品金額是否一致
			// 修改支付寶狀態
			paymentInfo.setState(1);
			paymentInfo.setPayMessage(params.toString());
			paymentInfo.setPlatformorderId(tradeNo);
			// 手動begin
			Integer updateResult = paymentInfoDao.updatePayInfo(paymentInfo);
			if (updateResult <= 0) {
				return Constants.PAY_FAIL;
			}
			// 調用訂單接口通知,支付狀態
			ResponseBase orderResult = orderServiceFegin.updateOrderIdInfo(1l, tradeNo, outTradeNo);
			if (!orderResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
				// 回滾rollback
				return Constants.PAY_FAIL;
			}
			// 2PC 3PC TCC MQ
			// 手動 提交 comiit;
			return Constants.PAY_SUCCESS;
		} catch (Exception e) {
			log.error("####支付寶異步通知出現異常,ERROR:", e);
			// 回滾 手動回滾 rollback
			return Constants.PAY_FAIL;
		} finally {
			log.info("#####支付寶異步通知synCallBack#####結束,params:{}", params);
		}
	}

在web層的服務中寫Controller

    @Autowired
	private PayCallBackFegin payCallBackFegin;
	// 錯誤頁面
	private static final String ERROR = "";
	//成功返回的頁面,這塊用的是templates模板
	private static final String PAY_SUCCESS = "pay_success";

   /**
	 * 使用token進行支付
	 * 
	 * @throws IOException
	 */
	@RequestMapping("/aliPay")
	public void aliPay(String payToken, HttpServletResponse response) throws IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter writer = response.getWriter();
		// 1.參數驗證
		if (StringUtils.isEmpty(payToken)) {
			return;
		}
		// 2.調用支付服務接口,獲取支付寶html元素
		ResponseBase payTokenResult = payServiceFegin.findPayToken(payToken);
		if (!payTokenResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
			String msg = payTokenResult.getMsg();
			writer.println(msg);
			return;
		}
		// 3.返回可以執行的html元素給客戶端
		LinkedHashMap data = (LinkedHashMap) payTokenResult.getData();
		String payHtml = (String) data.get("payHtml");
		log.info("###PayControllerpayHtml{}", payHtml);
		// 4.頁面上進行渲染
		writer.println(payHtml);
		writer.close();
	}	

	/**
	 * 同步回調
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@RequestMapping("/callBack/synCallBack")
	public void synCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
		Map<String, String[]> requestParams = request.getParameterMap();
		Map<String, String> params = new HashMap<String, String>();
		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);
		}
		PrintWriter writer = response.getWriter();

		ResponseBase synCallBack = payCallBackFegin.synCallBack(params);
		if (!synCallBack.getCode().equals(Constants.HTTP_RES_CODE_200)) {
			return;
		}
		LinkedHashMap data = (LinkedHashMap) synCallBack.getData();
		String htmlFrom = "<form name='punchout_form'"
				+ " method='post' action='http://127.0.0.1/callBack/synSuccessPage'>"
				+ "<input type='hidden' name='outTradeNo' value='" + data.get("out_trade_no") + "'>"
				+ "<input type='hidden' name='tradeNo' value='" + data.get("trade_no") + "'>"
				+ "<input type='hidden' name='totalAmount' value='" + data.get("total_amount") + "'>"
				+ "<input type='submit' value='立即支付' style='display:none'>"
				+ "</form><script>document.forms[0].submit();" + "</script>";
		writer.println(htmlFrom);
		writer.close();
	}

	/**
	 * 同步回調,解決隱藏參數
	 * 
	 * @param request
	 * @param outTradeNo
	 * @param tradeNo
	 * @param totalAmount
	 * @return
	 */
	@RequestMapping(value = "/callBack/synSuccessPage", method = RequestMethod.POST)
	public String synSuccessPage(HttpServletRequest request, String outTradeNo, String tradeNo, String totalAmount) {
		request.setAttribute("outTradeNo", outTradeNo);
		request.setAttribute("tradeNo", tradeNo);
		request.setAttribute("totalAmount", totalAmount);
		return PAY_SUCCESS;
	}

在實現payapi的服務中複製的支付寶的AlipayConfig類改變如下兩個參數

    // 服務器異步通知頁面路徑  需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
	public static String notify_url = "http://rtwtfv.natappfree.cc/callBack/asynCallBack";

	// 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
	public static String return_url = "http://rtwtfv.natappfree.cc/callBack/synCallBack";

成功頁面的templates模板

<h3>您的訂單號爲 ${outTradeNo} ,支付寶F易號${tradeNo} ,支付成功了一筆${totalAmount}元。</h3>

用postman測試
在這裏插入圖片描述
複製上面的token,在瀏覽器中進行測試
http:127.0.0.1/aliPay?payToken=TOKEN_MEMBER-b2439a51-ad44-49b0-81cb-086ee8e51bc3
用測試用戶支付成功後會跳轉到templates的成功模板中

頁面的回調還有寫完,歡迎關注,會持續補全

待更新。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章