名词定义
- 用户:在我们网站上交易的客户。
- 支付网关:是我们商户和支付宝开放平台对接的系统。
- 商户系统:充值成功后用于发货的系统。
- 微信支付平台:是阿里的支付宝开放。
支付宝扫码支付官网地址
支付接口:https://docs.open.alipay.com/api_1/
序列图
用户登录我们的网站,选择好商品之后,点击使用支付宝扫码付款,直到用户户看到付款成功。对应的序列图如下:
我们就是要做上图这么一件事,下面是详细的开发步骤。
一、注册
注册地址:https://memberprod.alipay.com/account/reg/index.htm
只有企业账户才能玩支付。
二、配置
登录微信公共号,获取如下三项配置:
- 应用ID - 支付宝分配给开发者的应用ID
- 商户私钥 - 商户自己生成一对密钥中的私钥
- 支付宝公钥 - 登录开放平台后自行下载
生成RSA密钥:https://docs.open.alipay.com/291/105971/
上传商户公钥并获取支付宝公钥:https://docs.open.alipay.com/291/105972/
使用私商户钥生成请求签名:https://docs.open.alipay.com/291/105974/
使用支付宝公钥验签
三、统一收单线下交易预创建(下单)
这是一个预交易接口,即调用后不会真正的发生金钱交易,只是告诉支付宝有个用户一会要支付1000元买手机,你给我生成一个支付链接,我把这个链接变成二维码,让他使用支付宝来扫描。调用此接口后会返回支付链接。
1 | /** |
其中Alipay开头的是阿里的sdk,使用maven依赖如下:1
2
3
4
5<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.87.ALL</version>
</dependency>
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>
对应接口文档地址:https://docs.open.alipay.com/api_1/alipay.trade.precreate
四、后台通知
当用户扫码完成支付后,微信会发起后台通知告诉我们交易成功了。
1 | /** |
对应的接口文档:https://docs.open.alipay.com/api_1/alipay.trade.precreate/
在文档的最后一行。
五、单笔查询
如果我们没收到后台通知怎么办?这时候就可以通过单笔查询主动去支付宝开放平台查询订单状态。
1 | Map<String, String> bizContent = new HashMap<>(4); |
对应接口文档地址:https://docs.open.alipay.com/api_1/alipay.trade.query/
六、下载对账单
如果我们需要日终对账,那么就要用到下载对账单接口。但是此接口不大符合我的需要,因此我没用这个接口,而是循环使用单笔查询代替的。