您好,登錄后才能下訂單哦!
最近項目APP需要接入微信、支付寶支付功能,在分配開發任務時,聽說微信支付接口比支付寶支付接口要難實現,由于我開發經驗不是那么豐富(現工作經驗1年半)且未接觸過支付接口開發,組里剛好又有支付接口的老司機,所以很自然把簡單的支付寶接口開發任務交給了我,看來開發組的組長還是很好人的嘛.....,廢話就不多說了,我們開始吧!
實現支付寶接口詳細過程
1.去支付寶官網申請公司企業賬號并開通一個應用,在應用里簽約APP支付功能
具體的申請截圖步驟,在這里我就不詳細說了,因為這不是文章的重點,可參考支付寶官網。
經過這一步,我們可以得過開發中需要用到的幾個參數
①商戶appid ②商戶公鑰、私鑰 ③支付寶公鑰 ④支付寶網關地址
解釋一下這幾個參數:
1.商戶appid是識別商戶的唯一ID,是讓支付寶識別,我們到底是哪一個商戶,這樣支付寶就能識別商戶對應的賬號、用戶號、收款賬號...等等一系列信息。
2.商戶公鑰、私鑰以及支付寶公鑰這3個參數是對商戶系統與支付寶進行信息交互的數字簽名用的,相信各位大學里也有學過關于數字簽名的一些知識,在這里,我就簡單說一下我理解的過程:首先是商戶系統需要給支付寶發送信息(支付、查詢等等....),涉及錢方面,咱們當前要謹慎一點對吧,所以我們需要對發送之前的信息加把鎖(用商戶私鑰進行簽名),然后再發送給支付寶。支付寶收到商戶發送的信息之后,發現上了把鎖,那肯定得要一把鑰匙(商戶公鑰)來解鎖對吧,所以商戶在跟支付寶簽約APP支付功能的時候,就得把這把鑰匙上傳給支付寶了,支付寶就可以用商戶的公鑰進行解鎖了。反過來也是一樣,支付寶需要發送信息給商戶信息,先用支付寶的私鑰進行簽名,再發送給商戶系統,商戶系統收到支付寶反饋過來的信息后,再用支付寶的公鑰進行解密。在這里我們并沒有用到支付寶的私鑰,所以我們并不需要得到支付寶的私鑰。這里放一個生成私鑰公鑰的支付寶官方工具
3.支付寶網關地址,是用來配置發送給支付寶的網關地址的。
2.將支付寶的SDK集成到項目系統里
支付寶的SDK指的就是支付寶提供的工具Jar包給我們開發者,SDK封裝了大量的基礎功能,使我們可以快速開發支付寶接口。這也是我在前面說的比微信支付接口更容易實現的原因。獲取支付寶SDK地址:支付寶SDK下載地址,這里我選擇JAVA的SDK。下載解壓后的SDK得到:
alipay-sdk-java20180122110032.jar、commons-logging-1.1.1.jar是我們需要導入到項目里的,因為項目后臺的大致的架構是maven+springBoot+jpa,所以我們需要從maven里導入jar包,首先是導入commons-logging-1.1.1.jar,不用多說,咱直接在pom.xml里加上:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency>
然后是alipay-sdk-java20180122110032.jar,如果你也像上面一樣直接加入到pom.xml,會發現,咦,怎么一直下載不下來。當然alipy的包在線上的maven倉庫并沒有,所以我們需要導入到本地的maven倉庫。前提是配置好maven的環境變量,將包放在G:\alipay\sdk下,然后打開dos窗口,cd進入到G:\alipay\sdk下,執行maven如下命令:
mvn install:install-file -DgroupId=com.alipay -DartifactId=sdk-java -Dversion=20180122110032 -Dpackaging=jar -Dfile=alipay-sdk-java20180122110032.jar
導入成功后,在項目的pom.xml里繼續添加
<!-- 支付寶SDK --> <dependency> <groupId>com.alipay</groupId> <artifactId>sdk-java</artifactId> <version>20180122110032</version> </dependency>
到此,我們就順利把支付寶SDK集成到項目里,是不是有點小興奮,我們很快可以開發了!
3.看支付寶提供的API和網上各位牛人總結的經驗,后臺使用支付寶的SDK與支付寶進行交互
先看一下支付寶支付流程的圖:
首先,我們來理一理開發的思路,按照我當前項目的需求,關于支付這一塊大概操作流程是:用戶在APP上選好要購買的商品,點擊“立即購買”,跳轉到訂單詳細頁面。選擇支付方式,點擊“確定支付”跳轉到支付寶APP,付款完成后,跳轉回APP,完成支付。這個過程,當用戶點擊“確定支付”時,APP需要調用商戶后臺接口。
這時候就是我們所需要做的事情:先是生成商戶系統一筆未支付的訂單,獲得商戶訂單ID(商戶系統生成)和訂單的一些其他信息,然后再調用支付寶的SDK提供的數字簽名方法,將需要傳給支付寶的信息進行加簽,然后把加簽后的字符串返回給APP。APP拉起支付寶APP,再把這個加簽的字符串傳給支付寶,完成支付。APP接收到同步通知后,還需要再次調用商戶后臺的接口(雖然同步通知也有付款情況,但需要以后臺通知為準),校驗訂單最終的付款情況。按照支付寶API上所說,當完成支付后,支付寶會做2個操作,一個是同步返回信息給APP,一個是異步通知商戶后臺返回支付狀態等信息,并且最終的支付結果是以異步通知為準。所以我們還需要考慮到一點,就是當用戶支付成功之后,商戶系統暫時沒有接收到支付寶的異步通知時。我們需要拿著這個商戶訂單ID主動調用SDK支付寶的查詢接口,去獲取該訂單的支付情況,并最終返回給APP。這個查詢的接口應該是給APP收到同步通知后,請求商戶系統后臺進行校驗的時候調用的。
根據我們上面思考所得,后臺只需要對外提供3個接口即可
1.用戶點擊“立即購買”時調用商戶后臺接口,后臺返回加簽后的訂單信息字符串
2.在支付完成之后,支付寶異步通知商戶后臺訂單的付款情況
3.在支付完成之后,跳轉回APP時,APP調用商戶后臺進行最終付款校驗
想通想明白之后,終于接一下我們要敲代碼了,哈哈哈哈
首先,我們來準備一下需要傳給支付寶SDK的公共基本參數,我把參數放到一個單獨的類里,你也可以放到數據庫里,代碼如下:
public class AlipayConfig { // 1.商戶appid //public static String APPID = "2017..."; //2.私鑰 pkcs8格式的 public static String RSA_PRIVATE_KEY ="MIIEwAIBADANBg....."; // 3.支付寶公鑰 public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkq....."; // 4.服務器異步通知頁面路徑 需http://或者https://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 public static String notify_url = "http://www.xxx.com/alipay/notify_url.do"; //5.頁面跳轉同步通知頁面路徑 需http://或者https://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 商戶可以自定義同步跳轉地址 public static String return_url = "http://www.xxx.com/alipay/return_url.do"; // 6.請求支付寶的網關地址 public static String URL = "https://openapi.alipay.com/gateway.do"; // 7.編碼 public static String CHARSET = "UTF-8"; // 8.返回格式 public static String FORMAT = "json"; // 9.加密類型 public static String SIGNTYPE = "RSA2"; }
1.實現第一個接口:用戶點擊“立即購買”時調用商戶后臺接口,后臺返回加簽后的訂單信息字符串。我把主要的處理邏輯寫在Service層了,Controller層直接調用就可以,這里就不放Controller層的代碼了
生成商戶訂單的代碼,我就不放了,這個根據各自的業務需求來做,生成后訂單之后,把訂單信息傳進來該方法進行處理,返回加簽后的字符串,直接返回給APP即可,代碼如下:
/** * 獲取支付寶加簽后臺的訂單信息字符串 * * @param request * @return */ @Override @Transactional(propagation = Propagation.REQUIRED) public String getAliPayOrderStr(OrderTest orderTest) { //最終返回加簽之后的,app需要傳給支付寶app的訂單信息字符串 String orderString = ""; logger.info("==================支付寶下單,商戶訂單號為:"+orderTest.getOutTradeNo()); //創建商戶支付寶訂單(因為需要記錄每次支付寶支付的記錄信息,單獨存一個表跟商戶訂單表關聯,以便以后查證) AlipaymentOrder alipaymentOrder=new AlipaymentOrder(); alipaymentOrder.setClubOrderId(orderTest.getId().toString());//商家訂單主鍵 alipaymentOrder.setOutTradeNo(orderTest.getOutTradeNo());//商戶訂單號 alipaymentOrder.setTradeStatus((byte) 0);//交易狀態 alipaymentOrder.setTotalAmount(Double.parseDouble(orderTest.getTotalAmount()));//訂單金額 alipaymentOrder.setReceiptAmount(0.00);//實收金額 alipaymentOrder.setInvoiceAmount(0.00);//開票金額 alipaymentOrder.setBuyerPayAmount(0.00);//付款金額 alipaymentOrder.setRefundFee(0.00); //總退款金額 try{ //實例化客戶端(參數:網關地址、商戶appid、商戶私鑰、格式、編碼、支付寶公鑰、加密類型),為了取得預付訂單信息 AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID, AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE); //實例化具體API對應的request類,類名稱和接口名稱對應,當前調用接口名稱:alipay.trade.app.pay AlipayTradeAppPayRequest ali_request = new AlipayTradeAppPayRequest(); //SDK已經封裝掉了公共參數,這里只需要傳入業務參數。以下方法為sdk的model入參方式 AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); //業務參數傳入,可以傳很多,參考API //model.setPassbackParams(URLEncoder.encode(request.getBody().toString())); //公用參數(附加數據) model.setBody(orderTest.getBody()); //對一筆交易的具體描述信息。如果是多種商品,請將商品描述字符串累加傳給body。 model.setSubject(orderTest.getSubjecy()); //商品名稱 model.setOutTradeNo(orderTest.getOutTradeNo()); //商戶訂單號(自動生成) // model.setTimeoutExpress("30m"); //交易超時時間 model.setTotalAmount(orderTest.getTotalAmount()); //支付金額 model.setProductCode("QUICK_MSECURITY_PAY"); //銷售產品碼(固定值) ali_request.setBizModel(model); logger.info("====================異步通知的地址為:"+alipayment.getNotifyUrl()); ali_request.setNotifyUrl(AlipayConfig.notify_url); //異步回調地址(后臺) ali_request.setReturnUrl(AlipayConfig.return_url); //同步回調地址(APP) // 這里和普通的接口調用不同,使用的是sdkExecute AlipayTradeAppPayResponse alipayTradeAppPayResponse = alipayClient.sdkExecute(ali_request); //返回支付寶訂單信息(預處理) orderString=alipayTradeAppPayResponse.getBody();//就是orderString 可以直接給APP請求,無需再做處理。 this.createAlipayMentOrder(alipaymentOrder);//創建新的商戶支付寶訂單 } catch (AlipayApiException e) { e.printStackTrace(); logger.info("與支付寶交互出錯,未能生成訂單,請檢查代碼!"); } return orderString; }
2.實現第二個接口:在支付完成之后,支付寶異步通知商戶后臺訂單的付款情況,這個是支付寶每隔一段時間來訪問一次的接口,直到你返回success,才會停止訪問,這里我分了2個地方進行調用
/** * 支付寶支付成功后.異步請求該接口 * @param request * @return * @throws IOException */ @RequestMapping(value="/notify_url",method=RequestMethod.POST) @ResponseBody public String notify(HttpServletRequest request,HttpServletResponse response) throws IOException { logger.info("==================支付寶異步返回支付結果開始"); //1.從支付寶回調的request域中取值 //獲取支付寶返回的參數集合 Map<String, String[]> aliParams = request.getParameterMap(); //用以存放轉化后的參數集合 Map<String, String> conversionParams = new HashMap<String, String>(); for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext();) { String key = iter.next(); String[] values = aliParams.get(key); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } // 亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化 // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8"); conversionParams.put(key, valueStr); } logger.info("==================返回參數集合:"+conversionParams); String status=alipayMentOrderService.notify(conversionParams); return status; }
/** * 支付寶異步請求邏輯處理 * @param request * @return * @throws IOException */ public String notify(Map<String, String> conversionParams){ logger.info("==================支付寶異步請求邏輯處理"); //簽名驗證(對支付寶返回的數據驗證,確定是支付寶返回的) boolean signVerified = false; try { //調用SDK驗證簽名 signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE); } catch (AlipayApiException e) { logger.info("==================驗簽失敗 !"); e.printStackTrace(); } //對驗簽進行處理 if (signVerified) { //驗簽通過 //獲取需要保存的數據 String appId=conversionParams.get("app_id");//支付寶分配給開發者的應用Id String notifyTime=conversionParams.get("notify_time");//通知時間:yyyy-MM-dd HH:mm:ss String gmtCreate=conversionParams.get("gmt_create");//交易創建時間:yyyy-MM-dd HH:mm:ss String gmtPayment=conversionParams.get("gmt_payment");//交易付款時間 String gmtRefund=conversionParams.get("gmt_refund");//交易退款時間 String gmtClose=conversionParams.get("gmt_close");//交易結束時間 String tradeNo=conversionParams.get("trade_no");//支付寶的交易號 String outTradeNo = conversionParams.get("out_trade_no");//獲取商戶之前傳給支付寶的訂單號(商戶系統的唯一訂單號) String outBizNo=conversionParams.get("out_biz_no");//商戶業務號(商戶業務ID,主要是退款通知中返回退款申請的流水號) String buyerLogonId=conversionParams.get("buyer_logon_id");//買家支付寶賬號 String sellerId=conversionParams.get("seller_id");//賣家支付寶用戶號 String sellerEmail=conversionParams.get("seller_email");//賣家支付寶賬號 String totalAmount=conversionParams.get("total_amount");//訂單金額:本次交易支付的訂單金額,單位為人民幣(元) String receiptAmount=conversionParams.get("receipt_amount");//實收金額:商家在交易中實際收到的款項,單位為元 String invoiceAmount=conversionParams.get("invoice_amount");//開票金額:用戶在交易中支付的可開發票的金額 String buyerPayAmount=conversionParams.get("buyer_pay_amount");//付款金額:用戶在交易中支付的金額 String tradeStatus = conversionParams.get("trade_status");// 獲取交易狀態 //支付寶官方建議校驗的值(out_trade_no、total_amount、sellerId、app_id) AlipaymentOrder alipaymentOrder=this.selectByOutTradeNo(outTradeNo); if(alipaymentOrder!=null&&totalAmount.equals(alipaymentOrder.getTotalAmount().toString())&&AlipayConfig.APPID.equals(appId)){ //修改數據庫支付寶訂單表(因為要保存每次支付寶返回的信息到數據庫里,以便以后查證) alipaymentOrder.setNotifyTime(dateFormat(notifyTime)); alipaymentOrder.setGmtCreate(dateFormat(gmtCreate)); alipaymentOrder.setGmtPayment(dateFormat(gmtPayment)); alipaymentOrder.setGmtRefund(dateFormat(gmtRefund)); alipaymentOrder.setGmtClose(dateFormat(gmtClose)); alipaymentOrder.setTradeNo(tradeNo); alipaymentOrder.setOutBizNo(outBizNo); alipaymentOrder.setBuyerLogonId(buyerLogonId); alipaymentOrder.setSellerId(sellerId); alipaymentOrder.setSellerEmail(sellerEmail); alipaymentOrder.setTotalAmount(Double.parseDouble(totalAmount)); alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount)); alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount)); alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount)); switch (tradeStatus) // 判斷交易結果 { case "TRADE_FINISHED": // 交易結束并不可退款 alipaymentOrder.setTradeStatus((byte) 3); break; case "TRADE_SUCCESS": // 交易支付成功 alipaymentOrder.setTradeStatus((byte) 2); break; case "TRADE_CLOSED": // 未付款交易超時關閉或支付完成后全額退款 alipaymentOrder.setTradeStatus((byte) 1); break; case "WAIT_BUYER_PAY": // 交易創建并等待買家付款 alipaymentOrder.setTradeStatus((byte) 0); break; default: break; } int returnResult=this.updateByPrimaryKey(alipaymentOrder); //更新交易表中狀態 if(tradeStatus.equals("TRADE_SUCCESS")) { //只處理支付成功的訂單: 修改交易表狀態,支付成功 if(returnResult>0){ return "success"; }else{ return "fail"; } }else{ return "fail"; } }else{ logger.info("==================支付寶官方建議校驗的值(out_trade_no、total_amount、sellerId、app_id),不一致!返回fail"); return"fail"; } } else { //驗簽不通過 logger.info("==================驗簽不通過 !"); return "fail"; } }
3.實現第三個接口:在支付完成之后,跳轉回APP時,APP調用商戶后臺進行最終付款校驗。我把主要的處理邏輯寫在Service層了,Controller層直接調用就可以,這里就不放Controller層的代碼了。
/** * 向支付寶發起訂單查詢請求 * @param request * @return * @throws IOException */ @Override public Byte checkAlipay(String outTradeNo) { logger.info("==================向支付寶發起查詢,查詢商戶訂單號為:"+outTradeNo); try { //實例化客戶端(參數:網關地址、商戶appid、商戶私鑰、格式、編碼、支付寶公鑰、加密類型) AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID, AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE); AlipayTradeQueryRequest alipayTradeQueryRequest = new AlipayTradeQueryRequest(); alipayTradeQueryRequest.setBizContent("{" + "\"out_trade_no\":\""+outTradeNo+"\"" + "}"); AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(alipayTradeQueryRequest); if(alipayTradeQueryResponse.isSuccess()){ AlipaymentOrder alipaymentOrder=this.selectByOutTradeNo(outTradeNo); //修改數據庫支付寶訂單表 alipaymentOrder.setTradeNo(alipayTradeQueryResponse.getTradeNo()); alipaymentOrder.setBuyerLogonId(alipayTradeQueryResponse.getBuyerLogonId()); alipaymentOrder.setTotalAmount(Double.parseDouble(alipayTradeQueryResponse.getTotalAmount())); alipaymentOrder.setReceiptAmount(Double.parseDouble(alipayTradeQueryResponse.getReceiptAmount())); alipaymentOrder.setInvoiceAmount(Double.parseDouble(alipayTradeQueryResponse.getInvoiceAmount())); alipaymentOrder.setBuyerPayAmount(Double.parseDouble(alipayTradeQueryResponse.getBuyerPayAmount())); switch (alipayTradeQueryResponse.getTradeStatus()) // 判斷交易結果 { case "TRADE_FINISHED": // 交易結束并不可退款 alipaymentOrder.setTradeStatus((byte) 3); break; case "TRADE_SUCCESS": // 交易支付成功 alipaymentOrder.setTradeStatus((byte) 2); break; case "TRADE_CLOSED": // 未付款交易超時關閉或支付完成后全額退款 alipaymentOrder.setTradeStatus((byte) 1); break; case "WAIT_BUYER_PAY": // 交易創建并等待買家付款 alipaymentOrder.setTradeStatus((byte) 0); break; default: break; } this.updateByPrimaryKey(alipaymentOrder); //更新表記錄 return alipaymentOrder.getTradeStatus(); } else { logger.info("==================調用支付寶查詢接口失敗!"); } } catch (AlipayApiException e) { // TODO Auto-generated catch block e.printStackTrace(); } return 0; }
至此,代碼已經上完了,里面可能涉及部分業務代碼,如果各位需要拿代碼,需要把業務代碼換成自己所需要的。
建議:可以邊看API邊進行開發,主要是看我們需要給支付傳什么參數,支付寶可以給我們傳什么參數,不然沒看清除,你會多很多坑要踩的,親試過。
感覺太少圖片了,這里給幾張API的圖片。。你們也可以自己去看 一些支付寶API
4.關于測試的一些事
支付寶有提供沙箱環境進行測試所使用,見支付寶沙箱調試指南。
但是樓主比較有米,直接用真實環境進行測試,其實測一次0.01元也是挺貴的吧,前提是你的電腦必須訪問外網和能夠被外網所訪問,建議可以找個內網穿透的工具。
寫了一整個下午,好累啊,本人新手,如有錯誤,請各位大神指教,希望對大家有用!!!!也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。