10.支付

支付相关知识点

1.加密

1.1.对称加密

加密解密使用同一把钥匙

在这里插入图片描述

1.2.非对称加密

钥匙A:加密明文发送
钥匙B:将经过钥匙A加密的密文解密
钥匙C:加密明文发送
钥匙D:将经过钥匙C加密的密文解密

在这里插入图片描述

1.2.1.公钥
公钥:用于解密的 A/C

在这里插入图片描述

1.2.2.私钥
私钥:用于加密的 B/D
1.2.3.签名
作用:
	防止中途数据传输篡改
	
加签流程:
	1.商户使用私钥对请求参数加签生成签名sign=xxxxx,发送请求
	2.支付宝接收请求,根据公钥生成签名,与传参签名对比,相等则验参成功

在这里插入图片描述

1.2.4.验签
验参流程:
	支付宝接收请求,根据公钥生成签名,与传参签名对比,相等则验参成功

支付宝

1.文档汇总

蚂蚁金服开放平台:  https://open.alipay.com/
应用:		       https://open.alipay.com/dev/workspace
API文档:		   https://open.alipay.com/api
开发文档:		  https://opendocs.alipay.com/open/270

2.接入流程

2.1.正式环境使用流程

在这里插入图片描述

1.创建应用
2.添加能力:选择新增能力=》支付,全选
3.签约:https://memberprod.alipay.com/account/reg/index.htm
	操作指南:https://opendocs.alipay.com/open/00r5uy

2.2.demo下载测试

demo下载:https://opendocs.alipay.com/open/270/106291

2.3.沙箱环境使用流程

1.找到沙箱文档步骤
	1)文档:https://opendocs.alipay.com/home(打开API集成工具)
	2)侧边栏找到沙箱环境:https://opendocs.alipay.com/common/02kkv7
	3)沙箱应用:https://open.alipay.com/platform/appDaily.htm
	4)支付宝开放平台开发助手:https://opendocs.alipay.com/common/02mriz
		工具下载下载:https://opendocs.alipay.com/common/02kipk
生成密钥、设置公钥
1.工具下载下载:https://opendocs.alipay.com/common/02kipk

2.生成商户私钥、公钥

3.拷贝商户私钥放到java配置文件中
  拷贝商户公钥设置到沙箱环境中
  拷贝支付宝公钥到java配置文件中

生成商户私钥、公钥:

在这里插入图片描述

设置RSA2公钥:

在这里插入图片描述

内网穿透
参考nps、npc相关文档搭建内网穿透

在这里插入图片描述

  • bug1:使用nps作内网穿透,无法使用域名必须使用IP:PORT,所以会造成nginx无法根据访问的域名gulimall.com来匹配请求
    • 解决:
      • 方案一:修改nginx配置文件gulimall.conf监听server_name 124.223.7.41

  • bug2:添加以上域名监听后,访问124.223.7.41:8888出现404异常
    • 原因:网关88未拦截到请求
    • 解决:
      • 方案一:在网关增加拦截规则,拦截124.223.7.41,将请求发送到order.gulimall.com
      • 方案二:在nginx转发时(/payed),设置host=order.gulimall.com,使网关可以正确拦截【推荐】
      • 方案三:内网穿透的地址直接配成192.168.56.1:9000【缺点:没有负载均衡了】
bug1:
	修改gulimall.conf
server {
    listen       80;
    server_name gulimall.com *.gulimall.com 124.223.7.41;

    location /static/ {
        root /usr/share/nginx/html;
    }

    location / {
        proxy_set_header Host $host;
        proxy_pass http://gulimall;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

bug2:
方案一:
        - id: gulimall_order_route2
          uri: lb://gulimall-order
          predicates:
            - Host=124.223.7.41

方案二:
	修改gulimall.conf
server {
    listen       80;
    server_name gulimall.com *.gulimall.com 124.223.7.41;

    location /static/ {
        root /usr/share/nginx/html;
    }

    location /payed/ {
        proxy_set_header Host order.gulimall.com;
        proxy_pass http://gulimall;
    }

    location / {
        proxy_set_header Host $host;
        proxy_pass http://gulimall;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

在这里插入图片描述

在这里插入图片描述

内网穿透配置:

在这里插入图片描述

账户密码
  • 商家信息
    • 商家账号:qdjyva8795@sandbox.com
    • 登录密码:111111
    • 商户PID:2088621957017110
  • 买家信息
    • 买家账号:ejjcyw3030@sandbox.com
    • 登录密码:111111
    • 支付密码:111111
    • 用户UID:2088102181519523
    • 用户名称:沙箱环境
    • 证件类型:身份证(IDENTITY_CARD)
    • 证件号码:631951191803114688
属性
#支付宝相关的配置
alipay.app_id=2021000118641369
alipay.merchant_private_key=xx
alipay.alipay_public_key=xx
#支付成功异步跳转的地址(内网穿透)
alipay.url=http://124.223.7.41:8888
#支付成功同步跳转的地址
alipay.return_url=http://member.gulimall.com/memberOrder.html
alipay.sign_type=RSA2
alipay.charset=utf-8
alipay.gatewayUrl=https://openapi.alipaydev.com/gateway.do
导入依赖、配置类
1.导入依赖
<!-- 支付宝sdk -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.10.111.ALL</version>
</dependency>
<dependency>
    <groupId>cn.springboot</groupId>
    <artifactId>best-pay-sdk</artifactId>
    <version>1.3.0</version>
</dependency>
2.配置类
@ConfigurationProperties(prefix = "alipay")
@Component
@Data
public class AliPayConfig {

    // 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
    private String app_id;

    // 商户私钥,您的PKCS8格式RSA2私钥
    private String merchant_private_key;

    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    private String alipay_public_key;

    // 当前项目域名地址
    private String url;

    // 服务器[异步通知]页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    // 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息
    private String notify_url;

    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    //同步通知,支付成功,一般跳转到成功页
    private String return_url;

    // 签名方式
    private String sign_type;

    // 字符编码格式
    private String charset;

    //订单超时时间
    private String timeout = "2m";

    // 支付宝网关
    private String gatewayUrl;
}
创建支付
1.支付接口
/**
 * @author: wan
 */
@Slf4j
@Controller
public class PayWebController {

    @Autowired
    OrderServiceImpl orderService;
    @Autowired
    PayContextStrategy payContextStrategy;
    @Autowired
    AliPayConfig aliPayConfig;

    /**
     * 创建支付,返回text/html页面
     * @param orderSn       订单号
     * @param type          支付类型
     * @param businessType  业务类型
     */
    @ResponseBody
    @GetMapping(value = "/html/pay", produces = "text/html")
    public String htmlPayOrder(@RequestParam(value = "orderSn", required = false) String orderSn,
                               @RequestParam(value = "payCode", required = true) Integer payCode,
                               @RequestParam(value = "businessCode", required = true) Integer businessCode) throws Exception {
        // 获取支付类型
        PayType payType = PayType.getByCode(payCode);
        // 获取业务类型
        PayBusinessDetailType businessDetailType = PayBusinessDetailType.getByCodeAndBusinessCode(
                payType.getCode(), businessCode);

        // 获取订单信息,构造参数
        PayVO order = orderService.getOrderPay(orderSn);
        order.setNotify_url(aliPayConfig.getUrl() + businessDetailType.getNotifyUrl());// 封装异步回调地址
        order.setReturn_url(businessDetailType.getReturnUrl());// 封装同步回调地址

        // 请求策略方法
        String html = payContextStrategy.pay(payType, order);
        return html;
    }
}
2.支付策略类
/**
 * 策略类
 */
@Component
public class PayContextStrategy {

    /**
     * 创建支付
     *
     * @param payType        策略类型
     * @param businessDetail 业务类型
     * @param order          订单数据
     * @return
     */
    public String pay(PayType payType, PayVO order) throws Exception {
        // 获取实际策略对象
        PayStrategy payStrategy = SpringUtils.getBean(payType.getStrategyBeanId(), PayStrategy.class);
        // 执行具体策略
        return payStrategy.pay(order);
    }
}
3.策略角色
/**
 * 支付宝策略角色实现
 */
@Component
public class AliPayStrategy implements PayStrategy {

    @Autowired
    AliPayServiceImpl aliPayService;

    /**
     * 创建支付
     * @param order 订单详情
     */
    @Override
    @Transactional
    public String pay(PayVO order) throws AlipayApiException {
        // 创建支付返回html渲染
        String html = aliPayService.pay(order);
        return html;
    }
}
4.支付宝远程调用
/**
 * 支付宝支付
 *
 * @Author: wanzenghui
 * @Date: 2022/1/4 23:19
 */
@Service
public class AliPayServiceImpl implements PayService {

    @Autowired
    AliPayConfig aliPayConfig;

    /**
     * 创建支付,获取支付页
     */
    @Override
    public String pay(PayVO order) throws AlipayApiException {
        //AlipayClient alipayClient = new DefaultAlipayClient(AlipayTemplate.gatewayUrl, AlipayTemplate.app_id, AlipayTemplate.merchant_private_key, "json", AlipayTemplate.charset, AlipayTemplate.alipay_public_key, AlipayTemplate.sign_type);
        //1、根据支付宝的配置生成一个支付客户端
        AlipayClient alipayClient = new DefaultAlipayClient(
                aliPayConfig.getGatewayUrl(),
                aliPayConfig.getApp_id(),
                aliPayConfig.getMerchant_private_key(),
                "json",
                aliPayConfig.getCharset(),
                aliPayConfig.getAlipay_public_key(),
                aliPayConfig.getSign_type());

        //2、创建一个支付请求 //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(order.getReturn_url());
        alipayRequest.setNotifyUrl(order.getNotify_url());

        //商户订单号,商户网站订单系统中唯一订单号,必填
        String out_trade_no = order.getOut_trade_no();
        //付款金额,必填
        String total_amount = order.getTotal_amount();
        //订单名称,必填
        String subject = order.getSubject();
        //商品描述,可空
        String body = order.getBody();

        alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\","
                + "\"total_amount\":\"" + total_amount + "\","
                + "\"subject\":\"" + subject + "\","
                + "\"body\":\"" + body + "\","
                + "\"timeout_express\":\"" + aliPayConfig.getTimeout() + "\","
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

        // 执行创建支付请求,返回支付页面
        String result = alipayClient.pageExecute(alipayRequest).getBody();

        //会收到支付宝的响应,响应的是一个页面,只要浏览器显示这个页面,就会自动来到支付宝的收银台页面
        System.out.println("支付宝响应:登录页面的代码\n" + result);

        return result;
    }
}
同步回调
不建议在同步回调修改订单状态
member模块接收异步回调

/**
 * 支付同步回调
 *
 * @Author: wanzenghui
 * @Date: 2022/1/5 0:17
 */
@Controller
public class MemberWebController {

    @Autowired
    private OrderFeignService orderFeignService;

    /**
     * 支付宝同步回调
     * 查询用户订单列表
     * @param pageNum
     * @param model
     * @return
     */
    @GetMapping(value = "/memberOrder.html")
    public String memberOrderPage(@RequestParam(value = "pageNum", required = false, defaultValue = "0") Integer pageNum,
                                  Model model) {
        // 获取支付宝回参,根据sign延签,延签成功修改订单状态【不建议在同步回调修改订单状态,建议在异步回调修改订单状态】

        // 封装分页数据
        Map<String, Object> page = new HashMap<>();
        page.put("page", pageNum.toString());

        // 分页查询当前用户的订单列表、订单项
        R orderInfo = orderFeignService.listWithItem(page);
        model.addAttribute("orders", orderInfo);

        return "orderList";
    }
}
异步回调

在这里插入图片描述

建议在异步回调修改订单状态

	程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知,直到超过 24 小时 22 分钟。一般情况下,25 小时以内完成 8 次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)。
bug_时间格式化
rejected value [2021-04-07 15:20:13]; codes [typeMismatch.xxx]

原因:异步回调参数时间时字符串类型,但是java参数类型是Date

解决:
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date notify_time;
1.回调接口
/**
 * 订单支付成功监听器
 *
 */
@RestController
public class OrderPayedListener {

    @Autowired
    PayContextStrategy payContextStrategy;

    /**
     * 支付宝支付异步通知
     * 只有支付成功会触发
     * @param request
     * @param asyncVo
     */
    @PostMapping(value = "/payed/ali/notify")
    public String handleAliNotify(HttpServletRequest request, AliPayAsyncVO asyncVo) throws AlipayApiException, UnsupportedEncodingException {
        asyncVo.setPayCode(PayType.ALI_PAY.getCode());// 封装付款类型
        Boolean result = payContextStrategy.notify(PayType.ALI_PAY, request, asyncVo);
        if (result) {
            return "success";// 返回success,支付宝将不再异步回调
        }
        return "error";
    }
}
2.回调策略类
/**
 * 策略类
 */
@Component
public class PayContextStrategy {

    /**
     * 处理回调
     * @param payType   策略类型
     * @param request   请求
     * @param asyncVo   回参VO
     * @return
     */
    public Boolean notify(PayType payType, HttpServletRequest request, PayAsyncVO asyncVo) throws AlipayApiException {
        // 获取实际策略对象
        PayStrategy payStrategy = SpringUtils.getBean(payType.getStrategyBeanId(), PayStrategy.class);
        // 执行具体策略
        return payStrategy.notify(request, asyncVo);
    }
}
3.策略角色
/**
 * 支付宝策略角色实现
 */
@Component
public class AliPayStrategy implements PayStrategy {

    @Autowired
    AliPayServiceImpl aliPayService;

    @Override
    public Boolean notify(HttpServletRequest request, PayAsyncVO asyncVo) throws AlipayApiException {
        // 验签
        Boolean signVerified = aliPayService.verify(request);
        if (signVerified) {
            // 修改订单状态
            System.out.println("签名验证成功...修改订单状态");
            aliPayService.handlePayResult(asyncVo);
        } else {
            System.out.println("签名验证失败...");
        }
        return signVerified;
    }
}
4.验签+修改订单状态
package com.atguigu.gulimall.order.service.impl.alipay;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.atguigu.common.constant.order.OrderConstant.OrderStatusEnum;
import com.atguigu.common.entity.order.PaymentInfoEntity;
import com.atguigu.common.vo.order.PayAsyncVO;
import com.atguigu.common.vo.order.PayVO;
import com.atguigu.common.vo.order.alipay.AliPayAsyncVO;
import com.atguigu.gulimall.order.config.AliPayConfig;
import com.atguigu.gulimall.order.service.PayService;
import com.atguigu.gulimall.order.service.impl.OrderServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 支付宝支付
 *
 * @Author: wanzenghui
 * @Date: 2022/1/4 23:19
 */
@Service
public class AliPayServiceImpl implements PayService {

    @Autowired
    OrderServiceImpl orderService;
    @Autowired
    AliPayConfig aliPayConfig;

    /**
     * 验签
     *
     * @param request 回参
     */
    @Override
    public Boolean verify(HttpServletRequest request) throws AlipayApiException {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用
            // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }

        return AlipaySignature.rsaCheckV1(params, aliPayConfig.getAlipay_public_key(),
                aliPayConfig.getCharset(), aliPayConfig.getSign_type()); //调用SDK验证签名
    }

    /**
     * 处理支付回调
     *
     * @param asyncVo
     */
    @Override
    public void handlePayResult(PayAsyncVO asyncVo) {
        // 封装交易流水信息
        AliPayAsyncVO aliVo = (AliPayAsyncVO) asyncVo;
        // 保存交易流水信息
        PaymentInfoEntity paymentInfo = new PaymentInfoEntity();
        paymentInfo.setOrderSn(aliVo.getOut_trade_no());
        paymentInfo.setAlipayTradeNo(aliVo.getTrade_no());
        paymentInfo.setTotalAmount(new BigDecimal(aliVo.getBuyer_pay_amount()));
        paymentInfo.setSubject(aliVo.getBody());
        paymentInfo.setPaymentStatus(aliVo.getTrade_status());
        paymentInfo.setCreateTime(new Date());
        paymentInfo.setCallbackTime(aliVo.getNotify_time());

        // 获取支付状态
        String tradeStatus = aliVo.getTrade_status();
        Integer orderStatus = null;
        if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {
            // 支付成功状态
            orderStatus = OrderStatusEnum.PAYED.getCode();
        }
        orderService.handlePayResult(orderStatus, aliVo.getPayCode(), paymentInfo);
    }
}
收单

在这里插入图片描述

1.订单超时,不允许支付
	解决:支付时设置超时时间 timeout_express=30m
	
2.订单解锁完成,异步通知才到
	解决:释放库存的时候,手动调用收单功能(参照官方demo的实现)

微信

paypal

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值