[Java] 支付寶(alipay) 境內支付API介接與實作

[Java] 支付寶(alipay) 境內支付API介接與實作

最近因為公司業務拓展需求,需要將公司的平台導入支付寶(alipay)的API,讓買家可以直接使用支付寶付款。原本以為這是一個很簡單的任務,沒想到因為對支付寶API的不熟悉,來來回回折騰了將近兩週才將支付功能開發完成。

支付寶的支付接口分為境內支付以及國際支付兩種。所謂的境內支付指的是買家用人民幣付款,賣家用人民幣取款。而國際支付則是買家用人民幣付款,賣家可以用指定幣種取款。因為我的公司是紐西蘭公司,當然就是用紐幣取款了。

這兩種支付接口所使用的API以及傳入的參數是完全不一樣的,但因為一開始不知道兩種支付方式使用不同的API,一直在螞蟻金服開放平台上的官方文件糾結了很久。且網路上的資訊新舊交雜,導致程式都寫完了才發現螞蟻金服開放平台上演示的API只能用於境內支付。恍然大悟之後改去用國際支付的API,程式重新撰寫配置才將此支付功能完成。


既然境內支付跟國際支付的兩個API我都嘗試過了,就索性寫下來做個紀錄吧。

境內支付API

關於境內支付的API使用方式,螞蟻金服開放平台寫得還算詳細,且有沙箱測試功能可以使用(但是錯誤率非常高,付款20次大約只有1次會成功…)。想要介接支付寶API,首先必須使用經過實名認證後的帳號,以及跟支付寶申請開通支付服務。至於怎麼申請我並不清楚,這邊只展示如何使用沙箱測試以及介接支付寶API。

⬇︎使用沙箱時出現的各種錯誤訊息…
2016-08-30 20.06.49

一、沙箱環境配置

首先先點擊開放平台-管理中心-沙箱環境進入沙箱配置畫面。首先會看到一組2016開頭的16位APPID,這是要傳入Alipay API使用的參數之一。
Screen Shot 2016-08-30 at 11.49.16

應用網關跟授權回調地址似乎沒什麼作用,可以暫時略過。

RSA密鑰依照網頁指示下載支付寶密鑰生成器產生密鑰即可。
Screen Shot 2016-08-30 at 11.49.45

要確保你的環境可以執行Java程式才能夠執行支付寶密鑰生成器。正確執行之後會產生三個檔案1. rsa_private_key_pkcs8.pem, 2. rsa_private_key.pem, 3. rsa_public_key.pem。用文字編輯器開啟 rsa_public_key.pem ,將 -----BEGIN PUBLIC KEY----------END PUBLIC KEY----- 之間的公鑰字串貼入應用公鑰的位置,用它來換取支付寶公鑰。支付寶公鑰以及 rsa_private_key_pkcs8.pem 也必須是要傳入Alipay API的參數。
Screen Shot 2016-08-30 at 11.59.27

點擊測試帳號頁籤會看到賣家、買家的測試帳號,其中2088開頭16位數字的商戶UID也是傳入支付寶API的必要參數之一。
Screen Shot 2016-08-30 at 11.50.03

二、開發環境配置

新版的境內支付Alipay API有Java, PHP以及.NET三種平台的SDK可以使用。將Alipay SDK jar檔放到IDE的專案中,並且配置好classpath後,基本的開發環境也就配置完畢。

使用Alipay API之前,先準備好ALIPAY沙箱測試網址、APP_IDRSA_PRIVATE_KEY_PKCS8、ALIPAY公鑰。

private static final String ALIPAY_DEV_GATEWAY = "https://openapi.alipaydev.com/gateway.do?";
private static final String APP_ID = "2016...........";
private static final String RSA_PRIVATE_KEY_PKCS8 = "MII....ADANBgkqh.......;
private static final String CHARSET = "UTF-8";
private static final String ALIPAY_PUBLIC_KEY = "MIGf........";

三、使用Alipay API

若Alipay的SDK有正確配置,則可以正確初始化AlipayClient

AlipayClient alipayClient = new DefaultAlipayClient(ALIPAY_DEV_GATEWAY, APP_ID, RSA_PRIVATE_KEY_PKCS8, "json", CHARSET, ALIPAY_PUBLIC_KEY);

我們公司的銷售平台想要讓買家在手機上瀏覽商品後,使用手機下單支付,因此我們使用的是手機網站支付API:alipay.trade.wap.pay

舊的API需要自行處理signature以及確認signature,新版的SDK都幫開發者處理好了,我們只要確保傳進去的參數是正確的。這裡要注意的是return_url以及notify_url必須要是外網能夠訪問的public網址,也就是說不能為localhost或是192.168.1.1之類的內網網址(網址通常會與你的正式平台環境相同)。且網址後面禁止使用?接任何參數。而out_trade_no是訂單號,必須要確保每次傳入時都不相同。Seller_id填賣家的UID,Product_code則是”QUICK_WAP_PAY“。

以下是一段使用Spring REST framework的demo code:


    public static final String PRODUCT_CODE = "QUICK_WAP_PAY";
    public static final String UID = "2088.......";//賣家測試帳號中的UID
    public static final String RETURN_URL = "http://alipay.test.doReturn";
    public static final String NOTIFY_URL = "http://alipay.test.norifyReceive";

    @RequestMapping(value = "doAliPay", method = RequestMethod.GET)
    public void doAlipay(@RequestParam(value = "number") String orderNo,HttpServletResponse response) {
        AlipayBizContent alipayBizContent = new AlipayBizContent();
        alipayBizContent.setOut_trade_no(orderNumber);
        alipayBizContent.setTotal_amount(TOTAL_PRICE);
        alipayBizContent.setSeller_id(UID);
        alipayBizContent.setSubject("ALIPAY TEST");
        alipayBizContent.setProduct_code(PRODUCT_CODE);

        ObjectMapper objectMapper = new ObjectMapper();
        String bizContentJson = "";
        try {
            bizContentJson = objectMapper.writeValueAsString(alipayBizContent);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        //alipay.trade.wap.pay API = AlipayTradeWapPayRequest
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
        alipayRequest.setReturnUrl(RETURN_URL);
        alipayRequest.setNotifyUrl(NOTIFY_URL);
        alipayRequest.setBizContent(bizContentJson);

        String form;
        try {
            form = alipayClient.pageExecute(alipayRequest).getBody();
            response.setContentType("text/html;charset=" + CHARSET);
            response.getWriter().write(form);
            response.getWriter().flush();
            } catch (AlipayApiException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

//AlipayBizContent Java Bean
public class AlipayBizContent {
    private String out_trade_no;
    private String subject;
    private String seller_id;
    private double total_amount;
    private String product_code;

    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 getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getSeller_id() {
        return seller_id;
    }

    public void setSeller_id(String seller_id) {
        this.seller_id = seller_id;
    }

    public double getTotal_amount() {
        return total_amount;
    }

    public void setTotal_amount(double total_amount) {
        this.total_amount = total_amount;
    }

    public String getProduct_code() {
        return product_code;
    }

    public void setProduct_code(String product_code) {
        this.product_code = product_code;
    }
}

若以上配置都正確,當發送HTTP GET請求到 /doAliPay時,畫面應該要出現支付寶登入付款的頁面。用沙箱測試中的買家帳號登入,若『運氣好』的話應該會出現支付成功的訊息。
s1

若API可以使用,強烈建議直接使用正式環境測試,因為沙箱的錯誤訊息太多,以至於很難測試notify_url。使用正式環境測試前要先去『建立應用』。
alipay8

alipay9

alipay10

接著依照畫面提示如同沙箱測試的步驟設定RSA密鑰。
alipay11

也記得去功能頁面添加你的平台所需要的服務功能。以我們的平台為例就是手機網站支付。
alipay12

最後拿正式環境的支付寶網關、商戶UID、APP_ID、支付寶公鑰以及rsa_private_key_pkcs8.pem置換到程式中。

四、支付回覆通知之參數驗證

成功付款之後,支付寶會回傳訊息到你所設定的return_url (GET request)以及notify_url (POST request)。假設你的notify_url為https://api.co.nz/receive_notify,return_url為https://api.co.nz/return,兩者收到的通知會是一樣的,只是前面的網址不相同:

https://api.co.nz/receive_notify.htm?total_amount=2.00&
buyer_id=2088102116773037&
body=AlipayTest2.1&
trade_no=2016071921001003030200089909&
refund_fee=0.00&
notify_time=2016-07-19 14:10:49&
subject=AlipayTest2.12.1&
sign_type=RSA&
charset=utf-8&
notify_type=trade_status_sync&
out_trade_no=0719141034-6418&
gmt_close=2016-07-19 14:10:46&
gmt_payment=2016-07-19 14:10:47&
trade_status=TRADE_FINISHED&
version=1.0&
sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+
lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDx
h5AaT9FPqRS6ZKxnzM=&
gmt_create=2016-07-19 14:10:44&
app_id=2015102700040153&
seller_id=2088102119685838&
notify_id=4a91b7a78a503640467525113fb7d8bg8e

return_url返回的參數可以忽略,但我們必須檢查從notify_url返回的參數total_amount, out_trade_no是否與資料庫的相同。此外還要驗證sign是否符合。若有任何一項參數不同,則代表此次付款有異常。檢查的方式是將得到的參數除去掉sign以及sign_type後,將剩餘參數放到HashMap中,並用AlipaySDK去檢驗。

以下是一段檢驗參數的程式範例:


Map<String, String> paramMap = new HashMap<>();
param.put("total_amount", TOTAL_AMOUNT);
param.put("buyer_id", BUYER_ID);
param.put("body", BODY);
param.put("trade_no", TRADE_NO);
param.put("refund_fee", REFUND_FEE);
param.put("notify_time", NOTIFY_TIME);
param.put("subject", SUBJECT);
param.put("charset", CHARSET);
param.put("notify_type", NOFITY_TYPE);
param.put("out_trade_no", OUT_TRADE_NO);
param.put("gmt_close", GMT_CLOSE);
param.put("gmt_payment", GMT_PAYMENT);
param.put("gmt_create", GMT_CREATE);
param.put("trade_status", TRADE_STATUS);
param.put("version", VERSION);
param.put("app_id", APP_ID);
param.put("seller_id", SELLER_ID);
param.put("notify_id", NOTIFY_ID);

boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET);
if(signVerfied){
    response.getWriter().write("Success");
} else {
    response.getWriter().write("Failure");
}

Reference links:

(Visited 1,133 time, 1 visit today)
Facebooktwittergoogle_plusredditpinterestlinkedinmail

2 thoughts on “[Java] 支付寶(alipay) 境內支付API介接與實作

    1. 我自己也不了解,幫不上忙(很久沒碰了)。請自行查詢官網資料囉。

Comments are closed.

Comments are closed.