名词定义
- 用户:在我们网站上交易的客户。
- 支付网关:是我们商户和微信支付平台对接的系统。
- 商户系统:充值成功后用于发货的系统。
- 微信支付平台:是腾讯的微信支付平台。
微信支付官网地址
扫码支付介绍:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1
序列图
用户登录我们的网站,选择好商品之后,点击使用微信扫码付款,直到用户户看到付款成功。对应的序列图如下:
我们就是要做上图这么一件事,下面是详细的开发步骤。
一、注册
注册地址:https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=
只有企业才能进行微信认证,只有进行了认证才能玩微信支付。
二、配置
登录微信公共号,获取如下三项配置:
- 公众账号ID - 微信支付分配的公众账号ID
- 商户号 - 微信支付分配的商户号
- key - key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
三、预交易(统一下单)
这是一个预交易接口,即调用后不会真正的发生金钱交易,只是告诉微信有个用户一会要支付1000元买手机,你给我生成一个支付链接,我把这个链接变成二维码,让他使用微信来扫描。调用此接口后会返回支付链接。
1 | /** |
wxPayConfig是微信支付配置,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import demo.sdk.IWXPayDomain;
import demo.sdk.WXPayConfig;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.InputStream;
/**
* @author kangyonggan
* @since 10/9/18
*/
@Log4j2
@Component
public class MyWXPayConfig extends WXPayConfig {
@Value("${wxpay.appid}")
private String appId;
@Value("${wxpay.mchid}")
private String mchId;
@Value("${wxpay.key}")
private String key;
@Value("${wxpay.domain}")
private String domain;
@Override
public String getAppID() {
return appId;
}
@Override
public String getMchID() {
return mchId;
}
@Override
public String getKey() {
return key;
}
@Override
public InputStream getCertStream() {
return null;
}
@Override
public IWXPayDomain getWXPayDomain() {
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
log.error("微信支付域名:{}, {}", domain, elapsedTimeMillis, ex);
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new DomainInfo(domain, true);
}
};
return iwxPayDomain;
}
}
QRCodeKit是一个二维码生成工具:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Base64OutputStream;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author kangyonggan
* @since 10/29/18
*/
public class QRCodeKit {
private static final String QRCODE_DEFAULT_CHARSET = "UTF-8";
private static final int QRCODE_DEFAULT_HEIGHT = 300;
private static final int QRCODE_DEFAULT_WIDTH = 300;
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
public static void main(String[] args) throws IOException, NotFoundException {
String data = "扫码看到我";
File logoFile = new File("/xxx/logo.png");
BufferedImage image = QRCodeKit.createQRCodeWithLogo(data, logoFile);
ImageIO.write(image, "png", new File("/xxx/res.png"));
}
/**
* Create qrcode with default settings
*
* @param data
* @return
*/
public static BufferedImage createQRCode(String data) {
return createQRCode(data, QRCODE_DEFAULT_WIDTH, QRCODE_DEFAULT_HEIGHT);
}
/**
* Create qrcode with default charset
*
* @param data
* @param width
* @param height
* @return
*/
public static BufferedImage createQRCode(String data, int width, int height) {
return createQRCode(data, QRCODE_DEFAULT_CHARSET, width, height);
}
/**
* Create qrcode with specified charset
*
* @param data
* @param charset
* @param width
* @param height
* @return
*/
public static BufferedImage createQRCode(String data, String charset, int width, int height) {
Map hint = new HashMap();
hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hint.put(EncodeHintType.CHARACTER_SET, charset);
return createQRCode(data, charset, hint, width, height);
}
/**
* Create qrcode with specified hint
*
* @param data
* @param charset
* @param hint
* @param width
* @param height
* @return
*/
public static BufferedImage createQRCode(String data, String charset, Map<EncodeHintType, ?> hint, int width,
int height) {
BitMatrix matrix;
try {
matrix = new MultiFormatWriter().encode(new String(data.getBytes(charset), charset), BarcodeFormat.QR_CODE,
width, height, hint);
return toBufferedImage(matrix);
} catch (WriterException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
/**
* Create qrcode with default settings and logo
*
* @param data
* @param logoFile
* @return
*/
public static BufferedImage createQRCodeWithLogo(String data, File logoFile) {
return createQRCodeWithLogo(data, QRCODE_DEFAULT_WIDTH, QRCODE_DEFAULT_HEIGHT, logoFile);
}
/**
* Create qrcode with default charset and logo
*
* @param data
* @param width
* @param height
* @param logoFile
* @return
*/
public static BufferedImage createQRCodeWithLogo(String data, int width, int height, File logoFile) {
return createQRCodeWithLogo(data, QRCODE_DEFAULT_CHARSET, width, height, logoFile);
}
/**
* Create qrcode with specified charset and logo
*
* @param data
* @param charset
* @param width
* @param height
* @param logoFile
* @return
*/
public static BufferedImage createQRCodeWithLogo(String data, String charset, int width, int height, File logoFile) {
Map<EncodeHintType, Object> hint = new HashMap<>(8);
hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hint.put(EncodeHintType.CHARACTER_SET, charset);
return createQRCodeWithLogo(data, charset, hint, width, height, logoFile);
}
/**
* Create qrcode with specified hint and logo
*
* @param data
* @param charset
* @param hint
* @param width
* @param height
* @param logoFile
* @return
*/
public static BufferedImage createQRCodeWithLogo(String data, String charset, Map<EncodeHintType, ?> hint,
int width, int height, File logoFile) {
try {
BufferedImage qrcode = createQRCode(data, charset, hint, width, height);
BufferedImage logo = ImageIO.read(logoFile);
int deltaHeight = height - logo.getHeight();
int deltaWidth = width - logo.getWidth();
BufferedImage combined = new BufferedImage(height, width, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) combined.getGraphics();
g.drawImage(qrcode, 0, 0, null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
g.drawImage(logo, Math.round(deltaWidth / 2), Math.round(deltaHeight / 2), null);
return combined;
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Return base64 for image
*
* @param image
* @return
*/
public static String getImageBase64String(BufferedImage image) {
String result = null;
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStream b64 = new Base64OutputStream(os);
ImageIO.write(image, "png", b64);
result = os.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
return result;
}
/**
* Decode the base64Image data to image
*
* @param base64ImageString
* @param file
*/
public static void convertBase64StringToImage(String base64ImageString, File file) {
FileOutputStream os;
try {
Base64 d = new Base64();
byte[] bs = d.decode(base64ImageString);
os = new FileOutputStream(file.getAbsolutePath());
os.write(bs);
os.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
生成二维码使用的是zxing:1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>
其他几个类,都是微信的sdk:
- IWXPayDomain
- WXPay
- WXPayConfig
- WXPayConstants
- WXPayReport
- WXPayRequest
- WXPayUtil
- WXPayXmlUtil
sdk下载地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
对应接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
四、后台通知
当用户扫码完成支付后,微信会发起后台通知告诉我们交易成功了。
1 | /** |
对应接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
五、单笔查询
如果我们没收到后台通知怎么办?这时候就可以通过单笔查询主动去微信支付平台查询订单状态。
1 | WXPay wxpay = new WXPay(wxPayConfig); |
对应接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
六、下载对账单
如果我们需要日终对账,那么就要用到下载对账单接口。但是此接口不大符合我的需要,因此我没用这个接口,而是循环使用单笔查询代替的。
对应接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_6