美文网首页
微信支付宝当面付集成

微信支付宝当面付集成

作者: 飞鹰雪玉 | 来源:发表于2020-09-14 20:41 被阅读0次

    这两天公司的项目有一个是集成微信支付和支付宝支付到项目中,对应的都是门店扫码枪扫码。顾客展示自己的二维码给店员,店员用扫码枪扫描顾客出示的二维码,然后将条码和订单信息一起发送到后台处理。最后完成付款。微信和支付宝的后台我用的都是java,前端项目是用的odoo开源项目,我需要在前端将微信支付和支付宝支付集成到odoo的pos模块里面(这块挺折磨人的。。。)。不多说了,去解决问题。

    微信支付

    支付模式在微信上称之为刷卡支付,也称之为付款码支付。开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1

    后端

    image.png
    这个图就是我们要实现的全部业务逻辑。现在我们需要去细细去看看开发文档里面的流程。
    1、首先我们需要去微信公众平台去申请线下扫码支付的接入,然后拿到我们开发需要的几个信息。app_id(微信分配的公众账号ID(企业号corpid即为此appId)),mch_id(商户号),还有一个重要的key,是我们自己配的,这个大家接入时候就都知道了。
    2、现在我们需要下载sdk,地址是https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1
    image.png
    下载解压后项目是这样
    image.png
    解压之后大概这样,我们将我们用的文件放到我们自己项目中,我们后台代码结构是这样,为了不影响别人开发以及条理清晰,我自己建了一个sdk包,将微信支付sdk放在这个下面
    代码逻辑.png
    然后我们需要自己写一个MyWXConfig来继承微信的WXPayConfig,来将我们在微信商户平台申请拿到的数据放在里面。就是上面我说的那几个东西。
    MyWXConfig.png
    这是我们需要的sdk引入完成。
    3、编写我们的代码去调用微信支付接口
    代码逻辑还是用的spring mvc模式
    controller.png service.png
    bean.png

    bean层里我们设计了两个对象DoWXMessage和DoWXResp,来实现将获取到的前端数据封装到我们的javaBean对象里面,以及将微信返回给我们的数据封装到对象里面然后返回给前端。

    package com.ym.odoo.bean.pay;
    
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    
    /**
     * 获取前端的值
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Data
    public class DoWXMessage implements Serializable
    {
        /**
         * 序列号
         */
        private static final long serialVersionUID = -2514960757328588003L;
    
        /**
         * 付款码
         */
        public String authCode;
    
        /**
         * 描述
         */
        public String body;
    
        /**
         * 订单号
         */
        public String outTradeNo;
    
        /**
         * 金额 单位:分
         */
        public String total;
    
    }
    
    
    package com.ym.odoo.bean.pay;
    
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    
    /**
     * 返回给前端
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Data
    public class DoWXResp implements Serializable
    {
    
        /**
         * 序列号
         */
        private static final long serialVersionUID = -8080348308283830117L;
    
    
        /**
         * 结果码
         */
        public String resultCode;
    
    }
    
    

    定义一个WXPayService的service接口,然后实现它来访问微信支付接口。

    package com.ym.odoo.service.pay;
    
    
    import com.ym.odoo.bean.pay.DoWXMessage;
    import com.ym.odoo.bean.pay.DoWXResp;
    
    
    /**
     * 微信支付接口
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    public interface WXPayService
    {
        /**
         * 发送请求到微信
         *
         * @param param
         * @return
         */
        DoWXResp wxPaySubmit(DoWXMessage param);
    }
    
    

    接口实现

    package com.ym.odoo.service.impl.pay;
    
    
    import com.ym.odoo.bean.pay.DoWXMessage;
    import com.ym.odoo.bean.pay.DoWXResp;
    import com.ym.odoo.constant.PayConst;
    import com.ym.odoo.service.pay.WXPayService;
    import com.ym.odoo.sdk.wechat.MyWXConfig;
    import com.ym.odoo.sdk.wechat.WXPay;
    import com.ym.odoo.sdk.wechat.WXPayConstants;
    import com.ym.odoo.sdk.wechat.WXPayUtil;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import java.util.Map;
    import java.util.Set;
    import java.util.SortedMap;
    import java.util.TreeMap;
    
    
    /**
     * 发送请求到微信
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Slf4j
    @AllArgsConstructor
    @Service
    public class WXPayServiceImpl implements WXPayService
    {
    
        /**
         * 微信支付
         * 
         * @param message
         *            DoWXMessage对象
         * @return DoWXResp对象
         */
        public DoWXResp wxPaySubmit(DoWXMessage message)
        {
            log.debug("wxPaySubmit:message={}", message);
            DoWXResp resp = new DoWXResp();
            try
            {
                MyWXConfig config = new MyWXConfig(false);
                WXPay wxpay = new WXPay(config, true, false);
                SortedMap<String, String> data = new TreeMap<>();
                data.put("appid", config.getAppID()); // 微信分配的公众账号ID(企业号corpid即为此appId)
                data.put("mch_id", config.getMchID()); // 商户号
                data.put("fee_type", config.getFreePay()); // 货币类型
                data.put("auth_code", message.getAuthCode()); // 付款码
    
                data.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串
                data.put("body", message.getBody());// 商品简单描述,该字段须严格按照规范传递。具体见pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_2,“店名-销售商品类目”
                                                    // 小张南山店-超市 线下门店支付
                data.put("out_trade_no", message.getOutTradeNo()); // 商户订单号(商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一)
                data.put("total_fee", message.getTotal()); // 订单总金额,单位为分,只能为整数,详见支付金额
                // packageParams.put("time_start", "20200820112700");
                // packageParams.put("time_expire","20200820114000");
                // packageParams.put("notify_url", notify_url);
                // packageParams.put("trade_type", trade_type);
    
                data.put("spbill_create_ip", PayConst.spbillIp); // 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
    
                // packageParams.put("notify_url", notify_url);
                // packageParams.put("trade_type", trade_type);
    
                // 根据package数据生成预支付订单号的签名sign
                String sign = WXPayUtil.generateSignature(data, PayConst.wxPayKey,
                    WXPayConstants.SignType.HMACSHA256);
                // 生成需要提交给统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder 的xml数据
                data.put("sign", sign);
    
                Map<String, String> respMap = wxpay.microPay(data);
                log.info("resp:{}", respMap);
    
                // 遍历微信返回给我们的resmap,如果return_code == “SUCCESS”并且 result_code == “SUCCESS”,返回给前端支付成功的消息
                Set<String> keys = respMap.keySet();
                for (String key : keys)
                {
                    // return_code == "SUCCESS",表示通信成功
                    if ("return_code".equals(key) && "SUCCESS".equals(respMap.get("return_code")))
                    {
                        switch (respMap.get("result_code"))
                        {
                            // 表示付款成功
                            case "SUCCESS":
                                resp.setResultCode("success");
                                break;
                            // 表示付款不成功
                            case "FAIL":
                                if ("USERPAYING".equals(respMap.get("err_code")))
                                {
                                    for (int i = 0; i < 5; i++ )
                                    {
                                        // 非免密支付需要每隔10秒去微信查询订单
                                        Thread.sleep(1000);
                                        //查询结果为true,表示付款成功
                                        if (oderQuery(config, wxpay, message))
                                        {
                                            resp.setResultCode("success");
                                        }
                                    }
                                    resp.setResultCode("fail");
                                }
                                else
                                {
                                    resp.setResultCode("fail");
                                }
                                break;
                        }
    
                    }
                }
            }
            catch (Exception ex)
            {
                log.error("wxPaySubmit={}", ex.toString());
            }
            return resp;
        }
    
        /**
         * 查询订单,用于非免密支付时候轮询查询
         * 
         * @param config
         *            微信配置文件
         * @param wxPay
         *            微信接口
         * @param message
         *            订单数据
         * @return 查询付款成功,返回true
         */
        public boolean oderQuery(MyWXConfig config, WXPay wxPay, DoWXMessage message)
        {
            try
            {
                SortedMap<String, String> dataQuery = new TreeMap<>();
                /**
                 * data2是用来查询订单的参数
                 */
                dataQuery.put("appid", config.getAppID());
                dataQuery.put("mch_id", config.getMchID());
                dataQuery.put("nonce_str", WXPayUtil.generateNonceStr());
                dataQuery.put("out_trade_no", message.getOutTradeNo());
                String signQuery = WXPayUtil.generateSignature(dataQuery, PayConst.wxPayKey,
                    WXPayConstants.SignType.HMACSHA256);
                dataQuery.put("sign", signQuery);
    
                Map<String, String> respMap = wxPay.orderQuery(dataQuery);
                Set<String> keys = respMap.keySet();
                for (String key : keys)
                {
                    return "return_code".equals(key) && "SUCCESS".equals(respMap.get("return_code"))
                           && "SUCCESS".equals(respMap.get("result_code"))
                           && "SUCCESS".equals(respMap.get("state_code"));
                }
    
            }
            catch (Exception ex)
            {
                log.error("wxPaySubmit={}", ex.toString());
            }
            return false;
        }
    }
    
    

    然后是我们的controller类

    package com.ym.odoo.controller.pay;
    
    
    import com.ym.odoo.bean.pay.DoWXMessage;
    import com.ym.odoo.bean.pay.DoWXResp;
    import com.ym.odoo.service.pay.WXPayService;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    
    /**
     * 微信支付前端请求
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Slf4j
    @AllArgsConstructor
    @RestController
    @RequestMapping("/wx")
    public class DoWXPayAction
    {
    
        private final WXPayService wxPayService;
        
        /**
         * 请求微信后台的路由
         * 
         * @param message
         *            请求数据封装成 TranctionPaeams 对象
         * @return 返回一个 wxPayService 对象
         */
        @PostMapping("/ordersPay")
        public DoWXResp ordersPay(@RequestBody DoWXMessage message)
        {
            log.debug("code:message={}", message);
            return wxPayService.wxPaySubmit(message);
    
        }
    }
    
    

    前端路由访问我们的接口,我们返回一个DoWXResp对象。
    至此,后端微信支付我们已经完成了。其中几个点需要说一下,
    在service里面,刚开始我没有实现轮询查询的接口,用FireFox的RestClient测试接口,调用微信支付,完成支付了,多测试几次之后出现问题了。付款成功了,但是我们返回给前端的是fail,原来是要分为两种情况,如果是免密支付的话,那我们调用支付接口的时候,当前微信返回给我们的return_code 和 result_code 都是SUCCESS,所以我们付款成功了,但是如果是非免密支付的话,刚开始微信返回给我们的return_code 是SUCCESS,但是result_code 是fail,err_code 是USERPAYING,这个时候用户正在验证金额,输入密码呢,如果我们返回给前端fail的话不久逻辑出问题了么。所以我们需要每隔10s拿着我们的订单号去调用微信给我们的查询订单接口orderQuery去查一下微信服务器看看用户付款成功了没?如果付款成功了,那我们就返回success,如果查了5次还是付款不成功,那我们就给前端返回fail,然后让前端重新下单,重新发起支付。

    前端,这个等我支付宝支付后端写完一起写

    支付宝支付

    支付宝支付的开发文档真的是详细全面,比微信好多了。我们需要称之为当面付里面的条码支付。开发文档地址:https://opendocs.alipay.com/open/194/105072

    后端

    1. 第一步我们需要先登录支付宝开放平台(open.alipay.com),在开发者中心中创建登记我们的应用,并提交审核,审核通过后会为我们生成应用唯一标识(APPID),并且可以申请开通开放产品使用权限。通过 APPID 我们的应用才能调用开放产品的接口能力。然后是配置密钥
      image.png
      这个支付宝说的很详细。主要三个
      APP_PRIVATE_KEY:应用私钥
      APP_PUBLIC_KEY:应用公钥
      ALIPAY_PUBLIC_KEY:支付宝公钥
      image.png
      这三个key的作用我们需要先明确。
      配置密钥的地址:(https://opendocs.alipay.com/open/291/105971)

    我们按照文档一步一步配置就行,这个不多说了。
    设置密钥到支付宝平台:https://open.alipay.com/platform/keyManage.htm

    image.png

    2.现在我们下载demo
    https://opendocs.alipay.com/open/54/103419/

    image.png

    2.1 这个是我们下载的java Demo,Demo是在支付宝标准 SDK 的基础上再做了一层封装。我们需要的是TradePaySDK这个,当然你可以直接在TradePayDemo里面测试。或许可以直接下载支付宝sdk然后gridle或者maven里面引入就直接可以调用了,我自己为了方便后面分析代码就直接将它sdk的代码也集成到项目里面了。


    image.png

    2.2 当然需要注意包名的更改,还有需要在gradle里面添加必要的包依赖

        implementation(
                "org.springframework.boot:spring-boot-starter-data-redis-reactive",
                "io.projectreactor:reactor-core",
                "org.mybatis.spring.boot:mybatis-spring-boot-starter:$mybatisSpringVersion",
                "org.postgresql:postgresql:$postgresqlVersion",
                "com.baomidou:mybatis-plus:$mybatisPlusVersion",
                "com.alibaba:druid-spring-boot-starter:$druidVersion",
                "com.alibaba:fastjson:$fastjsonVersion",
                "io.github.yedaxia:japidocs:$japidocsVersion",
                "org.apache.httpcomponents:httpclient:$httpclientVersion",
                "com.alipay.sdk:alipay-sdk-java:$alipaySdkJavaVersion",
                "commons-logging:commons-logging:$commonsLoggingVersion",
                "commons-configuration:commons-configuration:$commonsConfigurationVersion",
                "com.google.zxing:core:$zxingCoreVersion",
                "com.google.code.gson:gson:$gsonVersion"
    
        )
    

    2.3 方便管理,我们将版本都放在了外面的build.gradle里面了。

    buildscript {
        ext {
            lombokVersion = "1.18.12"
            fastjsonVersion = "1.2.72"
            metricsSpringVersion = "3.1.3"
            swaggerVersion = "3.0.0"
            swaggerModelsVersion = "1.6.2"
            postgresqlVersion = "42.2.15"
            mybatisSpringVersion = "2.1.3"
            mybatisPlusVersion = "3.3.2"
            druidVersion = "1.1.23"
            alibabaNacosVersion = "2.2.1.RELEASE"
            springCloudVersion = "Hoxton.SR6"
            japidocsVersion = "1.4.2"
            httpclientVersion = "4.5.3"
            alipaySdkJavaVersion = "3.3.4.ALL"
            commonsLoggingVersion = "1.1.1"
            commonsConfigurationVersion = "1.10"
            zxingCoreVersion = "2.1"
            gsonVersion = "2.8.6"
    
        }
    }
    
    

    2.4 还有最重要的一个就是我们的zfbinfo.properties文件,我把它放在resource目录下了。


    image.png

    里面需要填写申请配置好的密钥信息

    # 支付宝网关名、partnerId和appId
    open_api_domain = https://openapi.alipay.com/gateway.do
    mcloud_api_domain = http://mcloudmonitor.com/gateway.do
    pid = 00000000000
    appid = 0000000000
    
    # RSA私钥、公钥和支付宝公钥
    private_key = 0000000000
    public_key = 0000000000
    #SHA1withRsa对应支付宝公钥
    #alipay_public_key = 0000000000
    #SHA256withRsa对应支付宝公钥
    alipay_public_key = 0000000000
    
    # 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
    sign_type = RSA2
    # 当面付最大查询次数和查询间隔(毫秒)
    max_query_retry = 5
    query_duration = 5000
    
    # 当面付最大撤销次数和撤销间隔(毫秒)
    max_cancel_retry = 3
    cancel_duration = 2000
    
    # 交易保障线程第一次调度延迟和调度间隔(秒)
    heartbeat_delay = 5
    heartbeat_duration = 900
    
    

    我用0000000000标识的就是公钥,私钥,还有app的信息。

    1. 编写我们的controller 和 service
    controller结构.png
    package com.ym.odoo.controller.pay;
    
    
    import com.ym.odoo.bean.pay.DoAliPayMessage;
    import com.ym.odoo.bean.pay.DoAliPayResp;
    import com.ym.odoo.service.pay.AliPayService;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    
    /**
     * 支付宝支付前端请求
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    
    @Slf4j
    @AllArgsConstructor
    @RestController
    @RequestMapping("/alipay")
    public class DoAliPayAction
    {
    
        private final AliPayService aliPayService;
    
        /**
         * 支付宝支付路由
         * 
         * @param message
         *            前端传递的请求数据
         * @return DoAlipayResp对象
         */
        @PostMapping("/ordersPay")
        public DoAliPayResp ordersPay(@RequestBody DoAliPayMessage message)
        {
    
            log.debug("ordersPay:message={}", message);
            return aliPayService.tradeAliPay(message);
        }
    }
    
    

    编写service接口AliPayService,以及实现类AliPayServiceImpl。

    package com.ym.odoo.service.pay;
    
    
    import com.ym.odoo.bean.pay.DoAliPayMessage;
    import com.ym.odoo.bean.pay.DoAliPayResp;
    
    
    /**
     * 支付宝支付接口
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    public interface AliPayService
    {
    
        /**
         * 发送请求给支付宝
         * 
         * @param message
         * @return
         */
        DoAliPayResp tradeAliPay(DoAliPayMessage message);
    }
    
    
    package com.ym.odoo.service.impl.pay;
    
    
    import com.ym.odoo.bean.pay.DoAliPayMessage;
    import com.ym.odoo.bean.pay.DoAliPayResp;
    import com.ym.odoo.constant.PayConst;
    import com.ym.odoo.sdk.alipay.service.impl.AlipayTradeServiceImpl;
    import com.ym.odoo.service.pay.AliPayService;
    import com.ym.odoo.sdk.alipay.model.ExtendParams;
    import com.ym.odoo.sdk.alipay.model.GoodsDetail;
    import com.ym.odoo.sdk.alipay.model.builder.*;
    import com.ym.odoo.sdk.alipay.model.result.AlipayF2FPayResult;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    
    /**
     * 发送请求到支付宝
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Slf4j
    @AllArgsConstructor
    @Service
    public class AliPayServiceImpl implements AliPayService
    {
        /**
         * 当面付2.0支付
         *
         * @param message
         *            DoAliPayMessage对象
         * @return DoAliPayResp对象
         */
        @Override
        public DoAliPayResp tradeAliPay(DoAliPayMessage message)
        {
            log.debug("tradeAliPay:message={}", message);
    
            DoAliPayResp doAliPayResp = new DoAliPayResp();
    
            // 通过从前端传输过来的数据填充需要发送给支付宝API的数据
            String outTradeNo = message.getOutTradeNo();// 订单号
            String subject = message.getSubject();// 商品描述
            String totalAmount = message.getTotalAmount();// 总金额
            String authCode = message.getAuthCode();// 付款码
            String body = message.getBody();// 商品内容
            String storeId = message.getStoreId(); // 门店编号
            String operatorId = message.getOperatorId(); // 操作员编号
    
            /*--------------------------------上面这些是前端传输的数据----------------------------------------------------*/
    
            /*--------------------------------下面这些是后端直接确定的数据----------------------------------------------------*/
    
            // 商品明细列表,需填写购买商品详细信息,
            List<GoodsDetail> goodsDetailList = new ArrayList<>();
            // 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
            GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "xxx面包", 1000, 1);
            // 创建好一个商品后添加至商品明细列表
            goodsDetailList.add(goods1);
    
            // 继续创建并添加第一条商品信息,用户购买的产品为“黑人牙刷”,单价为5.00元,购买了两件
            GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "xxx牙刷", 500, 2);
            goodsDetailList.add(goods2);
    
            // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
            // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
            String sellerId = "";
    
            // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
            String providerId = PayConst.providerId;
            ExtendParams extendParams = new ExtendParams();
            extendParams.setSysServiceProviderId(providerId);
    
            // 支付超时,线下扫码交易定义为5分钟
            String timeoutExpress = PayConst.timeoutExpress;
    
            // 创建条码支付请求builder,设置请求参数
            AlipayTradePayRequestBuilder builder = new AlipayTradePayRequestBuilder()
                    .setOutTradeNo(outTradeNo).setSubject(subject).setAuthCode(authCode).setTotalAmount(
                            totalAmount).setStoreId(storeId).setBody(body).setOperatorId(
                            operatorId).setExtendParams(extendParams).setSellerId(
                            sellerId).setGoodsDetailList(goodsDetailList).setTimeoutExpress(
                            timeoutExpress);
    
    
            // 调用tradePay方法获取当面付应答
            PayConst.tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
            AlipayF2FPayResult result = PayConst.tradeService.tradePay(builder);
    
            log.debug("AlipayF2FPayResult:builder={}", result);
            // 对返回的应答结果做处理
            doAliPayResp.setOutTradeNo(result.getResponse().getOutTradeNo());
            doAliPayResp.setTotalAmount(result.getResponse().getTotalAmount());
            switch (result.getTradeStatus())
            {
                case SUCCESS:
                    log.info("支付宝支付成功");
                    doAliPayResp.setStatus("SUCCESS");
                    break;
    
                case FAILED:
                    log.error("支付宝支付失败!!!");
                    doAliPayResp.setStatus("FAILED");
                    break;
    
                case UNKNOWN:
                    log.error("系统异常,订单状态未知!!!");
                    doAliPayResp.setStatus("UNKNOWN");
                    break;
    
                default:
                    log.error("不支持的交易状态,交易返回异常!!!");
                    doAliPayResp.setStatus("error");
                    doAliPayResp.setOutTradeNo(null);
                    doAliPayResp.setTotalAmount(null);
                    break;
            }
    
            return doAliPayResp;
    
        }
    }
    
    

    支付宝支付因为sdk里面实现了TradeHbRunner这个类,实现了轮询查询。所以不需要我们再编写轮询查询。

    image.png
    package com.ym.odoo.sdk.alipay;
    
    import com.ym.odoo.sdk.alipay.model.builder.AlipayHeartbeatSynRequestBuilder;
    import com.ym.odoo.sdk.alipay.model.hb.*;
    import com.ym.odoo.sdk.alipay.service.AlipayMonitorService;
    import com.ym.odoo.sdk.alipay.service.impl.hb.AbsHbRunner;
    import com.ym.odoo.sdk.alipay.service.impl.hb.HbQueue;
    import com.ym.odoo.sdk.alipay.utils.Utils;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    /**
     执行调度,主要任务由两个线程完成,交易线程(调用当面付2.0接口)和交易保障线程(轮询),具体需要做的事情
     1.当面付程序每执行完一笔交易后将交易结果保存在临时队列
     2.轮询线程读取临时队列,获取基础采集信息和最多30条trade_info信息,调用支付宝monitor.heartbeat.syn接口
     示例代码仅封装了如何调用该接口api,采集数据,比如采集网络信息、交易耗时、异常信息等,需要系统商开发者自行完成。
     */
    /**
     * @program: odoo-system
     * @description
     * @author: liupeng
     * @email: liup@hdkernel.com
     * @create: 2020-09-03 13:44
     **/
    public class TradeHbRunner extends AbsHbRunner {
    
        public TradeHbRunner(AlipayMonitorService monitorService) {
            super(monitorService);
        }
    
        @Override
        public String getAppAuthToken() {
            // 对于系统商,如果是为了商户开发监控保障接口,则需要传此值,否则如果为系统商自己做交易保障接口开发,则可不传。
            return null;
        }
    
        @Override
        public AlipayHeartbeatSynRequestBuilder getBuilder() {
            // 系统商使用的交易信息格式,json字符串类型,从交易队列中获取
            List<SysTradeInfo> sysTradeInfoList = HbQueue.poll();
    
            // 异常信息的采集,系统商自行完成
            List<ExceptionInfo> exceptionInfoList = new ArrayList<ExceptionInfo>();
            //        exceptionInfoList.add(ExceptionInfo.HE_SCANER);
            //        exceptionInfoList.add(ExceptionInfo.HE_PRINTER);
            //        exceptionInfoList.add(ExceptionInfo.HE_OTHER);
    
            AlipayHeartbeatSynRequestBuilder builder = new AlipayHeartbeatSynRequestBuilder()
                    .setProduct(Product.FP).setType(Type.CR).setEquipmentId("cr1000001")
                    .setEquipmentStatus(EquipStatus.NORMAL).setTime(Utils.toDate(new Date()))
                    .setStoreId("store10001").setMac("0a:00:27:00:00:00").setNetworkType("LAN")
                    .setProviderId("2088731702963439") // 设置系统商pid
                    .setSysTradeInfoList(sysTradeInfoList) // 系统商同步trade_info信息
                    .setExceptionInfoList(exceptionInfoList) // 填写异常信息,如果有的话
                    ;
            return builder;
        }
    }
    
    
    

    为了防止代码中出现魔鬼数字。我们将支付里面需要的一些常量提取出来,写在constant包下


    image.png
    package com.ym.odoo.constant;
    
    
    import com.ym.odoo.sdk.alipay.service.AlipayTradeService;
    
    
    /**
     * 支付公共数据
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    public class PayConst
    {
        /**
         * 使用Configs提供的默认参数 AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
         */
        public static AlipayTradeService tradeService;
    
        /**
         * 支付宝分配的系统商编号
         */
        public static String providerId = "xxxxxxxxxxxxxxxxxxxx";
    
        /**
         * 支付宝支付超时,线下扫码交易定义为5分钟
         */
        public static String timeoutExpress = "5m";
    
        /**
         * 用来生成微信支付预支付订单号的签名sign的key
         */
        public static String wxPayKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    
        /**
         * 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
         */
        public static String spbillIp = "0.0.0.0";
    
    }
    
    

    同微信支付,我们实现了两个对象来存储支付宝支付的相关数据
    DoAliPayMessage 和 DoAliPayResp。

    package com.ym.odoo.bean.pay;
    
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    
    /**
     * 接受来自前端传输过来的有关支付宝支付的信息
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Data
    public class DoAliPayMessage implements Serializable
    {
        /**
         * 序列号
         */
        private static final long serialVersionUID = 9118721945528267835L;
    
        /**
         * 订单号
         */
        private String outTradeNo;
    
        /**
         * 订单标题
         */
        private String subject;
    
        /**
         * 总金额
         */
        private String totalAmount;
    
        /**
         * 付款码
         */
        private String authCode;
    
        /**
         * 描述
         */
        private String body;
    
        /**
         * 门店编号
         */
        private String storeId;
    
        /**
         * 商户操作员编号,添加此参数可以为商户操作员做销售统计
         */
        private String operatorId;
    
    }
    
    
    package com.ym.odoo.bean.pay;
    
    
    import lombok.Data;
    
    
    /**
     * 返回给门店的信息
     *
     * @author 刘鹏
     * @version 2020-09-04 16:41
     */
    @Data
    public class DoAliPayResp
    {
        /**
         * 订单号
         */
        private String outTradeNo;
    
        /**
         * 总金额
         */
        private String totalAmount;
    
        /**
         * 支付状态
         */
        private String status;
    }
    
    

    到此我们后端告一段落

    odoo前端

    好了,后端接口写好了。现在我们看看前端,因为要集成到odoo里面,所以涉及到odoo的东西有点复杂。
    odoo的pos模块:


    image.png

    我们需要实现这两个按钮完成微信和支付宝支付。
    修改的就是这三个文件。


    image.png

    先找到在js代码中的点击事件,在point_of_sale/static/src/js/screens.js中


    image.png
        click_paymentmethods: function(id) {
            // id分别是1,2,3,4 代表现金,银行,支付宝。微信
            var payment_method = this.pos.payment_methods_by_id[id];
            var order = this.pos.get_order();
            var selfClick = this;
    
            if(id === 1 ){
                if (order.electronic_payment_in_progress()) {
                    this.gui.show_popup('error', {
                        'title': _t('Error'),
                        'body': _t('There is already an electronic payment in progress.'),
                    });
                } else {
                    order.add_paymentline(payment_method);
                    this.reset_input();
    
                    this.payment_interface = payment_method.payment_terminal;
                    if (this.payment_interface) {
                        order.selected_paymentline.set_payment_status('pending');
                    }
                    this.render_paymentlines();
                }
            }
            if(id === 3){
                this.gui.show_popup('scannerAliPay', {
                    'title': _t('请扫描二维码'),
                    "codenumberAliPay":_t(''),
                    "order": order,
                    "payment_method" : payment_method,
                    "selfClick" : selfClick,
                });
            }
            if(id === 4){
                this.gui.show_popup('scannerWXpay', {
                    'title': _t('请扫描二维码'),
                    "codenumberWX":_t(''),
                    "order": order,
                    "payment_method" : payment_method,
                    "selfClick" : selfClick,
                });
            }
        },
    

    其中1是现金,之前odoo就已经集成好了,现在我们看3和4,分别对应微信和支付宝。
    这两个点击事件都是弹出一个popup。实现细节在point_of_sale/static/src/js/popup.js里面。

    
        var ScannerAliPayWidget = TextInputPopupWidget.extend({
            template: 'ScannerAliPayWidget',
    
            click_confirm: function () {
                let self = this;
                let value = self.$('input,text').val();
                let outOrder = self.options.order;
                let payment_method = self.options.payment_method;
                let selfClick = self.options.selfClick;
                /**
                 * 1、需要将付款吗和订单号一起发送到jsva后端去处理支付宝的扣款,并且回调
                 * 2、需要将当前页的订单发送到odoo后台去存储到数据库
                 */
                /**
                 * 2、将value和order里面的订单号一起发送到java后台。
                 */
                let outTradeNo = outOrder.name;
                let saleName = outOrder.employee.name;
                let totalAmount = outOrder.get_total_with_tax();
                let store = outOrder.pos.config.name;
                let operator = store + "/" + saleName;
    
                let oderAliReq = {
                    "outTradeNo": outTradeNo,
                    "subject": "腾营信息pos门店当面付",
                    "totalAmount": totalAmount,
                    "authCode": value,
                    "body": "腾营信息pos门店当面付",
                    "storeId": store,
                    "operatorId": operator,
                };
    
                $.ajax({
                    type: "post",
                    url: "/alipay/ordersPay.do",
                    data: JSON.stringify(oderAliReq),
                    dataType: "json",
                    contentType: 'application/json;charset=utf-8',
                    success: function (retMsg) {
                        if ("SUCCESS" === retMsg.status) {
                            /**
                             * 1、调用push_order方法完成后端数据传输和存储数据库
                             */
                            self.pos.push_order(outOrder);
                            alert("success");
    
                            outOrder.add_paymentline(payment_method);
                            selfClick.reset_input();
    
                            selfClick.payment_interface = payment_method.payment_terminal;
                            if (selfClick.payment_interface) {
                                outOrder.selected_paymentline.set_payment_status('pending');
                            }
                            selfClick.render_paymentlines();
    
                            /**
                             * 不应该刚关闭这个弹窗,应该显示一个付款成功的提示,然后跳转到打印小票的界面
                             */
                            self.gui.show_screen('receipt');
                        } else {
                            alert("false");
                        }
                    }
                });
                self.gui.close_popup();
                if (self.options.confirm) {
                    self.options.confirm.call(self, value);
                }
            },
        });
        gui.define_popup({name: 'scannerAliPay', widget: ScannerAliPayWidget});
    
    
        let scannerWXpayWidget = TextInputPopupWidget.extend({
            template: 'scannerWXpayWidget',
            click_confirm: function () {
                let self = this;
                let value = self.$('input,text').val();
                let outOrder = self.options.order;
                let payment_method = self.options.payment_method;
                let store = outOrder.pos.config.name;
                let selfClick = self.options.selfClick;
                /**
                 * 1、需要将付款吗和订单号一起发送到jsva后端去处理微信的扣款,并且回调
                 * 2、需要将当前页的订单发送到odoo后台去存储到数据库
                 */
                let outTradeNo = outOrder.name;
                let totalAmount = outOrder.get_total_with_tax();
    
                /**
                 * 微信支付不同于支付宝支付,以分为单位,并且传输的只能是整数。所以每次都要在total上乘以100,将单位从元转换成分
                 * body 格式是:店名-销售商品类目 例如:“小张南山店-超市”
                 * out_trade_no 格式中不能有空格
                 * @type {{total: number, out_trade_no: *, body: string, auth_code: *100}}
                 */
                let orderWXReq = {
                    "auth_code": value,
                    "body": "腾营信息-" + store,
                    "out_trade_no": outTradeNo.replace(/\s*/g, ""),
                    "total": totalAmount * 100,
                };
    
                $.ajax({
                    type: "post",
                    url: "/wx/ordersPay.do",
                    data: JSON.stringify(orderWXReq),
                    dataType: "json",
                    contentType: 'application/json;charset=utf-8',
                    success: function (reWXMsg) {
                        if ("success" === reWXMsg.resultCode ) {
                            /**
                             * 1、调用push_order方法完成后端数据传输和存储数据库
                             */
                            self.pos.push_order(outOrder);
                            alert("success");
    
                            outOrder.add_paymentline(payment_method);
                            selfClick.reset_input();
    
                            selfClick.payment_interface = payment_method.payment_terminal;
                            if (selfClick.payment_interface) {
                                outOrder.selected_paymentline.set_payment_status('pending');
                            }
                            selfClick.render_paymentlines();
    
                            /**
                             * 不应该刚关闭这个弹窗,应该显示一个付款成功的提示,然后跳转到打印小票的界面
                             */
    
                            self.gui.show_screen('receipt');
                        } else {
                            alert("false");
                        }
                    }
                });
                self.gui.close_popup();
                if (self.options.confirm) {
                    self.options.confirm.call(self, value);
                }
            },
        });
        gui.define_popup({name: 'scannerWXpay', widget: scannerWXpayWidget});
    
    

    xm文件

    
        <t t-name="ScannerAliPayWidget">
            <div role="dialog" class="modal-dialog">
                <div class="popup popup-textinput">
                    <header class="title"><t t-esc=" widget.options.title || '' " /></header>
                    <input type="text"
                           t-att-cid="codenumberAliPay"
                           t-value="widget.options.codenumber"
                           placeholder="请扫描支付宝二维码"/>
                    <footer class="footer">
                        <div class="button confirm">
                            Ok
                        </div>
                        <div class="button cancel">
                            Cancel
                        </div>
                    </footer>
                </div>
            </div>
        </t>
    
        <t t-name="scannerWXpayWidget">
            <div role="dialog" class="modal-dialog">
                <div class="popup popup-textinput">
                    <header class="title"><t t-esc=" widget.options.title || '' " /></header>
                    <input type="text"  t-value="widget.options.codenumberWX" placeholder="请扫描微信二维码"/>
                    <footer class="footer">
                        <div class="button confirm">
                            Ok
                        </div>
                        <div class="button cancel">
                            Cancel
                        </div>
                    </footer>
                </div>
            </div>
        </t>
    
    

    这两个widget实现的弹窗效果:


    image.png
    image.png

    用扫码枪扫码input接受到二维码信息,


    image.png

    然后点击确认,调用popup.js里面的
    click_confirm: function () {}方法,在这个方法里面,先实现用ajax去访问我们后端的接口,完成付款。
    这儿需要注意的是我们微信和支付包接口需要的数据格式,json数据格式,并且微信和支付宝不一样的是微信的金额单位是分,全是整数。而支付宝单位是元。所以微信拿到的金额需要乘以100,转换成分的单位。还有微信的订单号里面不能有空格,我们将订单号更改

    "out_trade_no": outTradeNo.replace(/\s*/g, "")
    

    成功之后调用odoo自己的方法去数据库添加订单信息

                            /**
                             * 1、调用push_order方法完成后端数据传输和存储数据库
                             */
                            self.pos.push_order(outOrder);
                            alert("success");
    
                            outOrder.add_paymentline(payment_method);
                            selfClick.reset_input();
    
                            selfClick.payment_interface = payment_method.payment_terminal;
                            if (selfClick.payment_interface) {
                                outOrder.selected_paymentline.set_payment_status('pending');
                            }
                            selfClick.render_paymentlines();
    
                            /**
                             * 不应该刚关闭这个弹窗,应该显示一个付款成功的提示,然后跳转到打印小票的界面
                             */
    
                            self.gui.show_screen('receipt');
    

    这样我们就完成了整个流程。最后我个人觉的odoo这个ERP系统是的确不错,大部分模块都是开源的,他自己封装的前端框架也特别厉害,需要花点时间去好好摸索摸索。

    相关文章

      网友评论

          本文标题:微信支付宝当面付集成

          本文链接:https://www.haomeiwen.com/subject/alzaektx.html