1. 程式人生 > >支付寶APP支付——支付流程說明及示例

支付寶APP支付——支付流程說明及示例

轉自:https://blog.csdn.net/flygoa/article/details/54891473

 

支付寶APP支付——支付流程說明及示例
官方示例圖
螞蟻金服開放平臺文件:地址

官方流程圖:地址


如圖,以Android平臺為例:

第4步:呼叫支付介面:此訊息就是本介面所描述的支付寶客戶端SDK提供的支付物件PayTask,將商戶簽名後的訂單資訊傳進payv2方法喚起支付寶收銀臺,交易資料格式具體參見請求引數說明。

第5步:支付請求:支付寶客戶端SDK將會按照商戶客戶端提供的請求引數傳送支付請求。

第8步:介面返回支付結果:商戶客戶端在第4步中呼叫的支付介面,會返回最終的支付結果(即同步通知),參見客戶端同步返回。

第13步:使用者在支付寶APP或H5收銀臺完成支付後,會根據商戶在手機網站支付API中傳入的前臺回跳地址return_url自動跳轉回商戶頁面,同時在URL請求中附帶上支付結果引數。同時,支付寶還會根據原始支付API中傳入的非同步通知地址notify_url,通過POST請求的形式將支付結果作為引數通知到商戶系統,詳情見支付結果非同步通知。

除了正向支付流程外,支付寶也提供交易查詢、關閉、退款、退款查詢以及對賬等配套API。

特別注意:

    構造交易資料並簽名必須在商戶服務端完成,商戶的應用私鑰絕對不能儲存在商戶APP客戶端中,也不能從服務端下發。

    同步返回的資料,只是一個簡單的結果通知,商戶確定該筆交易付款是否成功需要依賴服務端收到支付寶非同步通知的結果進行判斷。

    商戶系統接收到通知以後,必須通過驗籤(驗證通知中的sign引數)來確保支付通知是由支付寶傳送的。建議使用支付寶提供的SDK來完成,詳細驗籤規則參考非同步通知驗籤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
個人實際應用後的理解及實現
生成訂單資訊

app呼叫支付寶支付介面,服務端會返回支付資訊

支付寶服務端呼叫非同步通知介面,根據返回資訊處理自己的業務邏輯,比如記入本地臺賬,修改訂單狀態等

生成訂單資訊
1. 建立本地訂單並儲存, 商品,價格,訂單號


/**

 * 新增一條支付寶的訂單

 * @param user

 * @param vipPrice

 * @return

 */

private VipOrder addVipOrder(User user, VipPrice vipPrice) {

    //根據業務設定訂單引數

    VipOrder vo = new VipOrder();

    vo.setCostPrice(vipPrice.getCostPrice());//原價

    vo.setCurrentPrice(vipPrice.getCurrentPrice());//現價

    vo.setGoodsId(Integer.parseInt(vipPrice.getId()));//商品id

    vo.setGoodsName(vipPrice.getTypeName()+vipPrice.getName());//商品名稱

    vo.setPayMode(VipOrder.PAYMODE_ALIPAY);//支付寶

    vo.setPayState(VipOrder.PAYSTATE_CREATE);//支付狀態

    vo.setVipId(String.valueOf(vipPrice.getVipId()));//vipId

    vo.setVipName(vipPrice.getTypeName());//vip名稱

    vo.setTimesNum(vipPrice.getTimesNum());//時長

    vo.setUserId(user.getUid());//使用者id

    vo.setUserName(user.getUsername());//使用者名稱稱

    vo.setOrderNum(OrderInfoUtil2_0.getOutTradeNo(user.getUid()))//訂單號,自定義隨機碼就可以建議32位;

    vo.setComId(String.valueOf(user.getCom_id()));//公司id

    vo.setComName(user.getCom_name());//公司名稱

    vo.setIsClaim(2);//初始化賬單為未索取

    vipOrderMapper.add(vo);//新增一條資料

    return vo;

}
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
訂單實體


/**

 * vip_order 實體類

 * 

 * @author pzr

 */

public class VipOrder implements Serializable {

    /**

     * 支付方式 支付寶

     */

    public static final String PAYMODE_ALIPAY = "alipay";

    /**

     * 支付方式 微信

     */

    public static final String PAYMODE_WX = "wx";

    /**

     * 支付狀態 建立待付款

     */

    public static final String PAYSTATE_CREATE = "create";

    /**

     * 支付狀態 支付成功

     */

    public static final String PAYSTATE_SUCCESS = "success";

    /**

     * 支付狀態 支付完成,完成後不可退款

     */

    public static final String PAYSTATE_FINISHED = "finished";

    /**

     * 支付狀態 支付失敗

     */

    public static final String PAYSTATE_FAILURE = "failure";

    private static final long serialVersionUID = -1L;

    /**

     * 套餐id

     */

    private Integer goodsId = -1;

    /**

     * 原價

     */

    private String costPrice;

    /**

     * 備註

     */

    private String remark;

    /**

     * 商品時長 單位(月)

     */

    private Integer timesNum = -1;

    /**

     * vip編號

     */

    private String orderNum;

    /**

     * 現價

     */

    private String currentPrice;

    /**

     * 

     */

    private String id;

    /**

     * 訂單建立時間

     */

    private String createtime;

    /**

     * 支付狀態

     */

    private String payState;

    /**

     * 處理物件,包含【處理時間】【處理型別】

     */

    private Integer handId = -1;

    /**

     * 支付方式

     */

    private String payMode;

    /**

     * 使用者id

     */

    private Integer userId = -1;

    /**

     * 使用者名稱稱

     */

    private String userName;

    /**

     * 商品名稱

     */

    private String goodsName;

    /**

     * 企業id

     */

    private String comId;

    /**

     * 企業名稱

     */

    private String comName;

    /**

     * 會員名稱

     */

    private String vipName;

    /**

     * 會員id

     */

    private String vipId;

    /**

     * 是否索取發票,【1、已索取,2、未索取,3、索取中】

     */

    private Integer isClaim = -1;

    /**

     * 是否郵寄發票,【1、是,2、否】

     */

    private Integer isMail = -1;

    public String getVipId() {

        return vipId;

    }

    public void setVipId(String vipId) {

        this.vipId = vipId;

    }

    public String getVipName() {

        return vipName;

    }

    public void setVipName(String vipName) {

        this.vipName = vipName;

    }

    public String getComId() {

        return comId;

    }

    public void setComId(String comId) {

        this.comId = comId;

    }

    public String getComName() {

        return comName;

    }

    public void setComName(String comName) {

        this.comName = comName;

    }

    public void setGoodsId(Integer goodsId) {

        this.goodsId = goodsId;

    }

    public Integer getGoodsId() {

        return goodsId;

    }

    public void setCostPrice(String costPrice) {

        this.costPrice = costPrice;

    }

    public String getCostPrice() {

        return costPrice;

    }

    public void setRemark(String remark) {

        this.remark = remark;

    }

    public String getRemark() {

        return remark;

    }

    public void setTimesNum(Integer timesNum) {

        this.timesNum = timesNum;

    }

    public Integer getTimesNum() {

        return timesNum;

    }

    public void setOrderNum(String orderNum) {

        this.orderNum = orderNum;

    }

    public String getOrderNum() {

        return orderNum;

    }

    public void setCurrentPrice(String currentPrice) {

        this.currentPrice = currentPrice;

    }

    public String getCurrentPrice() {

        return currentPrice;

    }

    public void setId(String id) {

        this.id = id;

    }

    public String getId() {

        return id;

    }

    public void setCreatetime(String createtime) {

        this.createtime = createtime;

    }

    public String getCreatetime() {

        return createtime;

    }

    public void setPayState(String payState) {

        this.payState = payState;

    }

    public String getPayState() {

        return payState;

    }

    public void setHandId(Integer handId) {

        this.handId = handId;

    }

    public Integer getHandId() {

        return handId;

    }

    public void setPayMode(String payMode) {

        this.payMode = payMode;

    }

    public String getPayMode() {

        return payMode;

    }

    public void setUserId(Integer userId) {

        this.userId = userId;

    }

    public Integer getUserId() {

        return userId;

    }

    public void setUserName(String userName) {

        this.userName = userName;

    }

    public String getUserName() {

        return userName;

    }

    public void setGoodsName(String goodsName) {

        this.goodsName = goodsName;

    }

    public String getGoodsName() {

        return goodsName;

    }

    public Integer getIsClaim() {

        return isClaim;

    }

    public void setIsClaim(Integer isClaim) {

        this.isClaim = isClaim;

    }

    public Integer getIsMail() {

        return isMail;

    }

    public void setIsMail(Integer isMail) {

        this.isMail = isMail;

    }

    // 重寫Object物件的equals方法

    @Override

    public boolean equals(Object obj) {

        if (this == obj) {

            return true;

        } else {

            if (obj == null) {

                return false;

            }

            final VipOrder vipOrder = (VipOrder) obj;

            if (this.getId().equals(vipOrder.getId())) {

                return true;

            } else {

                return false;

            }

        }

    }

}
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
2. 根據訂單,結合支付寶業務物件,生成訂單資訊字串,並返回


/**

 * 支付寶生成訂單資訊

 * @param vipPrice

 * @param vo

 * @return

 */

private String aliPayHandle(VipPrice vipPrice, VipOrder vo) {

    String subject = vo.getGoodsName(); //商品標題

    String out_trade_no = vo.getOrderNum(); //商品網站唯一訂單號 32位

    String total_amount = vo.getCurrentPrice(); //支付價格

    //拼裝業務物件,可以設定個性化內容

    WorkParameter wp = new WorkParameter(subject, out_trade_no, total_amount);

    wp.setTimeout_express("30m");//收款時間

    wp.setBody(vipPrice.getName());

    String passback_params = vo.getId();

    try {

        passback_params = URLEncoder.encode(passback_params, "UTF-8");

    } catch (UnsupportedEncodingException e) {

        e.printStackTrace();

    }//URL編碼,支付寶需要以這樣的方式傳遞引數

    wp.setPassback_params(passback_params);//公用回傳引數

    String orderInfo = OrderInfoUtil2_0.getOrderInfo(wp);//生成返回字串

    return orderInfo;

}
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
OrderInfoUtil2_0工具類


import java.io.IOException;

import java.io.InputStream;

import java.io.UnsupportedEncodingException;

import java.net.URLEncoder;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Locale;

import java.util.Map;

import java.util.Properties;

import java.util.Random;

import com.fasterxml.jackson.annotation.JsonInclude.Include;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class OrderInfoUtil2_0 {

    /**

     * 構造授權引數列表

     * 

     * @param pid

     * @param app_id

     * @param target_id

     * @return

     */

    public static Map<String, String> buildAuthInfoMap(String pid,

            String app_id, String target_id) {

        Map<String, String> keyValues = new HashMap<String, String>();

        // 商戶簽約拿到的app_id,如:2013081700024223

        keyValues.put("app_id", app_id);

        // 商戶簽約拿到的pid,如:2088102123816631

        keyValues.put("pid", pid);

        // 服務介面名稱, 固定值

        keyValues.put("apiname", "com.alipay.account.auth");

        // 商戶型別標識, 固定值

        keyValues.put("app_name", "mc");

        // 業務型別, 固定值

        keyValues.put("biz_type", "openservice");

        // 產品碼, 固定值

        keyValues.put("product_id", "APP_FAST_LOGIN");

        // 授權範圍, 固定值

        keyValues.put("scope", "kuaijie");

        // 商戶唯一標識,如:kkkkk091125

        keyValues.put("target_id", target_id);

        // 授權型別, 固定值

        keyValues.put("auth_type", "AUTHACCOUNT");

        // 簽名型別

        keyValues.put("sign_type", "RSA");

        return keyValues;

    }

    /**

     * 構造支付訂單引數列表

     * 

     * @param pid

     * @param app_id

     * @param target_id

     * @return

     */

    public static Map<String, String> buildOrderParamMap(String app_id,

            WorkParameter wp) {

        InputStream in = Object.class.getResourceAsStream("/test.properties");

        Map<String, String> keyValues = new HashMap<String, String>();

        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setSerializationInclusion(Include.NON_EMPTY);

        String biz_contentStr = "";

        try {

            biz_contentStr = objectMapper.writeValueAsString(wp);

        } catch (JsonProcessingException e) {

            e.printStackTrace();

        }

        System.out.println(biz_contentStr);

        keyValues.put("app_id", app_id);

        keyValues.put("biz_content", biz_contentStr);

        keyValues.put("charset", "utf-8");

        keyValues.put("method", "alipay.trade.app.pay");

        keyValues.put("sign_type", "RSA");

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:s");

        keyValues.put("timestamp", sdf.format(new Date()));

        keyValues.put("notify_url", AlipayConfig.notify_url);

        keyValues.put("version", "1.0");

        return keyValues;

    }

    /**

     * 構造支付訂單引數資訊

     * 

     * @param map

     *            支付訂單引數

     * @return

     */

    public static String buildOrderParam(Map<String, String> map) {

        List<String> keys = new ArrayList<String>(map.keySet());

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < keys.size() - 1; i++) {

            String key = keys.get(i);

            String value = map.get(key);

            sb.append(buildKeyValue(key, value, true));

            sb.append("&");

        }

        String tailKey = keys.get(keys.size() - 1);

        String tailValue = map.get(tailKey);

        sb.append(buildKeyValue(tailKey, tailValue, true));

        return sb.toString();

    }

    /**

     * 拼接鍵值對

     * 

     * @param key

     * @param value

     * @param isEncode

     * @return

     */

    private static String buildKeyValue(String key, String value,

            boolean isEncode) {

        StringBuilder sb = new StringBuilder();

        sb.append(key);

        sb.append("=");

        if (isEncode) {

            try {

                sb.append(URLEncoder.encode(value, "UTF-8"));

            } catch (UnsupportedEncodingException e) {

                sb.append(value);

            }

        } else {

            sb.append(value);

        }

        return sb.toString();

    }

    /**

     * 對支付引數資訊進行簽名

     * 

     * @param map

     *            待簽名授權資訊

     * 

     * @return

     */

    public static String getSign(Map<String, String> map, String rsaKey) {

        List<String> keys = new ArrayList<String>(map.keySet());

        // key排序

        Collections.sort(keys);

        StringBuilder authInfo = new StringBuilder();

        for (int i = 0; i < keys.size() - 1; i++) {

            String key = keys.get(i);

            String value = map.get(key);

            authInfo.append(buildKeyValue(key, value, false));

            authInfo.append("&");

        }

        String tailKey = keys.get(keys.size() - 1);

        String tailValue = map.get(tailKey);

        authInfo.append(buildKeyValue(tailKey, tailValue, false));

        String oriSign = SignUtils.sign(authInfo.toString(), rsaKey);

        String encodedSign = "";

        try {

            encodedSign = URLEncoder.encode(oriSign, "UTF-8");

        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

        return "sign=" + encodedSign;

    }

    /**

     * 要求外部訂單號必須唯一。

     * 

     * @return

     */

    public static String getOutTradeNo(int id) {

        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssS",

                Locale.getDefault());

        Date date = new Date();

        String key = format.format(date);

        Random r = new Random();

        // 5位隨機碼

        int i = r.nextInt(100000) % (100000 - 10000 + 1) + 10000;

        // id補0

        String idStr = getIdStr(id);

        key = idStr + key + i;

        key = key.substring(0, 32);

        return key;

    }

    /**

     * id補0 字串長度12位 id不夠的補0

     * 

     * @param id

     * @return

     */

    private static String getIdStr(int id) {

        String str = "";

        if (0 < id && id < 10) {

            str = "00000000000" + id;

        }

        if (10 <= id && id < 100) {

            str = "0000000000" + id;

        }

        if (100 <= id && id < 1000) {

            str = "000000000" + id;

        }

        if (1000 <= id && id < 10000) {

            str = "00000000" + id;

        }

        if (10000 <= id && id < 100000) {

            str = "0000000" + id;

        }

        if (100000 <= id && id < 1000000) {

            str = "000000" + id;

        }

        if (1000000 <= id && id < 10000000) {

            str = "00000" + id;

        }

        return str;

    }

    /**

     * 獲取訂單資訊

     * 

     * @param wp

     *            訂單基礎資訊物件

     * @return

     */

    public static String getOrderInfo(WorkParameter wp) {

        Properties prop = new Properties();

        String appid = "";

        String private_key = "";

        InputStream in = OrderInfoUtil2_0.class

                .getResourceAsStream("zfbinfo.properties");

        try {

            prop.load(in);

            appid = prop.getProperty("appid").trim();

            private_key = prop.getProperty("private_key").trim();

        } catch (IOException e) {

            e.printStackTrace();

        }

        Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(appid,

                wp);

        String orderParam = OrderInfoUtil2_0.buildOrderParam(params);

        // 添加簽名

        String sign = OrderInfoUtil2_0.getSign(params, private_key);

        // 組裝引數

        String orderInfo = orderParam + "&" + sign;

        return orderInfo;

    }

}
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
zfbinfo.properties配置檔案(支付寶引數配置檔案)

可下載支付寶官方金鑰生成工具

地址:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1


open_api_domain =   https://openapi.alipaydev.com/gateway.do

mcloud_api_domain = http://mcloudmonitor.com/gateway.do

pid = pid

appid =  appid

private_key = 私鑰

public_key = 公鑰

alipay_public_key = 公鑰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
業務物件


/**

 * 業務引數物件

 * @author pzr

 *

 */

public class WorkParameter {

    /**

     * 【非必填】

     * 對一筆交易的具體描述資訊。如果是多種商品,請將商品描述字串累加傳給body。

     */

    private String body;

    /**

     * 【必填】

     * 商品的標題/交易標題/訂單標題/訂單關鍵字等。

     */

    private String subject;

    /**

     * 【必填】

     * 商戶網站唯一訂單號

     */

    private String out_trade_no;

    /**

     * 【非必填】

     * 該筆訂單允許的最晚付款時間,逾期將關閉交易。

     * 取值範圍:1m~15d。m-分鐘,h-小時,d-天,1c-當天(1c-當天的情況下,無論交易何時建立,都在0點關閉)。

     *  該引數數值不接受小數點, 如 1.5h,可轉換為 90m。

     */

    private String timeout_express;

    /**

     * 【必填】

     * 訂單總金額,單位為元,精確到小數點後兩位,取值範圍[0.01,100000000]

     */

    private String total_amount;

    /**

     * 【非必填】

     * 收款支付寶使用者ID。 如果該值為空,則預設為商戶簽約賬號對應的支付寶使用者ID

     */

    private String seller_id;

    /**

     * 【必填】

     * 銷售產品碼,商家和支付寶簽約的產品碼,為固定值QUICK_MSECURITY_PAY

     */

    private String product_code = "QUICK_MSECURITY_PAY";

    /**

     * 【非必填】

     * 商品主型別:0—虛擬類商品,1—實物類商品

     * 注:虛擬類商品不支援使用花唄渠道

     */

    private String goods_type;

    /**

     * 【非必填】

     * 公用回傳引數,如果請求時傳遞了該引數,則返回給商戶時會回傳該引數。

     * 支付寶會在非同步通知時將該引數原樣返回。本引數必須進行UrlEncode之後才可以傳送給支付寶

     */

    private String passback_params;

    /**

     * 【非必填】

     * 優惠引數

     * 注:僅與支付寶協商後可用

     */

    private String promo_params;

    /**

     * 【非必填】

     * 業務擴充套件引數,詳見下面的“業務擴充套件引數說明”

     */

    private String extend_params;

    /**

     * 【非必填】

     * 可用渠道,使用者只能在指定渠道範圍內支付

     * 當有多個渠道時用“,”分隔

     * 注:與disable_pay_channels互斥

     */

    private String enable_pay_channels;

    /**

     * 【非必填】

     * 禁用渠道,使用者不可用指定渠道支付

     * 當有多個渠道時用“,”分隔

     * 注:與enable_pay_channels互斥

     */

    private String disable_pay_channels;

    public WorkParameter(){

    }

    /**

     * 必填項構造方法

     * @param subject  商品的標題/交易標題/訂單標題/訂單關鍵字等。

     * @param out_trade_no 商戶網站唯一訂單號

     * @param total_amount 訂單總金額,單位為元,精確到小數點後兩位,取值範圍[0.01,100000000]

     */

    public WorkParameter(String subject,String out_trade_no,String total_amount){

        this.subject = subject;

        this.out_trade_no = out_trade_no;

        this.total_amount = total_amount;

    }

    public String getBody() {

        return body;

    }

    public void setBody(String body) {

        this.body = body;

    }

    public String getSubject() {

        return subject;

    }

    public void setSubject(String subject) {

        this.subject = subject;

    }

    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 String getTimeout_express() {

        return timeout_express;

    }

    public void setTimeout_express(String timeout_express) {

        this.timeout_express = timeout_express;

    }

    public String getTotal_amount() {

        return total_amount;

    }

    public void setTotal_amount(String total_amount) {

        this.total_amount = total_amount;

    }

    public String getSeller_id() {

        return seller_id;

    }

    public void setSeller_id(String seller_id) {

        this.seller_id = seller_id;

    }

    public String getProduct_code() {

        return product_code;

    }

    public void setProduct_code(String product_code) {

        this.product_code = product_code;

    }

    public String getGoods_type() {

        return goods_type;

    }

    public void setGoods_type(String goods_type) {

        this.goods_type = goods_type;

    }

    public String getPassback_params() {

        return passback_params;

    }

    public void setPassback_params(String passback_params) {

        this.passback_params = passback_params;

    }

    public String getPromo_params() {

        return promo_params;

    }

    public void setPromo_params(String promo_params) {

        this.promo_params = promo_params;

    }

    public String getExtend_params() {

        return extend_params;

    }

    public void setExtend_params(String extend_params) {

        this.extend_params = extend_params;

    }

    public String getEnable_pay_channels() {

        return enable_pay_channels;

    }

    public void setEnable_pay_channels(String enable_pay_channels) {

        this.enable_pay_channels = enable_pay_channels;

    }

    public String getDisable_pay_channels() {

        return disable_pay_channels;

    }

    public void setDisable_pay_channels(String disable_pay_channels) {

        this.disable_pay_channels = disable_pay_channels;

    }

}
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
app呼叫支付寶支付介面,服務端會返回支付資訊
參考:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.DO5i9o&treeId=204&articleId=105296&docType=1

關鍵程式碼:其中orderInfo就是服務端返回的訂單資訊


final String orderInfo = info;   // 訂單資訊

Runnable payRunnable = new Runnable() {

    @Override

    public void run() {

        PayTask alipay = new PayTask(DemoActivity.this);

        String result = alipay.payV2(orderInfo,true);

        Message msg = new Message();

        msg.what = SDK_PAY_FLAG;

        msg.obj = result;

        mHandler.sendMessage(msg);

    }

};

 // 必須非同步呼叫

Thread payThread = new Thread(payRunnable);

payThread.start();
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
支付寶服務端呼叫非同步通知介面處理
支付寶服務端呼叫非同步通知介面,根據返回資訊處理自己的業務邏輯,比如記入本地臺賬,修改訂單狀態等

支付寶回撥是要進行驗籤的,驗證通過後在進行操作

關鍵程式碼:


/**

 * 支付寶非同步回撥

 * 

 * @return

 * @throws UnsupportedEncodingException

 */

@RequestMapping(value = "/vipBuy.do", method = RequestMethod.POST)

@ResponseBody

public void vipBuy(HttpServletRequest request,HttpServletResponse response)

        throws UnsupportedEncodingException {

    String str = "";

    // 獲取支付寶POST過來反饋資訊

    Map<String, String> params = getReqParam(request);

    // 非同步通知ID

    String notify_id = request.getParameter("notify_id");

    // 交易狀態

    String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");

    // 獲取支付寶的通知返回引數//

    if (notify_id != "" && notify_id != null) {// //判斷接受的post通知中有無notify_id,如果有則是非同步通知。

        boolean signVerified = false;//驗籤標誌

        try {

            signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, "UTF-8");

        } catch (AlipayApiException e) {

            e.printStackTrace();

        }

        // 使用支付寶公鑰驗籤

        if (signVerified){

            //獲取回傳引數passback_params,即是訂單id

            String orderNum = vaan.getOut_trade_no();//訂單號

            String orderId = vaan.getPassback_params();//訂單id

            //通過訂單id和訂單編號獲取指定的訂單資訊

            VipOrder vo = new VipOrder();

            vo.setId(orderId);

            vo.setOrderNum(orderNum);

            vo = vipOrderMapper.findByIdOrderNum(vo);

            //驗證請求真實性

            boolean flag = checkTrue(vaan,vo);

            if(flag){

                //各狀態處理

                if (trade_status.equals("TRADE_FINISHED")) {

                    //沒有涉及退款流程,支付完成即是交易結束

                } else if (trade_status.equals("TRADE_SUCCESS")) {

                    //交易成功的邏輯處理

                }

                str = "success";

            }else{

                str = "failure";

            }

        } else{

            // 驗證簽名失敗

            str = "failure";

        }

    } 

    //返回給支付寶,以免導致重複傳送資料

    //程式執行完後必須列印輸出“success”(不包含引號)。

    //如果商戶反饋給支付寶的字元不是success這7個字元,支付寶伺服器會不斷重發通知,直到超過24小時22分鐘。

    //一般情況下,25小時以內完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h);

    try {

        response.getWriter().print(str);

        System.out.println(str);

    } catch (IOException e) {

        e.printStackTrace();

    }

}

/**

 * 獲取支付寶回撥傳入的引數,包含訂單的所有資訊

 * @param request

 * @return

 */

private Map<String, String> getReqParam(HttpServletRequest request) {

    Map<String, String> params = new HashMap<String, String>();

    Map<String, String[]> requestParams = request.getParameterMap();

    // 遍歷引數

    for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {

        String name = (String) iter.next();

        String[] values = (String[]) requestParams.get(name);

        String valueStr = "";

        for (int i = 0; i < values.length; i++) {

            valueStr = (i == values.length - 1) ? valueStr + values[i]

                    : valueStr + values[i] + ",";

        }

        // 亂碼解決,這段程式碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段程式碼轉化

        // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");

        params.put(name, valueStr);

    }

    return params;

}

/**

 * 通過支付型別和支付返回結果,獲取支付狀態 1-支付成功 2-支付失敗

 * @param return_code

 * @param string

 * @return

 */

private Integer getPayState(String returnCode, String payType) {

    if(payType.equals(VipOrder.PAYMODE_WX)){

        if(returnCode.equals("SUCCESS")){

            return 1;

        }else{

            return 2;

        }

    }else if(payType.equals(VipOrder.PAYMODE_ALIPAY)){

        if(returnCode.equals("TRADE_SUCCESS") || returnCode.equals("TRADE_FINISHED")){

            return 1;

        }else{

            return 2;

        }

    }

    return 2;

}

/**

 * 驗證資料真實性

 * 一、需要驗證該通知資料中的out_trade_no是否為商戶系統中建立的訂單號

 * 二、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額)

 * 三、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email)

 * 四、驗證app_id是否為該商戶本身

 * 五、驗證本地訂單狀態是否是已經成功的,避免重複購買會員

 * 上面驗證都通過,才可以進行後續工作

 * @param vaan

 * @return

 */

private boolean checkTrue(VipAlipayAsynNotify vaan,VipOrder vo) {

    //一、需要驗證該通知資料中的out_trade_no是否為商戶系統中建立的訂單號

    //如果不存在則說明反饋的訂單號和訂單id對不上

    if(vo == null){

        return false;

    }

    //支付完成,也不在進行業務處理

    if(vo.getPayState().equals(VipOrder.PAYSTATE_SUCCESS) || vo.getPayState().equals(VipOrder.PAYSTATE_FINISHED)){

        return false;

    }

    //二、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額)

    if(!vo.getCurrentPrice().equals(vaan.getTotal_amount())){

        return false;

    }

    //三、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email)

    //四、驗證app_id是否為該商戶本身