支付宝支付

简介: 支付宝支付

想对接支付,就需要申请对应的支付渠道。否则你只能模拟一个支付和支付成功,来走完自己的流程。

目前国内主要有微信支付和支付宝支付两种主流支付方式,但是微信支付不支持个体户,因此这里选择支付宝的沙箱支付。

其实支付流程基本如下图所示:


现在可以先去支付宝开发平台申请资质,https://open.alipay.com/develop/manage

然后直接下载手机版沙箱支付宝。

实现方案

以下是测试用例(java版):

public class PayTest {
 
    // 「沙箱环境」应用ID - 您的APPID,收款账号既是你的APPID对应支付宝账号。获取地址;https://open.alipay.com/develop/sandbox/app
    public static String app_id = "你的APPID";
    // 「沙箱环境」商户私钥,你的PKCS8格式RSA2私钥
    public static String app_private_key = "你的商户私钥"
    // 「沙箱环境」支付宝公钥
    public static String alipay_public_key = "你的支付宝公钥"
    // 「沙箱环境」服务器异步通知页面路径。如果非公网地址建议使用 natapp 等内网穿透工具
    public static String notify_url = "异步支付结果通知地址";
    // 「沙箱环境」页面跳转同步通知页面路径 需http://格式的完整路径,必须外网可以正常访问,才会同步跳转
    public static String return_url = "同步页面回调地址,可自动返回商户页面";
    // 「沙箱环境」
    public static String gatewayUrl = "支付宝网关地址";
    // 签名方式
    public static String sign_type = "RSA2";
    // 字符编码格式
    public static String charset = "utf-8";
 
    @Test
    public void test_AliPay() throws AlipayApiException {
        AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl,
                app_id,
                app_private_key,
                "json",
                charset,
                alipay_public_key,
                sign_type);
 
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();  // 发送请求的 Request类
        request.setNotifyUrl(notify_url);
//        request.setReturnUrl(return_url);
 
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", "daniel82AAAA000032333361X02");  // 我们自己生成的订单编号
        bizContent.put("total_amount", "0.01"); // 订单的总金额
        bizContent.put("subject", "测试商品");   // 支付的名称
        bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");  // 固定配置
        request.setBizContent(bizContent.toString());
 
        String form = alipayClient.pageExecute(request).getBody();
        log.info("测试结果:{}", form);
    }
}
  1. 这是支付宝沙箱支付 SDK 支付宝扫码的调用案例,本身这个 SDK 支持了支付宝所有的支付方式,非常方便。
  2. 支付宝沙箱不影响实际环境,且容易申请,如果可能,可以申请微信支付、
  3. 案例运行成功后,会拿到一个html表单,这是一个动态表单,直接加载html就可以去往支付界面。

具体实现

创建订单

@RequestMapping(value = "create_pay_order", method = RequestMethod.POST)
    public Response<String> createParOrder(@RequestHeader("Authorization") String token, @RequestParam Integer productId) {
        try {
            // 1. 校验token
            boolean success = authService.checkToken(token);
            if (!success) {
                return Response.<String>builder()
                        .code(Constants.ResponseCode.TOKEN_ERROR.getCode())
                        .info(Constants.ResponseCode.TOKEN_ERROR.getInfo())
                        .build();
            }
            // 2. token解析
            String openId = authService.getOpenId(token);
            assert openId != null;
            log.info("用户商品下单,根据商品ID创建支付单开始 openid:{} productId:{}", openId, productId);
 
            ShopCartEntity shopCartEntity = ShopCartEntity.builder()
                    .openid(openId)
                    .productId(productId)
                    .build();
 
            PayOrderEntity orderEntity = orderService.createOrder(shopCartEntity);
            log.info("用户商品下单,根据商品ID创建支付单完成 openid: {} productId: {} orderPay: {}", openId, productId, orderEntity.toString());
 
            return Response.<String>builder()
                    .code(Constants.ResponseCode.SUCCESS.getCode())
                    .info(Constants.ResponseCode.SUCCESS.getInfo())
                    .data(orderEntity.getPayUrl())
                    .build();
 
 
        }catch (Exception e) {
            log.error("用户商品下单,根据商品ID创建支付单失败", e);
            return Response.<String>builder()
                    .code(Constants.ResponseCode.UN_ERROR.getCode())
                    .info(Constants.ResponseCode.UN_ERROR.getInfo())
                    .build();
        }
    }


public interface IOrderService {
 
    /**
     * 用户下单,通过购物车信息,返回下单后的支付单
     *
     * @param shopCartEntity 简单购物车
     * @return 支付单实体对象
     */
    PayOrderEntity createOrder(ShopCartEntity shopCartEntity);
    
    //... 省略其他方法
    
}    
 
 
 
 @Override
    public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception {
        String openid = shopCartEntity.getOpenid();
 
        // 1. 查询有效的未支付订单
        UnpaidOrderEntity unpaidOrderEntity = orderRepository.queryUnpaidOrder(shopCartEntity);
        if (unpaidOrderEntity != null && unpaidOrderEntity.getPayStatus().equals(PayStatusVO.WAIT)) {
            log.info("创建订单-存在,已存在未支付订单,返回 openid: {} orderId: {} payUrl: {}", openid, unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getPayUrl());
            return PayOrderEntity.builder()
                    .openid(openid)
                    .payStatus(unpaidOrderEntity.getPayStatus())
                    .payUrl(unpaidOrderEntity.getPayUrl())
                    .orderId(unpaidOrderEntity.getOrderId())
                    .build();
        }else if (unpaidOrderEntity != null && unpaidOrderEntity.getPayStatus().equals(PayStatusVO.CREATE)) {
            log.info("创建订单-存在,存在未创建支付单订单,返回 openid: {} orderId: {}", openid, unpaidOrderEntity.getOrderId());
            PayOrderEntity payOrderEntity = this.doPrepayOrder(openid, unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getProductName(), unpaidOrderEntity.getTotalAmount());
            log.info("创建订单-完成,生成支付单。openid: {} orderId: {} payUrl: {}", openid, payOrderEntity.getOrderId(), payOrderEntity.getPayUrl());
            return payOrderEntity;
        }
 
        // 2. 查询商品
        ProductEntity productEntity = orderRepository.queryProduct(shopCartEntity.getProductId());
        // 如果所购商品已下线
        if (!productEntity.isAvailable()) {
            throw new ChatGPTException(Constants.ResponseCode.ORDER_PRODUCT_ERR.getCode(), Constants.ResponseCode.ORDER_PRODUCT_ERR.getInfo());
        }
 
        // 3. 保存订单
        OrderEntity orderEntity = this.doSaveOrder(openid, productEntity);
 
        // 4. 创建支付
        PayOrderEntity payOrderEntity = this.doPrepayOrder(openid, orderEntity.getOrderId(), productEntity.getProductName(), orderEntity.getTotalAmount());
        log.info("创建订单-完成,生成支付单。openid: {} orderId: {} payUrl: {}", openid, orderEntity.getOrderId(), payOrderEntity.getPayUrl());
 
        return payOrderEntity;
    }

创建订单前,要查询有效的未支付订单,如果存在直接返回支付宝支付界面。避免创建一堆的订单。

此外我们做流程分析时候知道,还有可能是订单存在,但无支付单。那么这个时候需要主动创建一条支付单,再返回。

如果确实需要创建新订单,则需要根据购物车商品ID,查询出对应的商品信息,创建并保存订单,最后再创建支付单更新到订单上。

支付回调

@PostMapping("pay_notify")
    public void payNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            log.info("支付回调,消息接收 {}", request.getParameter("trade_status"));
            if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
                Map<String, String> params = new HashMap<>();
                Map<String, String[]> requestParams = request.getParameterMap();
                for (Iterator 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);
                }
                String tradeNo = params.get("out_trade_no");
                String gmtPayment = params.get("gmt_payment");
                String alipayTradeNo = params.get("trade_no");
                String sign = params.get("sign");
                Double total_amount = Double.valueOf(params.get("total_amount"));
                boolean checkSignature  = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
 
                // 支付宝验签通过
                if (checkSignature) {
                    // 验签通过
                    log.info("支付回调,交易名称: {}", params.get("subject"));
                    log.info("支付回调,交易状态: {}", params.get("trade_status"));
                    log.info("支付回调,支付宝交易凭证号: {}", params.get("trade_no"));
                    log.info("支付回调,商户订单号: {}", params.get("out_trade_no"));
                    log.info("支付回调,交易金额: {}", params.get("total_amount"));
                    log.info("支付回调,买家在支付宝唯一id: {}", params.get("buyer_id"));
                    log.info("支付回调,买家付款时间: {}", params.get("gmt_payment"));
                    log.info("支付回调,买家付款金额: {}", params.get("buyer_pay_amount"));
                    log.info("支付回调,支付回调,更新订单 {}", tradeNo);
                    // 更新订单为已支付
                    boolean isSuccess =  orderService.changeOrderPaySuccess(tradeNo, alipayTradeNo, BigDecimal.valueOf(total_amount), dateFormat.parse(gmtPayment));
 
                    if (isSuccess) {
                        // 发布消息
//                        eventBus.post(tradeNo);
                        rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT,"", tradeNo);
 
                    }
                    response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
                }else {
                    response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
                }
            }
        }catch (Exception e) {
            log.error("支付失败", e);
            response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
        }
    }

支付回调主要以支付宝支付的消息通知为主,更新订单并发送消息。这两步操作都是非常快的,不会让支付回调超时。

所以一般我们不是通过支付回调直接发货,因为发货流程更长,MQ消息解耦则是更好的方式。

注意 eventBus 是 Guava 的消息总线,类似于 MQ 消息。为了应对高并发已经改为了rabbitmq,如果业务简单可以继续用Guava

到这支付功能基本实现了,不过实际的商品下单支付场景肯定还会复杂,并且还得考虑异常,比如下单未支付,支付未发货,消息通知异常等等。小伙伴们可以思考哦。  


目录
相关文章
|
JavaScript Docker 容器
Docker中文件拷贝命令的详细解释与真实案例
Docker中文件拷贝命令的详细解释与真实案例
1154 0
|
JSON 自然语言处理 物联网
基于PaddleNLP的ChatGLM-6B模型lora微调实现Data-To-Text 硬约束下的受控文本生成
基于PaddleNLP的ChatGLM-6B模型lora微调实现Data-To-Text 硬约束下的受控文本生成
737 0
|
机器学习/深度学习 计算机视觉
YOLOv5改进 | 2023 | MPDIoU、InnerMPDIoU助力细节涨点
YOLOv5改进 | 2023 | MPDIoU、InnerMPDIoU助力细节涨点
480 0
|
Web App开发 网络协议 算法
WebRTC 和一些常见的直播方案
【10月更文挑战第25天】
|
IDE 开发工具 虚拟化
EFI VMware Virtual SCSI Hard Drive (0.0) ... No Media
EFI VMware Virtual SCSI Hard Drive (0.0) ... No Media
|
自然语言处理 搜索推荐 Java
ElasticSearch 实现分词全文检索 - 概述
ElasticSearch 实现分词全文检索 - 概述
297 0
|
弹性计算
阿里云账号注册流程图文详解、账户实名认证和申请免费服务器全流程
阿里云账号注册支持手机号、支付宝等验证方式。使用手机号需手动验证,而支付宝等可自动完成实名认证。注册后须进行个人或企业实名认证才能正常使用服务。个人认证推荐使用支付宝快速完成;企业认证也支持支付宝法人扫描完成。完成认证后,可在免费中心申请最长达3个月的免费服务器试用,或选择付费方案获得更多资源。
|
Java 数据库连接 数据库
MyBatis TypeHandler详解:原理与自定义实践
MyBatis TypeHandler详解:原理与自定义实践
|
消息中间件 存储 缓存
【热点】Kafka与传统中间件(MQ,ETL,ESB)的比较
关注公众号“达摩院首座”,了解开发者最真实生活
3060 85
【热点】Kafka与传统中间件(MQ,ETL,ESB)的比较

热门文章

最新文章

下一篇
开通oss服务