Java后台支付宝支付接入及详解

上一篇讲了微信支付的三种支付方式(扫码支付、App支付、小程序支付)的接入,今天继续说说以电商项目为代表的Java后端支付宝支付接入及实现。

image

支付宝支付接入实现在接入前,我们先看看支付宝支付的官方文档:https://open.alipay.com/developmentDocument.htm

image

本次我主要讲的是“App支付”及“电脑网站支付(扫码支付)”的接入实现。一,准备工作需要有一个支付宝账号,然后登录进入支付宝开放平台,如果之前没有申请入驻过开放平台,第一次进入后需要填相关资料申请,再进入开发者中心,选择创建移动应用。

image

在选择“支付接入”后,填写对应的资料并添加相应的功能。等申请的应用通过后,再配置相应的开发者设置选项。包含回调地址、接口加密方式及配置密钥。

image

密钥配置可参考官方文档:https://docs.open.alipay.com/291/106103/

二,业务流程及原理

扫码支付业务流程图:

image

App支付业务流程图:

image

支付宝支付(扫码支付和App支付)业务流程大致总结如下: 1,客户在商户平台下单,生成订单信息;2,调用商户平台的支付接口,商户后台封装参数通过SDK发起支付请求;3,扫码支付方式支付宝后台返回一个html片段(form表单代码),输出到tml页面,展示付款二维码;App支付方式时返回App端发起付款请求的参数,后台再传给App,App端通过请求SDK调起支付宝支付服务;4,扫码支付方式时,客户可用手机的支付宝直接扫码支付或点击右边的“登录账户付款”,登录支付宝支付;app支付时在上一步已经调起了付款服务,只需输入密码付款。5,付款成功后,会跳转到支付宝支付成功的页面,3s跳转到商户的支付成功展示的页面;同时支付宝后台通过异步线程调用回调接口,更新订单付款记录的状态。注意:回调接口只要成功要给支付宝返回“success”,否则支付宝后台认为调用不成功,会多次重复调用。三,编码接入实现

首先,我们需要到支付宝开放平台下载Java版的SDK,然后可以手动将下载到本地的jar导入到maven库,然后在pom文件里配置,依赖如下:

<!-- 支付宝SDK -->
<dependency>
     <groupId>com.alipay</groupId>
     <artifactId>sdk-java</artifactId>
     <version>${alipay-sdk-java.version}</version>
</dependency>

注:我这里的版本号是 20180104135026

使用SDK很是方便,它里面已经封装了签名&验签、HTTP 接口请求等基础功能。只需要我们根据SDK提供的方法传参使用。

AlipayClient alipayClient = new DefaultAlipayClient(URL,APP_ID,APP_PRIVATE_KEY,FORMAT,CHARSET,ALIPAY_PUBLIC_KEY,SIGN_TYPE);

Service接口:

public interface PaymentService {
      /**
       * <p>支付宝扫码支付</p>
       *
       * @param orderNo
       * @param money
       * @return
       * @throws Exception
       */
    Map<String, String> aliQrPayment(String orderNo, double money) throws Exception;
    /**
       * <p>支付宝App支付</p>
       *
       * @param orderNo
       * @param money
       * @return
       * @throws Exception
       */
      Map<String,String> aliAppPayment(String orderNo, double money) throws Exception;
      /**
       * <p>支付宝回调服务</p>
       *
       * @param orderId
       * @param tradeNo
       * @return
       * @throws Exception
       */
      int aliNotify(String orderNo,String tradeNo) throws Exception;
}

Service实现类代码(部分):

@Service(value = "paymentService")
public class PaymentServiceImpl implements PaymentService {
      private static Logger LOGGER = LoggerFactory.getLogger(PaymentServiceImpl.class);
      
      @Autowired
      private PaymentRecordMapper paymentRecordMapper;
  
      @Override
        public Map<String, String> aliQrPayment(String orderNo, double money) throws Exception {
          LOGGER.info("【支付宝扫码支付】 订单编号="+orderId+",支付金额="+money);
          double payAmount = PayUtil.getPayAmountByEnv(PROJECT_ENV,money);
          Map<String,String> retMap = new HashMap<String, String>();
          //添加或更新支付记录到数据库
          int results = this.addOrUpdatePaymentRecord(orderNo, payAmount, PayConstant.PAY_METHOD_ALI, PayConstant.PAY_TRADE_TYPE_QR, false, null,null);
          if(results < 0){
            retMap.put("returnCode", "FAIL");
            retMap.put("returnMsg", "此订单已支付!");
            LOGGER.info("【支付宝扫码支付】  此订单已支付!");
          }else if(results == 0){
            retMap.put("returnCode", "FAIL");
            retMap.put("returnMsg", "支付记录生成或更新失败!");
            LOGGER.info("【支付宝扫码支付】 支付记录生成或更新失败!");
          }else{
            //扫码支付
            String param = PayConstant.ALI_PAY_WEB_PARAMS;
            //返回页面
            String returnUrl = "http://" + PayConfig.PC_SHOP_DOMAIN + PayConstant.ALI_PAY_RETURN_URL+orderNo;
            //调用SDK获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", PayConfig.ALI_APP_ID, PayConfig.ALI_PAY_PRIVATE_KEY, "json", "UTF-8", PayConfig.ALI_PAY_PUBLIC_KEY, "RSA"); 
              AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();//创建API对应的request
              alipayRequest.setReturnUrl(returnUrl);
              alipayRequest.setNotifyUrl(this.getNotifyUrl(PayConstant.PAY_TYPE_ALI));
              alipayRequest.setBizContent("{" +
                  "\"out_trade_no\":\""+ orderId +"\"," +
                  "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"," +
                  "\"total_amount\":\""+payAmount+"\"," +
                  "\"subject\":\""+param+"\"," +
                  "\"body\":\""+param+"\"" +
                  "}");
              String form = alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单
            //封装返回实体
            retMap.put("returnCode", PayConstant.SUCCESS);
            retMap.put("returnMsg", PayConstant.OK);
            retMap.put("body", form);
          }
          return retMap;
        }
      @Override
      @Transactional(readOnly=false,rollbackFor={Exception.class})
      public Map<String,String> aliAppPayment(String orderNo, double money) throws Exception{
          LOGGER.info("【支付宝App支付】开始下单, 订单编号="+orderId);
          Map<String,String> resultsMap = new HashMap<String,String>();
          //根据不同服务环境生成支付金额
          double payAmount = PayUtil.getPayAmountByEnv(PROJECT_ENV, money);
          //添加或更新支付记录
          int results = this.addOrUpdatePaymentRecord(.....);
          if(flag < 0){
            resultsMap.put("returnCode", "FAIL");
            resultsMap.put("returnMsg", "此订单已支付!");
            LOGGER.info("【支付宝App支付】 此订单已支付!");
          }else if(flag == 0){
            resultsMap.put("returnCode", "FAIL");
            resultsMap.put("returnMsg", "支付记录生成或更新失败!");
            LOGGER.info("【支付宝App支付】 支付记录生成或更新失败!");
          }else{
            //获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", 
            PayConfig.ALI_APP_ID, PayConfig.ALI_PAY_PRIVATE_KEY, "json", "UTF-8", PayConfig.ALI_PAY_PUBLIC_KEY, "RSA"); 
            //实例化具体API对应的request类
            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
            //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
            model.setBody(BaseConstants.PLATFORM_COMPANY_NICKAME);
            model.setSubject(BaseConstants.PLATFORM_COMPANY_PREFIX+orderNo);
            model.setOutTradeNo(orderNo);
            model.setTimeoutExpress("24h");
            model.setTotalAmount(String.valueOf(money));            
            model.setProductCode("QUICK_MSECURITY_PAY");
            request.setBizModel(model);
            request.setNotifyUrl(this.getNotifyUrl(PayConstant.PAY_TYPE_ALI));
            //这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            String orderString = response.getBody();
            resultsMap.put("payParams", orderString);
            resultsMap.put("returnCode", "SUCCESS");
            resultsMap.put("returnMsg", "OK");
          }
          return resultMap;
      }
      
      @Override
      @Transactional(readOnly=false,rollbackFor={Exception.class})
      public int aliNotify(String orderNo,String tradeNo) throws Exception{
          LOGGER.info("【支付宝支付回调】回调数据:"+"orderNo="+orderNo+",tradeNo="+tradeNo);
          //TODO 更新支付订单记录业务代码
          //此处添加自己的更新数据业务代码
          return 0;
      }
}

支付Controller接口层:

@RestController
@RequestMapping(value = "/api/payment/")
public class PaymentController {
    private static Logger logger = LoggerFactory.getLogger(PaymentController.class);
    
    @Value("${spring.profiles.active}")
    private String PROJECT_ENV;
    
    @Value("${error.page}")
    private String ERROR_PAGE;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private RedisCacheService cacheService;
    
  /**
   * 支付宝扫码支付接口
   * 
   * @param request
   * @return
   * @throws Exception
   */
  @ResponseBody
  @RequestMapping(value="aliQrPay", method=RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
  public JSONObject qrPay(HttpServletRequest request) throws Exception {
        String requestStr = RequestStr.getRequestStr(request);
        if (StringUtils.isEmpty(requestStr)) {
          throw new ParamException();
        }
         JSONObject jsonObj = JSONObject.parseObject(requestStr);
         if(StringUtils.isEmpty(jsonObj.getString("orderNo"))){
           throw new ParamException();
         }
         //验证订单是否存在
         String orderNo = jsonObj.getString("orderNo");
         JSONObject json = getPayOrder(orderNo);
         if(json == null){
           return AjaxUtil.renderFailMsg("订单不存在!");
         }else if(json.getDouble("payPrice") == null || json.getDouble("payPrice") < 0.01){
           return AjaxUtil.renderFailMsg("订单有误,请确认!");
         }else if(json.getInteger("orderStatus") != OrderConstant.PORDER_STATUS_YTJ){
           String msg = orderInfo.getpStatus() == OrderConstant.PORDER_STATUS_YFK?"此订单已支付!":"无效的订单,请确认!";
           return AjaxUtil.renderFailMsg(msg);
         }else{
             //支付宝扫码支付
             Map<String, String> resMap = paymentService.aliQrPayment(orderNo, json.getDouble("payPrice"));
             if(PayConstant.SUCCESS.equals(resMap.get("returnCode")) && PayConstant.OK.equals(resMap.get("returnMsg"))){
               String code = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
               cacheService.setCacheToRedis(code, resMap.get("body"), BusinessConstant.IMAGE_VERIFY_CODE_EXPIRE);
               logger.info("【Web扫码支付服务】支付宝支付下单成功!");
               return AjaxUtil.renderSuccessMsg(code,"ok");
             }
             logger.info("【Web扫码支付服务】支付宝支付下单失败!原因:"+resMap.get("returnMsg"));
             return AjaxUtil.renderFailMsg(resMap.get("returnMsg"));
         }
    }
    
  /**
   * 支付宝App支付接口
   * 
   * @param request
   * @return
   * @throws Exception
   */
  @ResponseBody
  @RequestMapping(value="aliAppPay", method=RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
  public JSONObject aliAppPay(HttpServletRequest request) throws Exception {
        String requestStr = RequestStr.getRequestStr(request);
        if (StringUtils.isEmpty(requestStr)) {
          throw new ParamException();
        }
         JSONObject jsonObj = JSONObject.parseObject(requestStr);
         if(StringUtils.isEmpty(jsonObj.getString("orderNo"))){
           throw new ParamException();
         }
         //验证订单是否存在
         String orderNo = jsonObj.getString("orderNo");
         JSONObject json = getPayOrder(orderNo);
         if(json == null){
           return AjaxUtil.renderFailMsg("订单不存在!");
         }else if(json.getDouble("payPrice") == null || json.getDouble("payPrice") < 0.01){
           return AjaxUtil.renderFailMsg("订单有误,请确认!");
         }else if(json.getInteger("orderStatus") != OrderConstant.PORDER_STATUS_YTJ){
           String msg = orderInfo.getpStatus() == OrderConstant.PORDER_STATUS_YFK?"此订单已支付!":"无效的订单,请确认!";
           return AjaxUtil.renderFailMsg(msg);
         }else{
             //支付宝app支付
             Map<String, String> resMap = paymentService.aliAppPayment(orderNo, json.getDouble("payPrice"));
             if("SUCCESS".equals(resMap.get("returnCode")) && "OK".equals(resMap.get("returnMsg"))){
               //统一下单成功
               resMap.remove("returnCode");
               resMap.remove("returnMsg");
               logger.info("【App支付服务】支付宝支付单成功!");
               return AjaxUtil.renderSuccessMsg(resMap);
             }
             logger.info("【App支付服务】支付宝支付单失败!原因:"+resMap.get("returnMsg"));
             return AjaxUtil.renderFailMsg(resMap.get("returnMsg"));
         }
  }
    
  /**
   * <p>支付宝扫码支付获取二维码页面</p>
   *
   * @param request
   * @param response
   * @throws Exception
   */
    @ResponseBody
    @RequestMapping(value="getAliPayHtml", method=RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    public void getAlipayInfo(HttpServletRequest request,HttpServletResponse response) throws Exception{
      //获取生成二维码的url
      try {
           //获取参数
           String code = request.getParameter("payCode");
           if(!StringUtils.isEmpty(code) && cacheService.isExistKey(code)){
             //调用业务层
             String html = (String) cacheService.getCacheByKey(code);
             response.setContentType("text/html;charset=" + "UTF-8");
             response.getWriter().write(html);//直接将完整的表单html输出到页面
             response.getWriter().flush();
             response.getWriter().close();
           }else{
             String html = error_page;
             response.setContentType("text/html;charset=" + "UTF-8");
             response.getWriter().write(html);//直接将完整的表单html输出到页面
             response.getWriter().flush();
             response.getWriter().close();
           } 
      }catch(Exception e){
        logger.error(e.getMessage());
        String html = error_page;
           response.setContentType("text/html;charset=" + "UTF-8");
           response.getWriter().write(html);//直接将完整的表单html输出到页面
           response.getWriter().flush();
           response.getWriter().close();
      }
    }

    /**
     * 支付宝支付完成回调
     * 
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value="aliNotify")
    public String aliNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
        String tradeStatus = request.getParameter("trade_status");//支付状态
        if (tradeStatus.equals("TRADE_FINISHED") || tradeStatus.equals("TRADE_SUCCESS")) {
            String orderNo= request.getParameter("out_trade_no"); //商户订单号
            String tradeNo = request.getParameter("trade_no");        //支付宝订单号
             //String total_fee = request.getParameter("total_amount");//支付金额
            if(paymentService.aliNotify(orderNo,tradeNo) > 0){
              logger.info("【支付宝支付回调响应】 响应内容:success!");
              return "success";
            }
          }
          logger.info("【支付宝支付回调响应】 响应内容:fail!");
          return "fail";
    }
}

四,测试

选择要购买的商品,然后下单,再去发起支付。

image

点击去支付,调用Java后台的支付接口

image

注:可能有人发现支付时金额怎么从625变成0.01了,这是后台做了处理,方便测试环境测试。支付成功后,跳转到支付宝付款成功页面。

image

本文章只是给大家讲解原理和接入思路及实现,所以文章里只放了核心业务代码。还有好多工具类及配置等代码,全放到这里来肯定也不现实,如果有需要完整代码的可关注公众号获取。获取方式扫码关注公众号;找到“关于我”>>>"联系我" ;添加我个人微信获取;

推荐阅读:

微信支付之扫码、APP、小程序支付接入详解

SpringBoot电商项目实战 — ElasticSearch接入实现

SpringBoot电商项目实战 — 商品的SPU/SKU实现

SpringBoot电商项目实战 — 数据库服务化切分

image

扫码关注公众号,发送关键词获取相关资料:

  1. 发送“Springboot”领取电商项目实战源码;

  2. 发送“SpringCloud”领取cloud学习实战资料;

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容