pom.xml
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
XStreamInitializer:
package cn.edu360.bike.test; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; import com.thoughtworks.xstream.security.NullPermission; import com.thoughtworks.xstream.security.PrimitiveTypePermission; import java.io.Writer; public class XStreamInitializer { public static XStream getInstance() { XStream xstream = new XStream(new PureJavaReflectionProvider(), new XppDriver() { @Override public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out, getNameCoder()) { protected String PREFIX_CDATA = "<![CDATA["; protected String SUFFIX_CDATA = "]]>"; protected String PREFIX_MEDIA_ID = "<MediaId>"; protected String SUFFIX_MEDIA_ID = "</MediaId>"; @Override protected void writeText(QuickWriter writer, String text) { if (text.startsWith(this.PREFIX_CDATA) && text.endsWith(this.SUFFIX_CDATA)) { writer.write(text); } else if (text.startsWith(this.PREFIX_MEDIA_ID) && text.endsWith(this.SUFFIX_MEDIA_ID)) { writer.write(text); } else { super.writeText(writer, text); } } @Override public String encodeNode(String name) { return name;//防止将_转换成__ } }; } }); xstream.ignoreUnknownElements(); xstream.setMode(XStream.NO_REFERENCES); xstream.addPermission(NullPermission.NULL); xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); return xstream; } }
BaseWxPayResult:
package cn.edu360.bike.test; import com.google.common.base.Joiner; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.*; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Data public abstract class BaseWxPayResult { /** * 返回状态码. */ @XStreamAlias("return_code") protected String returnCode; /** * 返回信息. */ @XStreamAlias("return_msg") protected String returnMsg; //当return_code为SUCCESS的时候,还会包括以下字段: /** * 业务结果. */ @XStreamAlias("result_code") private String resultCode; /** * 错误代码. */ @XStreamAlias("err_code") private String errCode; /** * 错误代码描述. */ @XStreamAlias("err_code_des") private String errCodeDes; /** * 公众账号ID. */ @XStreamAlias("appid") private String appid; /** * 商户号. */ @XStreamAlias("mch_id") private String mchId; /** * 服务商模式下的子公众账号ID. */ @XStreamAlias("sub_appid") private String subAppId; /** * 服务商模式下的子商户号. */ @XStreamAlias("sub_mch_id") private String subMchId; /** * 随机字符串. */ @XStreamAlias("nonce_str") private String nonceStr; /** * 签名. */ @XStreamAlias("sign") private String sign; //以下为辅助属性 /** * xml字符串. */ private String xmlString; /** * xml的Document对象,用于解析xml文本. */ private Document xmlDoc; /** * 将xml字符串转换成Document对象,以便读取其元素值. */ private Document getXmlDoc() { if (this.xmlDoc != null) { return this.xmlDoc; } try { this.xmlDoc = DocumentBuilderFactory .newInstance() .newDocumentBuilder() .parse(new ByteArrayInputStream(this.xmlString.getBytes("UTF-8"))); return xmlDoc; } catch (SAXException | IOException | ParserConfigurationException e) { throw new RuntimeException("非法的xml文本内容:" + this.xmlString); } } /** * 将bean通过保存的xml字符串转换成map. */ public Map<String, String> toMap() { if (StringUtils.isBlank(this.xmlString)) { throw new RuntimeException("xml数据有问题,请核实!"); } Map<String, String> result = new HashMap<>(); Document doc = this.getXmlDoc(); try { NodeList list = (NodeList) XPathFactory.newInstance().newXPath() .compile("/xml/*") .evaluate(doc, XPathConstants.NODESET); int len = list.getLength(); for (int i = 0; i < len; i++) { result.put(list.item(i).getNodeName(), list.item(i).getTextContent()); } } catch (XPathExpressionException e) { throw new RuntimeException("非法的xml文本内容:" + xmlString); } return result; } /** * 从xml字符串创建bean对象. */ public static <T extends BaseWxPayResult> T fromXML(String xmlString, Class<T> clz) { XStream xstream = XStreamInitializer.getInstance(); xstream.processAnnotations(clz); T result = (T) xstream.fromXML(xmlString); result.setXmlString(xmlString); return result; } /** * 获取xml中元素的值. */ public String getXmlValue(String... path) { Document doc = this.getXmlDoc(); String expression = String.format("/%s//text()", Joiner.on("/").join(path)); try { return (String) XPathFactory .newInstance() .newXPath() .compile(expression) .evaluate(doc, XPathConstants.STRING); } catch (XPathExpressionException e) { throw new RuntimeException("未找到相应路径的文本:" + expression); } } /** * 获取xml中元素的值,作为int值返回. */ Integer getXmlValueAsInt(String... path) { String result = this.getXmlValue(path); if (StringUtils.isBlank(result)) { return null; } return Integer.valueOf(result); } }
WxPayOrderQueryResult:
package cn.edu360.bike.test; import com.google.common.collect.Lists; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.*; import java.util.List; /** * <pre> * 查询订单 返回结果对象 * Created by Binary Wang on 2016-10-24. * 注释中各行每个字段描述对应如下: * <li>字段名 * <li>变量名 * <li>是否必填 * <li>类型 * <li>示例值 * <li>描述 * </pre> * */ @Data @NoArgsConstructor @XStreamAlias("xml") public class WxPayOrderQueryResult extends BaseWxPayResult { /** * <pre> * 字段名:营销详情. * 变量名:promotion_detail * 是否必填:否,单品优惠才有 * 类型:String(6000) * 示例值:[{"promotion_detail":[{"promotion_id":"109519","name":"单品惠-6","scope":"SINGLE","type":"DISCOUNT","amount":5,"activity_id":"931386","wxpay_contribute":0,"merchant_contribute":0,"other_contribute":5,"goods_detail":[{"goods_id":"a_goods1","goods_remark":"商品备注","quantity":7,"price":1,"discount_amount":4},{"goods_id":"a_goods2","goods_remark":"商品备注","quantity":1,"price":2,"discount_amount":1}]}]} * 描述:单品优惠专用参数,详见https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_201&index=3 * </pre> */ @XStreamAlias("promotion_detail") private String promotionDetail; /** * <pre>设备号 * device_info * 否 * String(32) * 013467007045764 * 微信支付分配的终端设备号, * </pre> */ @XStreamAlias("device_info") private String deviceInfo; /** * <pre>用户标识 * openid * 是 * String(128) * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o * 用户在商户appid下的唯一标识 * </pre> */ @XStreamAlias("openid") private String openid; /** * <pre>是否关注公众账号 * is_subscribe * 否 * String(1) * Y * 用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效 * </pre> */ @XStreamAlias("is_subscribe") private String isSubscribe; /** * <pre>交易类型 * trade_type * 是 * String(16) * JSAPI * 调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP,MICROPAY,详细说明见参数规定 * </pre> */ @XStreamAlias("trade_type") private String tradeType; /** * <pre>交易状态 * trade_state * 是 * String(32) * SUCCESS * SUCCESS—支付成功,REFUND—转入退款,NOTPAY—未支付,CLOSED—已关闭,REVOKED—已撤销(刷卡支付),USERPAYING--用户支付中,PAYERROR--支付失败(其他原因,如银行返回失败) * </pre> */ @XStreamAlias("trade_state") private String tradeState; /** * <pre>付款银行 * bank_type * 是 * String(16) * CMC * 银行类型,采用字符串类型的银行标识 * </pre> */ @XStreamAlias("bank_type") private String bankType; /** * <pre>订单金额 * total_fee * 是 * Int * 100 * 订单总金额,单位为分 * </pre> */ @XStreamAlias("total_fee") private Integer totalFee; /** * <pre>应结订单金额 * settlement_total_fee * 否 * Int * 100 * 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 * </pre> */ @XStreamAlias("settlement_total_fee") private Integer settlementTotalFee; /** * <pre>货币种类 * fee_type * 否 * String(8) * CNY * 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 * </pre> */ @XStreamAlias("fee_type") private String feeType; /** * <pre>现金支付金额 * cash_fee * 是 * Int * 100 * 现金支付金额订单现金支付金额,详见支付金额 * </pre> */ @XStreamAlias("cash_fee") private Integer cashFee; /** * <pre>现金支付货币类型 * cash_fee_type * 否 * String(16) * CNY * 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 * </pre> */ @XStreamAlias("cash_fee_type") private String cashFeeType; /** * <pre>代金券金额 * coupon_fee * 否 * Int * 100 * “代金券”金额<=订单金额,订单金额-“代金券”金额=现金支付金额,详见支付金额 * </pre> */ @XStreamAlias("coupon_fee") private Integer couponFee; /** * <pre>代金券使用数量 * coupon_count * 否 * Int * 1 * 代金券使用数量 * </pre> */ @XStreamAlias("coupon_count") private Integer couponCount; private List<Coupon> coupons; /** * <pre>微信支付订单号 * transaction_id * 是 * String(32) * 1009660380201506130728806387 * 微信支付订单号 * </pre> */ @XStreamAlias("transaction_id") private String transactionId; /** * <pre>商户订单号 * out_trade_no * 是 * String(32) * 20150806125346 * 商户系统的订单号,与请求一致。 * </pre> */ @XStreamAlias("out_trade_no") private String outTradeNo; /** * <pre>附加数据 * attach * 否 * String(128) * 深圳分店 * 附加数据,原样返回 * </pre> */ @XStreamAlias("attach") private String attach; /** * <pre>支付完成时间 * time_end * 是 * String(14) * 20141030133525 * 订单支付时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 * </pre> */ @XStreamAlias("time_end") private String timeEnd; /** * <pre>交易状态描述 * trade_state_desc * 是 * String(256) * 支付失败,请重新下单支付 * 对当前查询订单状态的描述和下一步操作的指引 * </pre> */ @XStreamAlias("trade_state_desc") private String tradeStateDesc; /** * 通过xml组装coupons属性内容 */ public void composeCoupons() { if (this.couponCount != null && this.couponCount > 0) { this.coupons = Lists.newArrayList(); for (int i = 0; i < this.couponCount; i++) { this.coupons.add(new Coupon(this.getXmlValue("xml/coupon_type_" + i), this.getXmlValue("xml/coupon_id_" + i), this.getXmlValueAsInt("xml/coupon_fee_" + i))); } } } @Data @Builder(builderMethodName = "newBuilder") @AllArgsConstructor public static class Coupon { /** * <pre>代金券类型 * coupon_type_$n * 否 * String * CASH * <li>CASH--充值代金券 * <li>NO_CASH---非充值代金券 * 订单使用代金券时有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_$0 * </pre> */ private String couponType; /** * <pre>代金券ID * coupon_id_$n * 否 * String(20) * 10000 * 代金券ID, $n为下标,从0开始编号 * </pre> */ private String couponId; /** * <pre>单个代金券支付金额 * coupon_fee_$n * 否 * Int * 100 * 单个代金券支付金额, $n为下标,从0开始编号 * </pre> */ private Integer couponFee; } }
test:
package cn.edu360.bike.test; public class XmlTest { public static void main(String[] args){ // String xml = "<xml><return_code>200</return_code><return_msg>SUCCESS</return_msg><total>123</total></xml>"; // BaseWxPayResult baseWxPayResult = BaseWxPayResult.fromXML(xml, BaseWxPayResult.class); // System.out.println(baseWxPayResult); // System.out.println(baseWxPayResult.toMap()); // System.out.println(baseWxPayResult.getXmlValue("xml/return_msg")); // System.out.println(baseWxPayResult.getXmlValueAsInt("xml/total")); String xml2 = "<xml>" + "<cash_fee>200</cash_fee><coupon_count>1</coupon_count>" + "<transaction_id>123123123</transaction_id>" + "<coupon_type_0>type0</coupon_type_0>" + "<coupon_id_0>0</coupon_id_0>" + "<coupon_fee_0>0</coupon_fee_0></xml>"; WxPayOrderQueryResult queryResult = WxPayOrderQueryResult.fromXML(xml2, WxPayOrderQueryResult.class); queryResult.composeCoupons(); System.out.println(queryResult); } }
参考:
https://gitee.com/52itstyle/spring-boot-pay/tree/master/src/main/java/com/itstyle/modules/weixinpay/controller