第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单

1.支付宝支付

1.1.蚂蚁金服开放平台

https://open.alipay.com/platform/home.htm

1.2.沙箱环境

https://openhome.alipay.com/platform/appDaily.htm

1.2.1.密钥设置

采用RSA2非对称加密方式

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第1张图片


支付宝密钥生成工具下载:

https://opendocs.alipay.com/open/291/105971

生成密钥:

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第2张图片

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第3张图片


支付宝沙箱 -> 设置公钥:商户公钥

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第4张图片


支付宝沙箱 -> 保存设置

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第5张图片

2.内网穿透


2.1.简介

内网穿透可以允许我们使用外网的网址来访问主机。


正常的外网需要访问应用的流程是:

  • 购买服务器并且有公网固定IP
  • 购买域名映射到服务器的IP
  • 域名需要进行备案和审核

2.2.使用场景

  • 开发测试(微信,支付宝等)
  • 智慧互联
  • 远程控制
  • 私有云

2.3.内网穿透常用软件

  • natapp:https://natapp.cn

  • 续断:http://www.zhexi.tech

  • 花生壳:https://www.oray.com

2.4.Natapp使用


2.4.1.下载natapp客户端

https://natapp.cn/#download

2.4.2.购买免费隧道



2.4.3.获取内网穿透地址

  • 以管理员身份运行natapp客户端
  • 执行此命令,natapp -authtoken 隧道authtoken

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第6张图片



3.整合支付宝


支付宝SDK:https://opendocs.alipay.com/open/54/cyz7do

Alipay Easy SDK:https://github.com/alipay/alipay-easysdk

API接口文档:https://github.com/alipay/alipay-easysdk/blob/master/APIDoc.md


3.1.导入支付宝服务端新版SDK

<dependency>
	<groupId>com.alipay.sdk</groupId>
	<artifactId>alipay-easysdk</artifactId>
	<version>2.2.0</version>
</dependency>

3.2.支付宝配置

# 支付宝-沙箱
alipay.protocol=https
alipay.gatewayHost=openapi.alipaydev.com
alipay.signType=RSA2
alipay.appId=9021000122694995
# 商户私钥
alipay.merchantPrivateKey=static/alipay/zh_sy.txt
# 支付宝公钥
alipay.alipayPublicKey=static/alipay/zh_gy.txt
# fsp6n6.natappfree.cc 内网穿透地址
alipay.notifyUrl=http://server.natappfree.cc/alipay/notify
# 支付成功之后跳转的链接地址
alipay.returnUrl=http://member.gmall.com/memberOrder.html
# 支付限时
alipay.timeoutExpress=1m

AlipayProperties

package com.atguigu.gmall.order.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 支付宝-沙箱环境参数 {@link AlipayProperties}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayProperties {

    private String protocol;
    private String gatewayHost;
    private String signType;
    /**
     * 应用id
     */
    private String appId;
    /**
     * 应用私钥
     */
    private String merchantPrivateKey;
    /**
     * 支付宝公钥
     */
    private String alipayPublicKey;
    /**
     * 异步通知接收服务地址
     */
    private String notifyUrl;
}

AlipayConfig

package com.atguigu.gmall.order.config;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.atguigu.common.utils.ReadTxtUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

/**
 * 支付宝配置 {@link AlipayConfig}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@EnableConfigurationProperties(AlipayProperties.class)
@Configuration
public class AlipayConfig {

    @Bean
    public Config config(AlipayProperties alipayProperties) throws IOException {
        Config config = new Config();
        config.protocol = alipayProperties.getProtocol();
        config.gatewayHost = alipayProperties.getGatewayHost();
        config.signType = alipayProperties.getSignType();

        config.appId = alipayProperties.getAppId();

        // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = ReadTxtUtils.readTxtFile(
                new ClassPathResource(alipayProperties.getMerchantPrivateKey()).getInputStream());

        //注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载
//        config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->";
//        config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->";
//        config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->";

        //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
         config.alipayPublicKey = ReadTxtUtils.readTxtFile(
                 new ClassPathResource(alipayProperties.getAlipayPublicKey()).getInputStream());

        //可设置异步通知接收服务地址(可选)
        config.notifyUrl = alipayProperties.getNotifyUrl();

        Factory.setOptions(config);

        return config;
    }
}

3.3.支付页面

支付宝支付页面,使用沙箱账号 -> 买家信息账号登录付款

第25天-支付宝沙箱,支付加密,内网穿透,支付宝新版SDK,支付回调,支付收单_第7张图片

OrderPayController

package com.atguigu.gmall.order.web;

import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * 订单支付 {@link OrderPayController}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@RestController
public class OrderPayController {

    @Autowired
    private AliPayService aliPayService;

    /**
     * 跳转到支付宝支付页
     * @param orderSn
     * @return
     */
    @GetMapping(value = "/payOrder", produces = "text/html")
    public String payOrder(@RequestParam("orderSn") String orderSn){
        // 返回的是支付宝收银页面
        String pay = aliPayService.pay(orderSn);

        return pay;
    }
}

AliPayServiceImpl

package com.atguigu.gmall.order.service.impl;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.atguigu.gmall.order.constant.OrderStatusEnum;
import com.atguigu.gmall.order.entity.PaymentInfoEntity;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.service.PaymentInfoService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import com.atguigu.gmall.order.vo.PayVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 支付宝支付接口实现 {@link AliPayServiceImpl}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {

    @Value("${alipay.returnUrl}")
    private String returnUrl;

    @Value("${alipay.timeoutExpress}")
    private String timeoutExpress;

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentInfoService paymentInfoService;

    /**
     * 支付
     * @param orderSn
     * @return
     */
    @Override
    public String pay(String orderSn) {
        PayVO payVO = orderService.getOrderPay(orderSn);
        payVO.setSubject("谷粒订单支付");
        payVO.setBody("");

        try {
            AlipayTradePagePayResponse response = Factory.Payment.Page()
                    // 订单允许的最晚付款时间,逾期将关闭交易
                    .optional("timeout_express", timeoutExpress)
                    .pay(payVO.getSubject(), payVO.getOut_trade_no(), payVO.getTotal_amount(), returnUrl);
            // body: 支付宝的收银台页面
            return response.body;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

3.4.支付成功异步通知

支付成功,跳转到:会员系统 -> 订单列表页面(orderList.html)

支付成功后异步通知回调地址(notifyUrl)使用的是内网穿透地址,需要在nginx进行精确配置,才能访问

/mydata/nginx/conf/conf.d/gmall.conf

server {
	listen 80;
	listen [::]:80;
	# gqjzd6.natappfree.cc 内网穿透地址
	server_name *.gmall.com gmall.com gqjzd6.natappfree.cc;
	
	location /static/ {
		root /usr/share/nginx/html;
	}
	
	# 精确配置内网穿透访问地址
	location /alipay/ {
		proxy_pass http://gmall;
		proxy_set_header Host order.gmall.com;
	}
	
	location / {
		proxy_pass http://gmall;
		proxy_set_header Host $host;
	}
}

OrderPayController

package com.atguigu.gmall.order.web;

import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * 订单支付 {@link OrderPayController}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@RestController
public class OrderPayController {

    @Autowired
    private AliPayService aliPayService;

    /**
     * 支付宝支付成功异步通知
     * @param vo
     * @return
     */
    @PostMapping("/alipay/notify")
    public String handleAlipay(PayAsyncVO vo, HttpServletRequest request){
        String result = null;
        try {
            result = aliPayService.payNotify(vo, request);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }
}

AliPayServiceImpl

package com.atguigu.gmall.order.service.impl;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.atguigu.gmall.order.constant.OrderStatusEnum;
import com.atguigu.gmall.order.entity.PaymentInfoEntity;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.service.PaymentInfoService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import com.atguigu.gmall.order.vo.PayVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 支付宝支付接口实现 {@link AliPayServiceImpl}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {

    @Value("${alipay.returnUrl}")
    private String returnUrl;

    @Value("${alipay.timeoutExpress}")
    private String timeoutExpress;

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentInfoService paymentInfoService;

    /**
     * 支付成功异步回调
     * @param vo
     * @param request
     * @return
     * @throws Exception
     */
    @Override
    public String payNotify(PayAsyncVO vo, HttpServletRequest request) throws Exception {
        // 验签
        Boolean signVerified = verifyNotify(request);
        // 验证成功
        if (signVerified) {
            log.info("异步通知验签成功...");
            // 保存交易流水
            PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
            paymentInfoEntity.setAlipayTradeNo(vo.getTrade_no());
            paymentInfoEntity.setOrderSn(vo.getOut_trade_no());
            paymentInfoEntity.setSubject(vo.getSubject());
            paymentInfoEntity.setTotalAmount(new BigDecimal(vo.getTotal_amount()));
            paymentInfoEntity.setPaymentStatus(vo.getTrade_status());
            Date parseDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(vo.getNotify_time());
            paymentInfoEntity.setCallbackTime(parseDate);
            paymentInfoService.save(paymentInfoEntity);

            // 修改订单状态信息
            if ("TRADE_SUCCESS".equals(vo.getTrade_status()) || "TRADE_FINISHED".equals(vo.getTrade_status())) {
                String outTradeNo = vo.getOut_trade_no();
                orderService.updateOrderStatus(outTradeNo, OrderStatusEnum.PAYED.getCode());
            }

            // 只要收到支付宝异步通知,告知订单支付成功,返回success,支付宝就不会再通知
            return "success";
        } else {
            log.error("异步通知验签失败...");
            return "fail";
        }
    }

    /**
     * 异步通知验签
     * @param request
     * @return
     * @throws Exception
     */
    public Boolean verifyNotify(HttpServletRequest request) throws Exception {
        log.info("异步通知验签...");
        Map<String,String> params = new HashMap<String,String>();
        Map<String,String[]> requestParams = request.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 result = Factory.Payment.Common().verifyNotify(params);

        return result;
    }
}

4.支付宝收单

  • 订单在支付页,不支付,一直刷新,订单过期了才支付,订单状态改为已付款,但是库存解锁了
    • 使用支付宝自动收单功能解决,只要一段时间不支付,就不能支付了
AlipayTradePagePayResponse response = Factory.Payment.Page()
	//订单允许的最晚付款时间,逾期将关闭交易
	.optional("timeout_express", timeoutExpress)
	.pay(payVO.getSubject(), payVO.getOut_trade_no(),
payVO.getTotal_amount(), returnUrl);
  • 由于延时等问题,订单解锁完成,正在解锁库存的时候,异步通知才到达
    • 订单解锁,手动调用收单
@RabbitHandler
public void handlerOrderClose(OrderEntity entity, Message message,
Channel channel) {
	log.info("收到过期的订单信息,准备关闭订单,{}", entity);
	try{
		orderService.closeOrder(entity);
	
		//手动调用支付宝收单
		aliPayService.close(entity.getOrderSn());
		
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
	} catch (Exception e) {
		try {
			channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
		} catch (IOException ioException) {
			ioException.printStackTrace();
		}
	}
}
  • 网络阻塞问题,订单支付成功的异步通知一直不到达

    • 查询订单列表时,异步(Ajax)获取当前未支付的订单状态,查询订单状态时,再获取一下支付宝此订单的状态

  • 其它各种问题

    • 每晚空闲时间下载支付宝对账单,一一进行对账

你可能感兴趣的:(谷粒商城,java)