这两天公司的项目有一个是集成微信支付和支付宝支付到项目中,对应的都是门店扫码枪扫码。顾客展示自己的二维码给店员,店员用扫码枪扫描顾客出示的二维码,然后将条码和订单信息一起发送到后台处理。最后完成付款。微信和支付宝的后台我用的都是java,前端项目是用的odoo开源项目,我需要在前端将微信支付和支付宝支付集成到odoo的pos模块里面(这块挺折磨人的。。。)。不多说了,去解决问题。
微信支付
支付模式在微信上称之为刷卡支付,也称之为付款码支付。开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
后端
这个图就是我们要实现的全部业务逻辑。现在我们需要去细细去看看开发文档里面的流程。
1、首先我们需要去微信公众平台去申请线下扫码支付的接入,然后拿到我们开发需要的几个信息。app_id(微信分配的公众账号ID(企业号corpid即为此appId)),mch_id(商户号),还有一个重要的key,是我们自己配的,这个大家接入时候就都知道了。
2、现在我们需要下载sdk,地址是https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1
下载解压后项目是这样
解压之后大概这样,我们将我们用的文件放到我们自己项目中,我们后台代码结构是这样,为了不影响别人开发以及条理清晰,我自己建了一个sdk包,将微信支付sdk放在这个下面
然后我们需要自己写一个MyWXConfig来继承微信的WXPayConfig,来将我们在微信商户平台申请拿到的数据放在里面。就是上面我说的那几个东西。
这是我们需要的sdk引入完成。
3、编写我们的代码去调用微信支付接口
代码逻辑还是用的spring mvc模式
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
后端
- 第一步我们需要先登录支付宝开放平台(open.alipay.com),在开发者中心中创建登记我们的应用,并提交审核,审核通过后会为我们生成应用唯一标识(APPID),并且可以申请开通开放产品使用权限。通过 APPID 我们的应用才能调用开放产品的接口能力。然后是配置密钥
这个支付宝说的很详细。主要三个
APP_PRIVATE_KEY:应用私钥
APP_PUBLIC_KEY:应用公钥
ALIPAY_PUBLIC_KEY:支付宝公钥
这三个key的作用我们需要先明确。
配置密钥的地址:(https://opendocs.alipay.com/open/291/105971)
我们按照文档一步一步配置就行,这个不多说了。
设置密钥到支付宝平台:https://open.alipay.com/platform/keyManage.htm
2.现在我们下载demo
https://opendocs.alipay.com/open/54/103419/
2.1 这个是我们下载的java Demo,Demo是在支付宝标准 SDK 的基础上再做了一层封装。我们需要的是TradePaySDK这个,当然你可以直接在TradePayDemo里面测试。或许可以直接下载支付宝sdk然后gridle或者maven里面引入就直接可以调用了,我自己为了方便后面分析代码就直接将它sdk的代码也集成到项目里面了。
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目录下了。
里面需要填写申请配置好的密钥信息
# 支付宝网关名、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的信息。
- 编写我们的controller 和 service
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这个类,实现了轮询查询。所以不需要我们再编写轮询查询。
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包下
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模块:
我们需要实现这两个按钮完成微信和支付宝支付。
修改的就是这三个文件。
先找到在js代码中的点击事件,在point_of_sale/static/src/js/screens.js中
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实现的弹窗效果:
用扫码枪扫码input接受到二维码信息,
然后点击确认,调用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系统是的确不错,大部分模块都是开源的,他自己封装的前端框架也特别厉害,需要花点时间去好好摸索摸索。