• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

微信小程序支付以及微信退款开发

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

最近公司项目急着测试,需要开发微信小程序+微信支付+微信退款,本着这几天的一些研究,决定记录一下开发的过程。

本着知识分享的原则,希望对大家有所帮助。

本篇针对的是微信小程序的支付开发,如果有对微信公众号的支付开发需要的,可以去我的github上看看,有个sell的项目很好的完成了公众号方面的支付与退款,代码很全,用的是最优秀的sdk,肯定对你们学习和工作有帮助,下面贴一下github链接: https://github.com/wenbingshen/springboot

也可以关注我的微信公众号:《小沈干货》不迷路。

废话不多说,开始我们的小程序支付开发之旅:

首先呢,开发之前,需要交代的是,有关微信支付的开发需要有自己的商户号和密钥,这在微信支付开发文档上面讲的很清楚,有过支付开发经验的对这一点很清楚。

 

 

了解了上面的情况后咱们就开始着手开发吧!

先编写一个常量类Constant,将有关的配置常量配在里面:

public class Constant {

    public static final String DOMAIN = "http://sellbin.natapp1.cc";//配置自己的域名

    public static final String APP_ID = "填写自己的";

    public static final String APP_SECRET = "填写自己的";

    public static final String APP_KEY = "填写自己的";

    public static final String MCH_ID = "填写自己的";  //商户号

    public static final String URL_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    public static final String URL_NOTIFY = Constant.DOMAIN + "/wxpay/views/payInfo.jsp";

    public static final String TIME_FORMAT = "yyyyMMddHHmmss";

    public static final int TIME_EXPIRE = 2;  //单位是day

}

支付的时候,我们需要利用发起支付的用户code去微信接口获取用户的openid,只有得到了openid才能去申请预付单获得prepayId,然后去唤起微信支付。

微信支付文档上面也写的很清楚:

接下来我们编写PayController类去调用微信支付的接口:

package luluteam.wxpay.controller;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import luluteam.wxpay.constant.Constant;
import luluteam.wxpay.entity.PayInfo;
import luluteam.wxpay.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Controller
public class PayController {

    private static Logger log = Logger.getLogger(PayController.class);

    @ResponseBody
    @RequestMapping(value = "/prepay", produces = "text/html;charset=UTF-8")
    public String prePay(String code, ModelMap model, HttpServletRequest request) {

        System.out.println("code:"+code);
        String content = null;
        Map map = new HashMap();
        ObjectMapper mapper = new ObjectMapper();

        boolean result = true;
        String info = "";

        log.error("\n======================================================");
        log.error("code: " + code);

        String openId = getOpenId(code);
        System.out.println("获取openid啊"+openId);
        if(StringUtils.isBlank(openId)) {
            result = false;
            info = "获取到openId为空";
        } else {
            openId = openId.replace("\"", "").trim();

            String clientIP = CommonUtil.getClientIp(request);

            log.error("openId: " + openId + ", clientIP: " + clientIP);

            String randomNonceStr = RandomUtils.generateMixString(32);
            String prepayId = unifiedOrder(openId, clientIP, randomNonceStr);

            log.error("prepayId: " + prepayId);

            if(StringUtils.isBlank(prepayId)) {
                result = false;
                info = "出错了,未获取到prepayId";
            } else {
                map.put("prepayId", prepayId);
                map.put("nonceStr", randomNonceStr);
            }
        }

        try {
            map.put("result", result);
            map.put("info", info);
            content = mapper.writeValueAsString(map);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return content;
    }


    private String getOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + Constant.APP_ID +
                "&secret=" + Constant.APP_SECRET + "&js_code=" + code + "&grant_type=authorization_code";

        HttpUtil httpUtil = new HttpUtil();
        try {

            HttpResult httpResult = httpUtil.doGet(url, null, null);

            if(httpResult.getStatusCode() == 200) {

                JsonParser jsonParser = new JsonParser();
                JsonObject obj = (JsonObject) jsonParser.parse(httpResult.getBody());

                log.error("getOpenId: " + obj.toString());

                if(obj.get("errcode") != null) {
                    log.error("getOpenId returns errcode: " + obj.get("errcode"));
                    return "";
                } else {
                    return obj.get("openid").toString();
                }
                //return httpResult.getBody();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 调用统一下单接口
     * @param openId
     */
    private String unifiedOrder(String openId, String clientIP, String randomNonceStr) {

        try {

            String url = Constant.URL_UNIFIED_ORDER;

            PayInfo payInfo = createPayInfo(openId, clientIP, randomNonceStr);
            String md5 = getSign(payInfo);
            payInfo.setSign(md5);

            log.error("md5 value: " + md5);

            String xml = CommonUtil.payInfoToXML(payInfo);
            xml = xml.replace("__", "_").replace("<![CDATA[1]]>", "1");
            //xml = xml.replace("__", "_").replace("<![CDATA[", "").replace("]]>", "");
            log.error(xml);

            StringBuffer buffer = HttpUtil.httpsRequest(url, "POST", xml);
            log.error("unifiedOrder request return body: \n" + buffer.toString());
            Map<String, String> result = CommonUtil.parseXml(buffer.toString());


            String return_code = result.get("return_code");
            if(StringUtils.isNotBlank(return_code) && return_code.equals("SUCCESS")) {

                String return_msg = result.get("return_msg");
                if(StringUtils.isNotBlank(return_msg) && !return_msg.equals("OK")) {
                    //log.error("统一下单错误!");
                    return "";
                }

                String prepay_Id = result.get("prepay_id");
                return prepay_Id;

            } else {
                return "";
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return "";
    }

    private PayInfo createPayInfo(String openId, String clientIP, String randomNonceStr) {

        Date date = new Date();
        String timeStart = TimeUtils.getFormatTime(date, Constant.TIME_FORMAT);
        String timeExpire = TimeUtils.getFormatTime(TimeUtils.addDay(date, Constant.TIME_EXPIRE), Constant.TIME_FORMAT);

        String randomOrderId = CommonUtil.getRandomOrderId();

        PayInfo payInfo = new PayInfo();
        payInfo.setAppid(Constant.APP_ID);
        payInfo.setMch_id(Constant.MCH_ID);
        payInfo.setDevice_info("WEB");
        payInfo.setNonce_str(randomNonceStr);
        payInfo.setSign_type("MD5");  //默认即为MD5
        payInfo.setBody("JSAPI支付测试");
        payInfo.setAttach("支付测试4luluteam");
        payInfo.setOut_trade_no(randomOrderId);
        payInfo.setTotal_fee(1);
        payInfo.setSpbill_create_ip(clientIP);
        payInfo.setTime_start(timeStart);
        payInfo.setTime_expire(timeExpire);
        payInfo.setNotify_url(Constant.URL_NOTIFY);
        payInfo.setTrade_type("JSAPI");
        payInfo.setLimit_pay("no_credit");
        payInfo.setOpenid(openId);

        return payInfo;
    }

    private String getSign(PayInfo payInfo) throws Exception {
        StringBuffer sb = new StringBuffer();
        sb.append("appid=" + payInfo.getAppid())
                .append("&attach=" + payInfo.getAttach())
                .append("&body=" + payInfo.getBody())
                .append("&device_info=" + payInfo.getDevice_info())
                .append("&limit_pay=" + payInfo.getLimit_pay())
                .append("&mch_id=" + payInfo.getMch_id())
                .append("&nonce_str=" + payInfo.getNonce_str())
                .append("&notify_url=" + payInfo.getNotify_url())
                .append("&openid=" + payInfo.getOpenid())
                .append("&out_trade_no=" + payInfo.getOut_trade_no())
                .append("&sign_type=" + payInfo.getSign_type())
                .append("&spbill_create_ip=" + payInfo.getSpbill_create_ip())
                .append("&time_expire=" + payInfo.getTime_expire())
                .append("&time_start=" + payInfo.getTime_start())
                .append("&total_fee=" + payInfo.getTotal_fee())
                .append("&trade_type=" + payInfo.getTrade_type())
                .append("&key=" + Constant.APP_KEY);

        log.error("排序后的拼接参数:" + sb.toString());

        return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();
    }



}

小程序端通过wx.request发起网络请求,通过服务器发起预支付,获取prepayId以及其他支付需要签名的参数后,利用wx.requestPayment发起支付。

// 1. 完成页面结构、布局、样式
// 2. 设计数据结构
// 3. 完成数据绑定
// 4. 设计交互操作事件
// 5. 数据存储
var app = getApp()   //实例化小程序,从而获取全局数据或者使用全局函数
// console.log(app.globalData)
var MD5Util = require(\'../../utils/md5.js\');

Page({
  // ===== 页面数据对象 =====
  data: {
    input: \'\',
    todos: [],
    leftCount: 0,
    allCompleted: false,
    logs: [],
    price: 0.01,
    number: 18820000000,
    deviceNo: 10080925
  },
  
  // ===== 页面生命周期方法 =====
  onLoad: function () {

  },
  // ===== 事件处理函数 =====
  wxPay: function (e) {
    var code = \'\'     //传给服务器以获得openId
    var timestamp = String(Date.parse(new Date()))   //时间戳
    var nonceStr = \'\'   //随机字符串,后台返回
    var prepayId = \'\'    //预支付id,后台返回
    var paySign = \'\'     //加密字符串

    //获取用户登录状态
    wx.login({
      success: function (res) {
        if (res.code) {
          code = res.code
          //发起网络请求,发起的是HTTPS请求,向服务端请求预支付
          wx.request({
            url: \'http://sellbin.natapp1.cc/prepay\',
            data: {
              code: res.code
            },
            success: function (res) {
              console.log(res.data);
              if (res.data.result == true) {
                nonceStr = res.data.nonceStr
                prepayId = res.data.prepayId
                // 按照字段首字母排序组成新字符串
                var payDataA = "appId=" + app.globalData.appId + "&nonceStr=" + res.data.nonceStr + "&package=prepay_id=" + res.data.prepayId + "&signType=MD5&timeStamp=" + timestamp;
                var payDataB = payDataA + "&key=" + app.globalData.key;
                // 使用MD5加密算法计算加密字符串
                paySign = MD5Util.MD5(payDataB).toUpperCase();
                // 发起微信支付
                wx.requestPayment({
                  \'timeStamp\': timestamp,
                  \'nonceStr\': nonceStr,
                  \'package\': \'prepay_id=\' + prepayId,
                  \'signType\': \'MD5\',
                  \'paySign\': paySign,
                  \'success\': function (res) {
                    // 保留当前页面,跳转到应用内某个页面,使用wx.nevigeteBack可以返回原页面
                    wx.navigateTo({
                      url: \'../pay/pay\'
                    })
                  },
                  \'fail\': function (res) {
                    console.log(res.errMsg)
                  }
                })
              } else {
                console.log(\'请求失败\' + res.data.info)
              }
            }
          })
        } else {
          console.log(\'获取用户登录态失败!\' + res.errMsg)
        }
      }
    });
  },
   formSubmit: function (e) {
    console.log(\'form发生了submit事件,携带数据为:\', e.detail.value)
  },
  formReset: function () {
    console.log(\'form发生了reset事件\')
  }
})

接下来,我们贴一下微信退款相关的代码RefundController

package luluteam.wxpay.controller;

import luluteam.wxpay.constant.Constant;
import luluteam.wxpay.entity.WxRefundInfoEntity;
import luluteam.wxpay.service.WxRefundInfoService;
import luluteam.wxpay.util.common.PayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.text.DecimalFormat;
import java.util.*;

@Controller
public class RefundController extends HttpServlet {

    private static Logger log = Logger.getLogger(PayController.class);

    @Autowired
    private WxRefundInfoService wxRefundInfoService;

    @RequestMapping(params = "refund", method = RequestMethod.POST)
    @Transactional
    public @ResponseBody
    Map<String, Object> refund(String openid, String orderId, HttpServletRequest request) {
        Map<String, Object> result = new HashMap<String, Object>();
        String currTime = PayUtils.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = PayUtils.buildRandom(4) + "";
        String nonceStr = strTime + strRandom;
        String outRefundNo = "wx@re@" + PayUtils.getTimeStamp();
        String outTradeNo = "";
        String transactionId = "";

        String unionId = openid;
        String appid = Constant.APP_ID;
        String mchid = Constant.MCH_ID;
        String key =  Constant.APP_KEY;//mch_key
//        String key = ResourceUtil.getConfigByName("wx.application.mch_key");
        if (StringUtils.isNotEmpty(orderId)) {
            int total_fee = 1;
            //商户侧传给微信的订单号32位
            outTradeNo = "115151sdasdsadsadsadas";
            DecimalFormat df = new DecimalFormat("0.00");
            //String fee = String.valueOf(df.format((float)total_fee/100));
            String fee = String.valueOf(total_fee);
            SortedMap<String, String> packageParams = new TreeMap<String, String>();
            packageParams.put("appid", appid);
            packageParams.put("mch_id", mchid);//微信支付分配的商户号
            packageParams.put("nonce_str", nonceStr);//随机字符串,不长于32位
            packageParams.put("op_user_id", mchid);//操作员帐号, 默认为商户号
            //out_refund_no只能含有数字、字母和字符_-|*@
            packageParams.put("out_refund_no", outRefundNo);//商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
            packageParams.put("out_trade_no", outTradeNo);//商户侧传给微信的订单号32位
            packageParams.put("refund_fee", fee);
            packageParams.put("total_fee", fee);
            packageParams.put("transaction_id", transactionId);//微信生成的订单号,在支付通知中有返回
            String sign = PayUtils.createSign(packageParams, key);

            String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
            String xmlParam = "<xml>" +
                    "<appid>" + appid + "</appid>" +
                    "<mch_id>" + mchid + "</mch_id>" +
                    "<nonce_str>" + nonceStr + "</nonce_str>" +
                    "<op_user_id>" + mchid + "</op_user_id>" +
                    "<out_refund_no>" + outRefundNo + "</out_refund_no>" +
                    "<out_trade_no>" + outTradeNo + "</out_trade_no>" +
                    "<refund_fee>" + fee + "</refund_fee>" +
                    "<total_fee>" + fee + "</total_fee>" +
                    "<transaction_id>" + transactionId + "</transaction_id>" +
                    "<sign>" + sign + "</sign>" +
                    "</xml>";
            log.info("---------xml返回:" + xmlParam);
            String resultStr = PayUtils.post(refundUrl, xmlParam);
            log.info("---------退款返回:" + resultStr);
            //解析结果
            try {
                Map map = PayUtils.doXMLParse(resultStr);
                String returnCode = map.get("return_code").toString();
                if (returnCode.equals("SUCCESS")) {
                    String resultCode = map.get("result_code").toString();
                    if (resultCode.equals("SUCCESS")) {
                        //保存退款记录,可在数据库建一个退款表记录
                        WxRefundInfoEntity refundInfoEntity = new WxRefundInfoEntity();
                        refundInfoEntity.setCreateDate(new Date());
                        refundInfoEntity.setAppid(appid);
                        refundInfoEntity.setMchId(mchid);
                        refundInfoEntity.setNonceStr(nonceStr);
                        refundInfoEntity.setSign(sign);
                        refundInfoEntity.setOutRefundNo(outRefundNo);
                        refundInfoEntity.setOutTradeNo(outTradeNo);
                        refundInfoEntity.setTotalFee(total_fee);
                        refundInfoEntity.setRefundFee(total_fee);
                        refundInfoEntity.setUnionid(unionId);
                        wxRefundInfoService.save(refundInfoEntity);
                        result.put("status", "success");
                    } else {
                        result.put("status", "fail");
                    }
                } else {
                    result.put("status", "fail");
                }
            } catch (Exception e) {
                e.printStackTrace();
                result.put("status", "fail");
            }
        }
        return result;
    }
}

支付与退款有关的工具类和实体类一并贴在下面,如果有不清楚的,可以去我的github上面下载源码:

https://github.com/wenbingshen/wechatpay

 

entity包:

package luluteam.wxpay.entity;

import java.io.Serializable;

public class PayInfo implements Serializable {

    private String appid;
    private String mch_id;
    private String device_info; //设备号,小程序传"WEB"
    private String nonce_str;
    private String sign;
    private String sign_type;  //签名类型
    private String body;
    //private String detail;
    private String attach;
    private String out_trade_no;
    private int total_fee;
    private String spbill_create_ip;
    private String time_start;
    private String time_expire;
    private String notify_url;
    private String trade_type; //交易类型,JSAPI
    private String limit_pay;  //指定支付方式,no_credit
    private String openid;

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getDevice_info() {
        return device_info;
    }

    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getSign_type() {
        return sign_type;
    }

    public void setSign_type(String sign_type) {
        this.sign_type = sign_type;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getAttach() {
        return attach;
    }

    public void setAttach(String attach) {
        this.attach = attach;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public int getTotal_fee() {
        return total_fee;
    }

    public void setTotal_fee(int total_fee) {
        this.total_fee = total_fee;
    }

    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }

    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }

    public String getTime_start() {
        return time_start;
    }

    public void setTime_start(String time_start) {
        this.time_start = time_start;
    }

    public String getTime_expire() {
        return time_expire;
    }

    public void setTime_expire(String time_expire) {
        this.time_expire = time_expire;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }

    public String getLimit_pay() {
        return limit_pay;
    }

    public void setLimit_pay(String limit_pay) {
        this.limit_pay = limit_pay;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }
}
WxRefundInfoEntity类,用来退款后向数据添加退款的记录,方面查账:
package luluteam.wxpay.entity;

import javax.persistence.Entity;
import java.io.Serializable;
import java.util.Date;

@Entity
public class WxRefundInfoEntity implements Serializable {
    private Date createDate;

    private String appid;

    private String mchId;

    private String nonceStr;

    private String sign;

    private String outRefundNo;

    private String outTradeNo;

    private int totalFee;

    private int refundFee;

    private String unionid;

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getMchId() {
        return mchId;
    }

    public void setMchId(String mchId) {
        this.mchId = mchId;
    }

    public String getNonceStr() {
        return nonceStr;
    }

    public void setNonceStr(String nonceStr) {
        this.nonceStr = nonceStr;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getOutRefundNo() {
        return outRefundNo;
    }

    public void setOutRefundNo(String outRefundNo) {
        this.outRefundNo = outRefundNo;
    }

    public String getOutTradeNo() {
        return outTradeNo;
    }

    public void setOutTradeNo(String outTradeNo) {
        this.outTradeNo = outTradeNo;
    }

    public int getTotalFee() {
        return totalFee;
    }

    public void setTotalFee(int totalFee) {
        this.totalFee = totalFee;
    }

    public int getRefundFee() {
        return refundFee;
    }

    public void setRefundFee(int refundFee) {
        this.refundFee = refundFee;
    }

    public String getUnionid() {
        return unionid;
    }

    public void setUnionid(String unionid) {
        this.unionid = unionid;
    }
}

 

util包:

package luluteam.wxpay.util.common;

import java.security.MessageDigest;

public class MD5 {
    private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
            "8", "9", "a", "b", "c", "d", "e", "f"};

    /**
     * 转换字节数组为16进制字串
     * @param b 字节数组
     * @return 16进制字串
     */
    public static String byteArrayToHexString(byte[] b) {
        StringBuilder resultSb = new StringBuilder();
        for (byte aB : b) {
            resultSb.append(byteToHexString(aB));
        }
        return resultSb.toString();
    }

    /**
     * 转换byte到16进制
     *  
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap