IONIC3微信支付 (附后台JAVA)

Ionic3微信支付流程

总结通俗来说微信支付一共两步(最后附全部代码):

1.统一下单(给微信获得微信返回的支付订单号(prepay_id))

解释一下:通俗点讲用户支付钱的时候,需要跳到微信界面,根据一个微信支付订单(微信界面肯定是微信的东西,和自己的项目没有任何关系)来支付钱,这个微信支付订单是微信生成的以下简称vx订单,怎么生成?项目后台根据一系列参数生成一个map,利用map和商户微信支付密钥key根据规则生成签名sign,将sign也放入到此前的map中,再将map转换为xml(格式参考微信支付文档,微信只接受xml),并将此xml通过post请求发送给微信(发送url为微信统一定死的地址,详细接着看下面),不论成功与否都回返回一个xml,若成功xml转map后会取得此次统一下单的终极目的参数prepay_id(预支付id),这个id是统一下单步骤的终极目标,获得此id代表已经成功让微信为用户之后的跳转到微信支付界面付钱生成了一个订单界面,等候用户跳转微信支付。若失败则要查看返回的xml中,returnMsg会提示报的错误,根据错误排查(文章最后会有一系列坑及解决方案)。

注意:此步骤完成不会跳转微信支付接口,仅仅是给微信支付下了个可以理解为预付订单,取得预付订单号prepay_id

统一下单xml示例:

IONIC3微信支付 (附后台JAVA)_第1张图片

参数举例:商品订单号out_trade_no(unique,并不是vx订单)和钱数total_fee(any)已经用户支付时的客户端ip spbill_create_ip等等一系列参数,参考下图。

IONIC3微信支付 (附后台JAVA)_第2张图片

以下是必不可少的参数小白解释(具体可参考微信支付文档):

appid(String 32):微信开发平台创建app时取得

mch_id(String 32):商户号id:微信开发平台申请微信支付资格成功时取得。

body(String 32):自己定义对订单商品的描述,比如body = 充值Q币,用户跳到微信支付界面时显示出的充值界面内容即为充值Q币。

Out_trade_no(String 32):商品订单号,此订单号为我们自己生成的订单号,必须唯一。

Total_fee(Int):单位为分! 用户实际支付的金额

Spbill_create_ip(String 16):用户支付时客户端ip

Trader_type(String 16):APP(app支付时定死)

Nonce_str(String 32):随机生成字符串

Notify_url(String 256):用户支付完成后(完成统一下单不会走回调!),微信收到了钱或支付失败都有可能返回一个xml,返回时需要异步的访问我们服务器的一个url地址,此地址作为支付的回调函数,在此地址中处理相应业务逻辑,返回时仅仅会有商品订单号(项目生成),和实际支付金额,实际支付时间有用。

Sign(String 32):生成签名,需要用到商户支付密钥(不是appSecret!)此出生成签名必须与返回给TS的参数中生成签名的方式一致。具体生成方式网上一大堆。

1.调起支付接口

将统一下单取得的prepay_id和其他参数如下等再次(第二次)生成签名,与之前同意下单的签名不相同!将这些参数发送回前台准备进行微信窗口的跳转(调起支付接口),具体操作见下节。

 

IONIC3微信支付 (附后台JAVA)_第3张图片

IONIC3微信支付 (附后台JAVA)_第4张图片

注意:timestamp必须为String类型的时间戳!扩展字段为定死。商户订单号outtradeno可不传,为前台处理业务逻辑用。

2.App微信支付资格准备

https://open.weixin.qq.com/cgi-bin/frame?t=home/app_tmpl&lang=zh_CN

1.创建app,注册微信平台相关信息。取得appid,appSecret

2.通过后,开通微信支付资格(商户号申请),具体材料有打包并且已经签名成功的apk

3.取得资格后需要取得的参数为商户号,商户支付密钥。

最终上述步骤取得微信支付实际所需参数有:

APPID:申请的appid

MCH_ID:商户号

KEY:商户号的支付密钥(不是appSecret!)

下面是后台示例此步骤所获得的参数一览:

IONIC3微信支付 (附后台JAVA)_第5张图片

注意:apk包名和apk的签名一旦绑定不可更改,必须与正式发版后的包名签名一致,否则

会导致出现签名错误(普通错误)。

微信支付插件安装

Cmd: cordova plugin add [email protected] --variable wechatappid=xxxxxxxxxxxxxxx

注意:@2.0为版本号可更换 wechatappid为申请的appid(不是商户号!),此处必须与申请微信支付的app的appid一致。

安装完成后,打开confing.xml若如图所示版本号的appid无误则为安装成功。

IONIC3微信支付 (附后台JAVA)_第6张图片

IONIC3微信支付 (附后台JAVA)_第7张图片

请求后台进行统一下单

安装成功后,经大量测试,无需引入module等其他操作,直接在要使用微信支付的page.ts开搞。

1.在ts中组件声明前声明一个window对象。

IONIC3微信支付 (附后台JAVA)_第8张图片

2.直接请求(get/post都可)后台(必须参数:支付金额(注意微信接口的单位为分,此处若为元需处理),用户id(进行支付钱的业务处理需要),订单号可后台生成),后台返回值重新组装一个类似于post请求的对象参数,必须的参数如下图(微信文档有7个必须,经测试有下图5个为必须即可)

IONIC3微信支付 (附后台JAVA)_第9张图片

  1. 统一下单后台代码示例

注意:

  1. 发送的post请求编码格式一定要统一(UTF-8),否则返回的xml会报body乱码错误
  2. KEY一定要是商户支付密钥不是appSercet!!两者不同。
  3. Total_fee在此处的单位一定是分,不论之前业务处理以元或其他单位,此处必须转换成分。
  4. 订单号一定是唯一的,此处建议取上一个订单号+1,再拼接一个一定位数的随机字符串,在回调函数中直接sub掉一定位数的字符串即可拿到唯一的订单号。
  5. 回调函数不能带参数!
  6. 统一下单的签名方式一定要和调起支付接口参数的签名方式一致!但并不是同一个签名!

因为签名时所需参数多了一个prepay_id!

  1. Timestamp时间戳一定要是string!
  2. 注意发送参数的大小写和非人的下划线变化。Eg:mch_id =>partnerid,nonce_str =>noncestr
  3. 统一下单中返回的xml报错商户订单号重复,解决方法检查发送给微信的商户订单号是否与上次未完成的订单重复,若未重复,等待一天即可(微信取消支付异常的预付订单)。

 

IONIC3微信支付 (附后台JAVA)_第10张图片

调起支付接口,跳转微信界面。

拿到通过第一步统一下单取得的参数后,将此对象参数通过下图请求发送即可跳转微信界面。

若失败 reason会打印错误(eg:1.普通错误2.用户取消支付返回等)。成功则为完成支付。

注意:

  1. 注意es5和es6的坑,回调函数中的this指向问题,es5 为function(data){}其中的this不是指ts的this而是回调函数中的this 。es6写法为(data)=> this指向为ts中的this。\
  2. 若返回普通错误,说明这个方法本身没问题,且插件安装正确。在统一下单成功的情况下,检查appid / 商户号 /商户支付密钥(不是appSecert)是否和申请通过时的一致!
  3. 若一致可能是申请通过时间未到24小时(一般要在项目开始时申请,不要先做现申请)!

 

 

以下为全部代码:

ts:

//声明window
declare var window;
@Component({
    selector: 'page-paywaymodal',
    templateUrl: 'paywaymodal.html',
})
    /**
     * 微信统一下单
     */
    getWXPayParam(item) {
        console.log(item.type);
        let paymentObject = {
            paymentFee: this.payFee,
            payer: GlobalConfig.USERINFO.id,
            paymentType: item.paymentConfigType,
            paymentConfigName: item.paymentConfigName,
            paymentConfigId: item.paymentConfigId,
            paymentStatu: 0,
            orderIdList: this.orderId
        };
        this.httpService.post(this.payUrl, paymentObject).then(data => {
            if (!data.success) {
                this.toastAlertService.errorToast(data.message, 1000, "top");
            } else {
                console.log(data);
                let params = {
                    // appid:data.preOrderResult.appid,   
                    partnerid: data.preOrderResult.partnerid,  //商户号
                    prepayid: data.preOrderResult.prepayid,    //预支付id
                    //package: data.preOrderResult.package,
                    noncestr: data.preOrderResult.noncestr,    //随机数
                    timestamp: data.preOrderResult.timestamp,  //时间戳
                    sign: data.preOrderResult.sign,             //签名
                    //appID
                };
                this.WXPay(params, data.preOrderResult.outtradeno, data.preOrderResult.paymentFee);
            }
        });
    }
    /**
     * 调起微信支付接口
     * @param params 
     */
    WXPay(params, paymentSn, paymentFee) {
        window.Wechat.sendPaymentRequest(params, (data) => {
            this.toastAlertService.successToast(data.message, 2000, "top");
            this.viewCtrl.dismiss(true);
        }, (reason) => {
            this.toastAlertService.successToast("支付失败" + reason, 2000, "middle");
            this.viewCtrl.dismiss(false);
        })
    }

java(serviceImpl):

package com.xxx.common.business;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.xxx.common.api.WeChatConfig;
import com.xxx.utils.CommonUtil;
import com.xxxx.utils.HttpRequest;
import com.xxxx.utils.WXPayUtil;
import com.xxxx.weixin.api.WxOrderService;
/**
 * 微信支付接口(仔细看注解)
 * @author lu
 *
 */
@Service
public class WxOrderServiceImpl implements WxOrderService {
	
	/**
	 * ==========================================
	 * 参数:
	 * out_trade_no:订单号 total_fee:支付钱数  body:所购买的物品简介
	 * 注意:
	 *  1.微信预付单:指的是在自己的平台需要和微信进行支付交易生成的一个微信订单,称之为“预付单”
	 *  2.订单:指的是自己的网站平台与用户之间交易生成的订单或消费记录
	 *  3.时间戳一定要转成字符串类型,生成签名方式一定要相同
	 * 微信支付过程:
	 * 1. 生成网站订单或消费记录(一定要有否则保存不了参数处理业务逻辑)
	 * 2. 用户支付 --> 网站在微信平台生成预付单(实际取得的是预支付订单号)
	 * 3. 最终实际根据预付单的信息进行支付
	 * ==========================================
	 */
	
	@Override
	public Map placeOrder(String body,String out_trade_no,String total_fee,HttpServletRequest request) throws Exception {
		System.out.println("进入产生预付订单api");
		try {
			//获取请求用户端实际ip
			String ip = request.getHeader("x-forwarded-for");
			if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
				ip = request.getHeader("Proxy-Client-IP");
			}
			if(ip == null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)){
				ip = request.getHeader("WL-Proxy-Client-IP");
			}
			if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
			    ip = request.getRemoteAddr();
			}
			if(ip.indexOf(",")!=-1){
				String[] ips = ip.split(",");
				ip = ips[0].trim();
			}
			System.out.println("------请求用户端ip----------"+ip);
			Map params = new HashMap();	
			String total_yuan = "";// new BigDecimal(total_fee).movePointRight(2).toString();//分转元(测试先设支付为分)
			// 订单总金额,单位为元(测试中先为分)
			if (StringUtils.isNotEmpty(total_fee)) {
				total_yuan = new BigDecimal(total_fee).movePointRight(2).toString();//充值碳汇币以元为单位(分转元)3-》300分;// new BigDecimal(recharge_fee).movePointRight(2).toString();//充值碳汇币以元为单位(分转元)
			}
			// 用户支付次数过于密集,会在极短的时间内生成累加订单号和生成订单之间会有可能出现重复订单号的问题,故在发送给微信的订单号后面追加随机数,在回调函数中sub掉取出,会大大降低订单的重复率		
			out_trade_no += CommonUtil.getUUID().substring(0, 3);//3位随机数			
			params.put("appid",WeChatConfig.APPID);//APPID
			params.put("mch_id",WeChatConfig.MCH_ID);//商户号
			params.put("body",body);//详情描述
			params.put("out_trade_no", out_trade_no);//订单号
			params.put("total_fee", total_yuan);//支付费用(分)
			//params.put("total_fee", "1");测试用
			params.put("spbill_create_ip", ip);//用户端实际ip
			params.put("trade_type",WeChatConfig.TRADE_TYPE);//交易类型
			params.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
			params.put("notify_url",WeChatConfig.NOTIFY_URL);//回调url
			String sign = WXPayUtil.generateSignature(params,WeChatConfig.KEY);//生成签名
			params.put("sign", sign);
			System.out.println("统一下单参数:"+params.toString());
			String xml = WXPayUtil.mapToXml(params).replace("", "").trim();//将所有参数(map)转xml格式,微信方接收方式为xml
			String xmlStr = HttpRequest.sendPost(WeChatConfig.PLACEANORDER_URL, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
			System.out.println("xmlStr显示:" + xmlStr);
			String prepay_id = "";
			if (xmlStr.indexOf("SUCCESS") != -1) {  
				Map map = WXPayUtil.xmlToMap(xmlStr);  
				prepay_id = (String) map.get("prepay_id");//预支付ID  
				System.out.println("取得预支付id:"+prepay_id);
			}
			Map payMap = new HashMap();
			payMap.put("appid",WeChatConfig.APPID);  			
			payMap.put("partnerid",WeChatConfig.MCH_ID);
			payMap.put("prepayid",prepay_id);//预支付id(预支付提交后微信返回)
			payMap.put("package",WeChatConfig.PACKAGE);//扩展字段
			payMap.put("noncestr",WXPayUtil.generateNonceStr());//随机字符串
			payMap.put("timestamp",WXPayUtil.getCurrentTimestamp()+"");//时间戳
			String paySign = WXPayUtil.generateSignature(payMap,WeChatConfig.KEY);
			payMap.put("sign", paySign);//签名(注意:签名方式一定要与统一下单接口使用的一致)
			payMap.put("outtradeno", out_trade_no.substring(0,out_trade_no.length() - 3));//订单号
			payMap.put("paymentFee",total_fee);//total_yuan 实际支付金额
			System.out.println("吊起支付接口参数:"+payMap.toString());
			return payMap;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}


}

/**
 * @Description: 微信支付的一些参数配置
 * @author lu
 */ 
public class WeChatConfig {
	
	public static final String APPID = "xxxxxxxx";						// 公众账号ID
	public static final String MCH_ID = "xxxxxxxx";						// 商户号
	public static final String KEY = "xxxxxxxxxx";		// 商户密钥
	public static final String PACKAGE = "Sign=WXPay";
/*	// APP和网页支付提交用户端ip, Native支付填调用微信支付API的机器IP, 即:服务器ip地址
	public static final String SPBILL_CREATE_IP = "127.0.0.1";*/
	// 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。(需要配置)
	public static final String NOTIFY_URL = "xxxxxxx/appPay/callBack";
	// 支付方式,取值如下:JSAPI,NATIVE,APP
	public static final String TRADE_TYPE = "APP";
	// 微信支付 - 统一下单地址
	public static final String PLACEANORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	
}


 

在下辛苦填坑,转载请注明出处! 有问题欢迎指出请教加VX kuaibaojing

你可能感兴趣的:(IONIC3,Angular,JAVA,微信支付)