支付寶當面付掃碼支付功能詳解


前言: 上篇呢主要是針對微信驗證登錄做了講解,當然微信也是提供了很多的接口來供開發者進行調用,同樣,微信也有支付,相信小伙伴們學習了上篇的登錄之后,已經能夠融匯貫通,做出微信的支付功能。那么本篇呢就講解一下支付寶的支付功能,同樣的,通過這一個例子,你就能使用支付寶其它的功能,還是那句老話,就當做是一個敲門磚吧,好了,下面就開始吧。

 

本篇為原創,轉載請標出處http://www.cnblogs.com/gudu1/p/8094197.html

 

  微信有測試公眾號測試,那么支付寶呢?他也有,不過名字是叫做支付寶沙箱環境,地址:https://sandbox.alipaydev.com/sms/receive.htm ,掃碼登陸之后:

 

  >> APPID,支付寶網關,以及應用網關 這些呢是固定不變的。

  >> 我們已經知道微信使用的是 SHA-1 加密,那么支付寶使用的就是RSA 和 RSA2 加密,當然了,支付寶推薦使用RSA2加密,兩個的區別就是RSA2 是2048 位,RSA 是1024位,所以RSA2加密更好,我們就使用它就好。

  >> 授權回調地址就是用戶掃碼進行支付之后,支付寶服務器回調我們程序接口的地址,規則呢跟微信的是一樣的,這里不多講了,不明白的請看一下上篇微信驗證登錄的講解 http://www.cnblogs.com/gudu1/p/8087130.html

  >> 接下來的就配置一下我們的密匙,使用哪個加密方式就配置哪個就好了,支付寶很周到,給我們提供了生成密匙的工具,下載位置:https://docs.open.alipay.com/291/105971 ,這個也是支付寶的官方文檔,具體怎么使用,里面都很詳細,這里就不占篇幅了,然后生成之后,就直接 copy 進去配置就好了,很簡單的。

 

  下面還是老樣子,先貼出代碼,然后講解代碼中的一些點,支付寶也提供了Java 、PHP、.NET  版本的支付代碼,這點是非常到位的,所以我們只是站在巨人的肩膀上,前人栽樹,后人乘涼,下載地址:https://docs.open.alipay.com/194/105201/ ,下載我們都會,下載之后把代碼copy 出來,需要使用哪個功能就copy 哪個,因為我這里使用的支付寶生成二維碼預下單的功能,然后:

Controller 

@Controller @RequestMapping("/order/") public class OrderController { @RequestMapping("pay.do") @ResponseBody private ServerResponse<Map<String, String>> pay(HttpSession session, Long orderNo, HttpServletRequest request) { Integer userId = ((User) session.getAttribute(Const.CURRENT_USER)).getId(); String path = request.getSession().getServletContext().getRealPath(PropertiesUtil.getProperty("upload_image_path")); if (orderNo == null || orderNo == 0L) { return ServerResponse.createByErrorMessage("支付訂單不能為空"); } return orderService.pay(userId, orderNo, path); } }

 

 Service 的pay 方法

@Service("iOrderService") public class OrderServiceImpl implements IOrderService { private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class); // 支付寶當面付2.0服務
    private static AlipayTradeService tradeService; static { /** 一定要在創建AlipayTradeService之前調用Configs.init()設置默認參數 * Configs會讀取classpath下的zfbinfo.properties文件配置信息,如果找不到該文件則確認該文件是否在classpath目錄 */ Configs.init("zfbinfo.properties"); /** 使用Configs提供的默認參數 * AlipayTradeService可以使用單例或者為靜態成員對象,不需要反復new */ tradeService = new AlipayTradeServiceImpl.ClientBuilder().build(); } /** * 支付寶預創建支付訂單,生成支付的二維碼用戶用戶掃碼支付 * * @param userId 用戶ID * @param orderNo 訂單號 * @param path 本地上傳圖片地址 * @return
     */ @Transactional public ServerResponse pay(Integer userId, Long orderNo, String path) { Map<String, String> mapResult = Maps.newHashMap(); Order order = orderMapper.selectOrderByOrderNo(orderNo); if (order == null) { return ServerResponse.createBySuccessMessage("該訂單不存在"); } // (必填) 商戶網站訂單系統中唯一訂單號,64個字符以內,只能包含字母、數字、下划線, // 需保證商戶系統端不能重復,建議通過數據庫sequence生成,
        String outTradeNo = order.getOrderNo().toString(); // (必填) 訂單標題,粗略描述用戶的支付目的。如“xxx品牌xxx門店當面付掃碼消費”
        String subject = "Happy_mmall 掃碼付款"; // (必填) 訂單總金額,單位為元,不能超過1億元 // 如果同時傳入了【打折金額】,【不可打折金額】,【訂單總金額】三者,則必須滿足如下條件:【訂單總金額】=【打折金額】+【不可打折金額】
        String totalAmount = order.getPayment().toString(); // (可選) 訂單不可打折金額,可以配合商家平台配置折扣活動,如果酒水不參與打折,則將對應金額填寫至此字段 // 如果該值未傳入,但傳入了【訂單總金額】,【打折金額】,則該值默認為【訂單總金額】-【打折金額】
        String undiscountableAmount = "0"; // 賣家支付寶賬號ID,用於支持一個簽約賬號下支持打款到不同的收款賬號,(打款到sellerId對應的支付寶賬號) // 如果該字段為空,則默認為與支付寶簽約的商戶的PID,也就是appid對應的PID
        String sellerId = ""; // 訂單描述,可以對交易或商品進行一個詳細地描述,比如填寫"購買商品2件共15.00元"
        String body = new StringBuffer().append("訂單:").append(order.getOrderNo()).append(",共花費").append(order.getPayment()).append("元").toString(); // 商戶操作員編號,添加此參數可以為商戶操作員做銷售統計
        String operatorId = "test_operator_id"; // (必填) 商戶門店編號,通過門店號和商家后台可以配置精准到門店的折扣信息,詳詢支付寶技術支持
        String storeId = "test_store_id"; // 業務擴展參數,目前可添加由支付寶分配的系統商編號(通過setSysServiceProviderId方法),詳情請咨詢支付寶技術支持
        ExtendParams extendParams = new ExtendParams(); extendParams.setSysServiceProviderId("2088100200300400500"); // 支付超時,定義為120分鍾
        String timeoutExpress = "120m"; // 商品明細列表,需填寫購買商品詳細信息,
        List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>(); // 添加 用戶 預支付的訂單中的商品
        List<OrderItem> orderItemList = orderItemMapper.selectListByUserIdAndOrderNo(orderNo, userId); for (OrderItem orderItem : orderItemList) { GoodsDetail goods1 = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(), BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(), new Double(100)).longValue(), orderItem.getQuantity()); goodsDetailList.add(goods1); } // 創建掃碼支付請求builder,設置請求參數
        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder() .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo) .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body) .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams) .setTimeoutExpress(timeoutExpress) .setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付寶服務器主動通知商戶服務器里指定的頁面http路徑,根據需要設置
 .setGoodsDetailList(goodsDetailList); // 創建預支付訂單對象
        AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder); switch (result.getTradeStatus()) { case SUCCESS: logger.info("支付寶預下單成功: )"); // 獲取響應 Response
                AlipayTradePrecreateResponse response = result.getResponse(); // 簡單打印一下日志
 dumpResponse(response); // 創建本地上傳圖片的文件夾,不存在則創建
                File folder = new File(path); if (!folder.exists()) { folder.setWritable(true); folder.mkdirs(); } // 需要修改為運行機器上的路徑,
                String filePath = String.format(path + "/qr-%s.png", response.getOutTradeNo()); // %s 是一種占位符,即后面的response.getOutTradeNo() ,只是生成額隨機字符串,防止重名
                String fileName = String.format("/qr-%s.png", response.getOutTradeNo()); logger.info("filePath:" + filePath); // 上傳到本地服務器
                ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath); File targetFile = new File(path, fileName); try { // 上傳圖片到FTP服務器,上傳FTP完畢之后,刪除本地存儲的圖片
 FTPUtil.upload(Lists.newArrayList(targetFile)); } catch (IOException e) { logger.error("上傳二維碼失敗", e); e.printStackTrace(); } // 剛剛上傳到FTP的圖片地址URL
                String qrPathUrl = PathUtil.getFTPImgPath(targetFile.getName()); mapResult.put("qrPath", qrPathUrl); mapResult.put("orderNo", orderNo.toString()); return ServerResponse.createBySuccess(mapResult); case FAILED: logger.error("支付寶預下單失敗!!!"); return ServerResponse.createByErrorMessage("支付寶預下單失敗!!!"); case UNKNOWN: logger.error("系統異常,預下單狀態未知!!!"); return ServerResponse.createByErrorMessage("系統異常,預下單狀態未知!!!"); default: logger.error("不支持的交易狀態,交易返回異常!!!"); return ServerResponse.createByErrorMessage("不支持的交易狀態,交易返回異常!!!"); } } }

 

  >> 在此之前呢不要忘記添加支付寶的集成依賴jar包。

  >> 為了方便理解,代碼中的每一步都添加了注釋,代碼比較多,但是大多數的代碼都是直接copy支付寶 提供的Demo,然后根據我們自己的業務需求修改。

  >> 由於篇幅問題,請注意靜態代碼塊中的代碼,會加載一個配置文件,這個配置文件支付寶同樣有提供,我們只要修改一下其中的參數值,APPID、PID(商戶UID)、以及加密的公鑰和私鑰。

  >> 我們程序支付整體的思路是這樣:用戶確認下單后,點擊支付,然后會調用我們pay.do 這個接口,然后我們的程序在向支付寶服務器發送請求之前會添加一些參數,就是商品的訂單號、收款平台信息、以及購買商品需要支付的總價格,需要修改的一處是 創建AlipayTradePrecreateRequestBuilder對象的時候,把call_back的URL修改成我們自己程序的接口,然后程序就向支付寶發送消息,因為這里支付寶集成做的特別好,只需要創建一個預支付對象,把需要的參數傳進去,就可以發起預支付請求,返回二維碼字節流,我們把二維碼進行保存,然后展示給用戶,進行掃碼支付,用戶掃碼之后,不管是支付成功或者支付失敗都會回調我們的call_back中配置的URL,進行處理。

 

 接下來就是我們的 call_back :

@Controller @RequestMapping("/order/") public class OrderController { @RequestMapping("alipay_callback.do") @ResponseBody private ServerResponse callBack(HttpServletRequest request) throws AlipayApiException { // 取出支付寶回調攜帶的所有參數並進行轉換,數組轉換為字符串
        Map<String, String[]> tempParams = request.getParameterMap(); // 參數存放 Map
        Map<String, String> requestParams = Maps.newHashMap(); for (Iterator<String> iterator = tempParams.keySet().iterator(); iterator.hasNext(); ) { String key = iterator.next(); String[] strs = tempParams.get(key); String str = ""; // 這里如果數組的長度是1,說明只有一個,直接賦值就好,如果超過一個,后面加一個逗號來隔離
            for (int i = 0; i < strs.length; i++) { str = strs.length - 1 == i ? str + strs[i] : str + strs[i] + ","; } requestParams.put(key, str); } // 去除sign_type
        requestParams.remove("sign_type"); try { // 驗證簽名
            boolean result = AlipaySignature.rsaCheckV2(requestParams, Configs.getPublicKey(), "utf-8", Configs.getSignType()); if (!result) { return ServerResponse.createByErrorMessage("非法請求,再惡意請求我就報警找網警了"); } } catch (AlipayApiException e) { logger.error("支付寶回調驗證異常", e); e.printStackTrace(); throw e; } // 調用Service 方法進行處理
        ServerResponse serverResponse = orderService.alipayCallBack(requestParams); if (!serverResponse.isSuccess()) { logger.error("OrderController.callBack()","數據操作失敗"); return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_FAILED); } logger.info("支付寶支付回調完成,沒有異常"); return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_SUCCESS); } }

 

 

  >> 這個支付寶回調URL是這樣:http://smyang.s1.natapp.cc/order/alipay_callback.do,對應我們的Controller 的RequestMapping中的路由,代碼中都有添加注釋,還是很清晰的。

  >> 支付寶的驗證簽名的規則是怎樣的呢?在通知返回的參數中除了sign_type和sign,其余的都是待驗簽的參數,詳細請看 https://docs.open.alipay.com/194/103296/ ,但是這里只remove 掉了sign_type,通過查看源碼發現,在支付寶集成代碼中需要獲取一下sign,然后它才remove 掉了sign,所以這里我們只需要remove掉sign_type就好,而且是必須的。

  >> 在 rsaCheckV2() 方法中我們加入的參數sign_type,指定了使用哪種加密方式來驗簽,如果通過就確定這個支付過程是安全的,同時 Configs 這個類也是支付寶給我們提供的,很到位的吧!

  >> 最后調用了Service方法進行我們的代碼邏輯,比如:更新數據庫中用戶的支付狀態,這個就是我們自己的業務需求了,在我們的業務代碼中主要是通過支付寶回調參數中的 tradeStatus 這個字段來判斷用戶是否支付成功,具體的tradeStatus的狀態,可以自行查看支付寶官方文檔。

 

當然,支付寶不止這一種支付方式,另外還有好多,可以自行去查看,官方文檔才是我們最好的老師。好了,到這里就先結束了,后續如果有不足的地方再另行修改。

 

        The End。。。。。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM