亚洲熟妇av一区二区三区,久久久久久精品观看sss,免费观看四虎精品国产永久,国产成人精品一区二三区熟女,天堂网在线最新版www资源网

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

一般來說支付功能都是通過公司申請賬戶進行開發(fā)、測試。如果是個人想了解學習一下,可以使用支付寶提供的沙箱功能,做一些基礎功能的學習和測試。我查了一圈沒發(fā)現(xiàn)微信支付有類似的功能,如果誰知道請說一下。

準備工作

登錄支付寶的開發(fā)者中心控制臺,如圖:

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

設置密鑰:

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

生成密鑰的工具,如圖:

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

下面還有安卓版的支付寶(沙箱版),配合使用。

代碼

接下來我們就可以著手進行開發(fā)。文檔很豐富,大家可以根據自己的情況選用,我這里就是用Spring Boot,采用老版的支付寶服務端SDK,簡單地實現(xiàn)一下當面付、PC網頁掃碼付、支付寶回調通知接口(無外網環(huán)境下如何測試)、基于AOP的驗簽、基于hibernate-validator的參數(shù)校驗以及全局異常捕獲。因為只是學習功能,所以代碼寫的不是那么規(guī)整,而且不涉及數(shù)據庫。

之前在工作中,支付作為一個基礎服務,是用Dubbo服務提供對外接口的,隨著后續(xù)的發(fā)展,出現(xiàn)了一系列的問題:

  1. 一開始是沒有消息驗簽的。
  2. 回調業(yè)務系統(tǒng)的接口是HTTP,因為支付服務不可能也用Dubbo Service的方式調業(yè)務系統(tǒng)的接口,這得引入多少業(yè)務系統(tǒng)的jar啊。這樣一來,就很別扭了,你調我接口Dubbo Service,我調你接口HTTP請求。
  3. 業(yè)務系統(tǒng)都要引入支付服務的jar包。

所以后來改成HTTP服務了。

先看一下結構圖:

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

pom.xml引入jar包:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java --> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.10.145.ALL</version> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator --> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

在實際工作中,微服務的背景下,要分配給每個業(yè)務系統(tǒng)一個clientId、一個驗證消息簽名用的密鑰,還有支付渠道ID——支付寶當面付、掃碼支付、APP支付,微信小程序支付等等,不同的channelId對應不同的處理策略。

我們在工作中支付流程是:

1、用戶發(fā)起支付,業(yè)務系統(tǒng)搜集訂單信息,通過HTTP請求至支付服務(這里可以是異步也可以是同步)。

2、支付服務對消息進行驗簽,校驗參數(shù)合法性,入庫,轉發(fā)請求至支付寶或微信(這里可以是異步也可以是同步)。

3、如果是同步,則直接將返回的信息回傳給業(yè)務系統(tǒng),例如PC網頁掃碼支付,支付寶就返回了<form>標簽的HTML代碼,業(yè)務系統(tǒng)的前端頁面要嵌進去。如果是當面付這種,其實是可以走異步的,按需處理吧。

4、用戶完成支付后,支付寶會有兩個回調return_url、notify_url告知支付狀態(tài),支付服務接到結果后再回調業(yè)務系統(tǒng)的接口,回寫支付狀態(tài)。

驗簽的AOP代碼:

@Aspect@Componentpublic class SignAspect { @Pointcut("execution(* org.leo.demo.controller..PayController.*(..))") public void executionPay() { } @Around("executionPay()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { ServletrequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); // 獲取請求頭 Enumeration<String> enumeration = request.getHeaderNames(); Map<String, String> headerMap = Maps.newHashMap(); while (enumeration.hasMoreElements()) { String name = enumeration.nextElement(); String value = request.getHeader(name); headerMap.put(name, value); } Gson gson = new Gson(); String json = gson.toJson(pjp.getArgs()[0]); String signFromHeader = headerMap.get("sign"); // 每個客戶端的salt應該是不同的,此處可以根據clientId去DB或Redis中取,順便也校驗一下clientId是否存在 String signFronEncrypt = SignUtils.encrypt(json, SignUtils.salt); System.out.print("n請求明文:" json); System.out.print("n請求簽名:" signFromHeader); System.out.println("n加密簽名:" signFronEncrypt); if (signFromHeader.equals(signFronEncrypt)) { Object result = pjp.proceed(); return result; } else { DefaultResult<PayResult> result = new DefaultResult<PayResult>(); result.setCode(999); result.setMsg("驗簽錯誤"); return result; } }}

我在這里使用了AOP而非Filter,僅僅是因為我不喜歡Filter獲取消息體時,inputStream無法傳入Controller,還要額外處理一下。公司里面倒是用Filter?的多,不知道大家在實際工作中是如何處理的。?

消息加密方法:

/** * 真實環(huán)境中,鹽應該是一個客戶端分配一個 */ public static final String salt = "111111"; public static String encrypt(String data, String salt) { // 因為只是驗簽,沒必要解密。MD5已經被廢棄 return Hashing.sha256().newHasher().putString(data salt, Charsets.UTF_8).hash().toString(); }

消息加密的方法有很多種,而且每個客戶分配的密鑰也必須不同,我這里只是為了展示一下功能,采用了Guava的Hashing.sha256方法,實際工作中要按照要求進行修改。

HTTP請求除了放在body的json消息體外,還要在header上放入固定的sign,postman請求如圖:

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

下面是PayController:

@RestController@Validatedpublic class PayController { @Autowired private AliPayFace2FaceService aliPayFace2FaceService; @Autowired private AliPayScan2PayService aliPayScan2PayService; @PostMapping("/pay.do") @ResponseBody public DefaultResult<PayResult> pay(@Valid @RequestBody PayParam param) { System.out.println("Controller:" param.toString()); if (param.getChannelId() == 1) { return aliPayFace2FaceService.pay(param); } else if (param.getChannelId() == 2) { return aliPayScan2PayService.pay(param); } else { DefaultResult<PayResult> result = new DefaultResult<PayResult>(); result.setCode(222); result.setMsg("支付渠道錯誤"); return result; } } @RequestMapping("/alipaynotify.do") @ResponseBody public String alipayNotify(HttpServletRequest req) throws AlipayApiException { Map<String, String> params = new HashMap<String, String>(); Map<String, String[]> requestParams = req.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] ","; } params.put(name, valueStr); } boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); if (signVerified) { System.out.println("支付寶回調驗簽通過"); System.out.println(params.toString()); return "success"; } else { System.out.println("支付寶回調驗簽失敗"); return "error"; } }}

alipayNotify這個方法我們后面再說。支付渠道應該有個枚舉類,這里省略了。

Controller里的pay方法有點像策略模式,根據不同的支付渠道ID,使用對應的處理Service。只是我們這里省下了Context類。

對參數(shù)的校驗,這里放在Controller了,大家可以按照公司的開發(fā)規(guī)范,放在Service也可以。處理類代碼如下:

@ControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseBody public DefaultResult<List<Map<String, String>>> validParam(MethodArgumentNotValidException e) { // 按需重新封裝需要返回的錯誤信息 List<Map<String, String>> errorMsgs = Lists.newArrayList(); // 解析原錯誤信息,封裝后返回,此處返回非法的字段名稱,原始值,錯誤信息 for (FieldError error : e.getBindingResult().getFieldErrors()) { Map<String, String> errorMap = Maps.newLinkedHashMap(); errorMap.put("字段", error.getField()); errorMap.put("消息", error.getDefaultMessage()); errorMap.put("傳入值", error.getRejectedValue().toString()); errorMsgs.add(errorMap); } DefaultResult<List<Map<String, String>>> result = new DefaultResult<List<Map<String, String>>>(); result.setCode(444); result.setMsg("參數(shù)校驗錯誤"); result.setData(errorMsgs); return result; }}

IPayService這個沒什么好說的,就是通用的方法,本例就寫了一個支付接口。

下面是當面付的Service,因為是學習測試用,所以省略了訂單入庫的代碼:

@Servicepublic class AliPayFace2FaceService implements IPayService { @Override public DefaultResult<PayResult> pay(PayParam param) { System.out.println("Service:" param.toString()); // 應從相關配置和數(shù)據庫拿這些參數(shù) AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type); AlipayTradePayRequest request = new AlipayTradePayRequest(); AlipayTradePayModel model = new AlipayTradePayModel(); request.setBizModel(model); model.setOutTradeNo(param.getOrderNo()); model.setSubject(param.getSubject()); // 計算金額應有專門的工具類實現(xiàn) BigDecimal b1 = new BigDecimal(param.getTotalAmount()); BigDecimal b2 = new BigDecimal(100); BigDecimal bdResult = b1.divide(b2, 2, RoundingMode.DOWN); model.setTotalAmount(bdResult.toString()); model.setAuthCode(param.getAuthCode());// 沙箱錢包中的付款碼 model.setScene("bar_code"); AlipayTradePayResponse response = null; try { response = alipayClient.execute(request); System.out.println(response.getBody()); System.out.println(response.getTradeNo()); DefaultResult<PayResult> result = new DefaultResult<PayResult>(); PayResult payResult = new PayResult(); if (response.getCode().equals("10000")) { payResult.setPayOrderNo(response.getTradeNo()); } else { result.setCode(Integer.valueOf(response.getCode())); result.setMsg(response.getMsg() "。" response.getSubMsg()); } return result; } catch (AlipayApiException e) { DefaultResult<PayResult> result = new DefaultResult<PayResult>(); result.setCode(753); result.setMsg("支付寶異常"); result.setData(null); return result; } }}

其中AlipayConfig里面就是之前我們在支付寶上配置的公鑰、私鑰、網關地址、APPID、還有我們的回調接口,這些信息應該從配置文件或者DB里面獲取。

支付寶接收的金額是元,小數(shù)點后兩位,也就是只到分了。而我們在工作中,實際上金額存的都是long型,沒有小數(shù)點,直接到分,這里要寫一個專門的工具類處理一下,本例省略了。

付款碼就是沙箱支付寶APP中,“付款”-“查看數(shù)字”。測試的時候要快,因為這段數(shù)字會變。

下面是PC網頁掃碼付的Service:

@Servicepublic class AliPayScan2PayService implements IPayService { @Override public DefaultResult<PayResult> pay(PayParam param) { System.out.println("Service:" param.toString()); 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); // 付款金額 BigDecimal b1 = new BigDecimal(param.getTotalAmount()); BigDecimal b2 = new BigDecimal(100); BigDecimal bdResult = b1.divide(b2, 2, RoundingMode.DOWN); // 最晚付款時間。m分鐘,h小時,d天,1c-當天(0點關閉)。 String timeoutExpress = "15m"; String body = ""; DefaultResult<PayResult> result = new DefaultResult<PayResult>(); PayResult payResult = new PayResult(); alipayRequest.setBizContent("{"out_trade_no":"" param.getOrderNo() ""," ""total_amount":"" bdResult ""," ""subject":"" param.getSubject() ""," ""body":"" body ""," ""timeout_express":"" timeoutExpress ""," ""product_code":"FAST_INSTANT_TRADE_PAY"}"); try { String htmlStr = alipayClient.pageExecute(alipayRequest).getBody(); System.out.println(htmlStr); payResult.setHtmlStr(htmlStr); } catch (AlipayApiException e) { result.setCode(753); result.setMsg("支付寶異常"); result.setData(null); } return result; }}

這里返回的是一段HTML代碼(微信支付返回的是一個二維碼),我們拿到之后,可以新建一個HTML文件放進去,直接掃碼支付。

個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能(個人開發(fā)者如何使用支付寶沙箱開發(fā)支付功能)

掃碼支付后,支付寶會回調我們提供的return_url和notify_url接口,告知結果,但是作為個人開發(fā),如果沒有外網IP,該如何驗證一下呢?

先說一下return_url和notify_url的區(qū)別。

掃碼支付完成后,支付網頁會同步跳轉到return_url,展示一些信息,這是個get請求,僅發(fā)一次。

而notify_url是由支付寶后端發(fā)起的post請求,如果我們不返回success消息,支付寶會進行重發(fā)。

所以從工作中來說,我們一般是把return_url做一個臨時展示,而在notify_url中進行一些邏輯處理,例如回寫訂單狀態(tài)等操作。

在測試開發(fā)中,因為我們沒有外網IP,網頁會彈出一個信息框,告知地址無法訪問,這時候我們就可以把整個url復制下來,自己在瀏覽器上訪問一下,從而可以驗證我們的回調接口是否正常。

版權聲明:本文內容由互聯(lián)網用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權/違法違規(guī)的內容, 請發(fā)送郵件至 舉報,一經查實,本站將立刻刪除。