微信支付宝当面付集成

这两天公司的项目有一个是集成微信支付和支付宝支付到项目中,对应的都是门店扫码枪扫码。顾客展示自己的二维码给店员,店员用扫码枪扫描顾客出示的二维码,然后将条码和订单信息一起发送到后台处理。最后完成付款。微信和支付宝的后台我用的都是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 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 respMap = wxpay.microPay(data);
            log.info("resp:{}", respMap);

            // 遍历微信返回给我们的resmap,如果return_code == “SUCCESS”并且 result_code == “SUCCESS”,返回给前端支付成功的消息
            Set 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 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 respMap = wxPay.orderQuery(dataQuery);
            Set 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 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: [email protected]
 * @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 sysTradeInfoList = HbQueue.poll();

        // 异常信息的采集,系统商自行完成
        List exceptionInfoList = new ArrayList();
        //        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文件


    
        
    

    
        
    

这两个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系统是的确不错,大部分模块都是开源的,他自己封装的前端框架也特别厉害,需要花点时间去好好摸索摸索。

你可能感兴趣的:(微信支付宝当面付集成)