PayPal支付接入


在騷擾了PayPal的技術支持好幾天以後終於成功對接了PayPal支付,很是感謝PayPal的技術支持人員,沒有她估計一週都搞不定。記錄一下這個過程。java

接到這個任務聯繫了PayPal的技術以後,第一件事就是向她要了一些文檔。PayPal提供了一個demo商店https://demo.paypal.com/c2/demo/navigation?merchant=bigbox&page=shoppingCart&locale.x=zh_XC&token=EC-8F899625FB177521Vgit

,首先我在上面體驗了一把PayPal支付的整個流程,登陸sandbox帳號而後支付就好了,能夠看下面的截圖感覺一下,github

 

 

sandbox帳號是你註冊了企業帳號以後PayPal送給你的兩個測試用帳號,你能夠在sandbox環境中測試你的代碼,固然你也能夠本身註冊sandbox帳號,當商務部給我一個企業帳號密碼時,我登陸https://developer.paypal.com/developer/accounts?event=linkAccountAssociatedweb

能夠看到裏面的sandbox帳戶,我選擇新建一個本身的sandbox商家帳戶,與live環境一樣且必需要作的是申請簽名:https://www.sandbox.paypal.com/webapps/customerprofile/summary.viewapi

(切記!!!sandbox環境用live環境的簽名會報"security header is not valid",這是sandbox的簽名,一樣的,live環境只須要域名中去掉tomcat

sandbox便可找到),以下圖服務器

PayPal的API有提供兩種調用方式,NVP和SOAP,我選擇了前者。支持方式是IPN,通常都是選擇IPN,由於咱們開發基本上都要根據支付平臺的結果處理一下本身的業務,在看了IPN這個文檔https://www.paypal-biz.com/product/pdf/PayPal_IPN&PDT_Guide_V1.0.pdf以後,大體瞭解了和PayPal的交互流程,文檔中的notify_url是PayPal在你調用DoEC後回調你的連接,PayPal會在請求你連接時帶上一些訂單的參數,詳請點擊app

https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/webapp

作了這些準備工做以後咱們能夠開始寫代碼了。ide

此處應當插入個時序圖的-_-!!

調用API都會用到的公共參數是:"USER=""PWD=""SIGNATURE=""VERSION=",後面再也不贅述

下訂單調用PayPal的SetExpressCheckout方法,能夠參考這個已通過驗證的示例(密碼簽名記得用本身申請的哦_):

附上大家也許用得着的代碼:

Map<String, String> nvpMap = PayPalUtil.setExpressCheckout(params);
if (nvpMap != null && nvpMap.get("ACK") != null) {
String strAck = nvpMap.get("ACK").toUpperCase();
if (strAck.equals("SUCCESS")
|| strAck.equals("SUCCESSWITHWARNING")) {

String checkoutUrl = PayPalUtil.checkoutUrl;
String token = checkoutUrl, nvpMap.get("TOKEN");//下單成功以後能夠保存此token以作後續調用API之用

//do something

} else {
String ErrorCode = nvpMap.get("L_ERRORCODE0");
String ErrorShortMsg = nvpMap.get("L_SHORTMESSAGE0");
String ErrorLongMsg = nvpMap.get("L_LONGMESSAGE0");
String ErrorSeverityCode = nvpMap.get("L_SEVERITYCODE0");
String errorString = "SetExpressCheckout API call failed. "
+ "Detailed Error Message: " + ErrorLongMsg
+ "Short Error Message: " + ErrorShortMsg
+ "Error Code: " + ErrorCode
+ "Error Severity Code: " + ErrorSeverityCode;
log.error(errorString);

}
}

 

具體的請求參數以及響應說明在這裏:https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/

下單成功拿到與此訂單相關的token以後,用戶點擊continue付款以後PayPal會請求你在setExpressCheckout中傳給PayPal的RETURNURL,PayPal帶來的請求參數中只有兩個參數,

一個token,一個payerID這兩個參數都是在調用最後一個DoExpressCheckout API的時候要傳的,PayPal的請求可能以下示例:

http://XXX.com/paypal?token=EC-0B0244963D237432J&PayerID=93JGVG4CSVCN4 ,PayerID爲買家的account ID.所以,你必須提供一個處理類來處理PayPal的請求,接到PayPal請求以後,接下來你要作的是調用API GetExpressCheckoutDetails

須要帶上兩個參數:METHOD=GetExpressCheckoutDetails&TOKEN=DJFJSLDFJS ,這個API並非必須調用,建議調用來獲取訂單信息和訂單狀態,好比金額等作風控校驗。響應碼詳情請參考:
https://developer.paypal.com/docs/classic/api/merchant/GetExpressCheckoutDetails_API_Operation_NVP/

來到這裏,離成功只差一步了,那就是在上面確認了訂單無誤以後調用API DoExpressCheckoutPayment告訴PayPal這個交易沒錯,你能夠扣錢了~~~最後你能夠再重定向到本身的商戶頁面。如下參數爲必須:
parasMap.put("METHOD", "DoExpressCheckoutPayment");
parasMap.put("TOKEN", token);
parasMap.put("PAYERID", payerId);
parasMap.put("PAYMENTREQUEST_0_PAYMENTACTION", "Sale");//sale是當即到帳,order是預受權,通常都是sale
parasMap.put("PAYMENTREQUEST_0_AMT", amount);
parasMap.put("PAYMENTREQUEST_0_NOTIFYURL", notify_url);//這個notify_url是成功調用DoExpressCheckoutPayment後PayPal最後請求你的處理器,你能夠作一些風險控制。裏面一樣會傳不少的訂單數據給你。


DoExpressCheckoutPayment的請求與相應:
https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/DoExpressCheckoutPayment_API_Operation_NVP/

PayPal在最後請求你的notify_url中所帶來的參數以下:
https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/


另外的兩個用於查詢訂單的API你也許也會用獲得:

TransactionSearch API Operation (NVP) : https://developer.paypal.com/docs/classic/api/merchant/TransactionSearch_API_Operation_NVP/

GetTransactionDetails API Operation (NVP) : https://developer.paypal.com/docs/classic/api/merchant/GetTransactionDetails_API_Operation_NVP/

然而有一個問題,所需的參數txn_id是PayPal在notify_url請求時才帶來的,這但是最後一個交互了,萬一丟了這個訂單豈不是再也查不到了?別急,看本文最後的說明。

PayPal錯誤碼參考:
https://developer.paypal.com/docs/classic/api/errorcodes/


有幾點最後但卻很是重要的須要說明:

1.用心的同窗估計已經發現了,以上所述與IPN的文檔少了一步,那就是IPN文檔"1.3 通知確認- 給PayPal的https回撥",這步在我諮詢了PayPal的技術支持後我直接省略了,正如文檔中所說,假如你知足以下條件,建議保留此步:

您的網站是放在共享服務器上的;您未在您的 Web 服務器上啓用 SSL;

2.如果你丟失了PayPal的notify_url請求,你能夠根據TransactionSearch這個API去查某個訂單詳情,有一個INVNUM的參數,若是你在SetExpressCheckOut時傳過去了,PayPal會幫你保存在訂單中,你能夠用此參數得到某一個訂單,所以這個參數你在SetEC

的時候即可以傳給PayPal,值通常取本身這邊的訂單號。這樣即便你最後收不到notify_url你後續也能夠根據你本身的訂單號去獲取訂單。這裏也有一個問題須要切記,這個API中的STARTDATE必需要是GMT時間,轉換代碼以下能夠參考:

// Must be a valid date, in UTC/GMT format; for example,
// 2013-08-24T05:38:48Z.
// 將北京時間轉爲GMT時間,此處直接減一天好了,雖然只是相差8個小時
SimpleDateFormat pSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
DateFormat fSdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
fSdf.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
Calendar temp = Calendar.getInstance();
temp.setTime(pSdf.parse(startdate));
temp.add(Calendar.DAY_OF_YEAR, -1);
startdate = fSdf.format(temp.getTime());
} catch (ParseException e1) {
e1.printStackTrace();
log.error("時間轉換失敗,startdate爲:" + startdate);
return null;
}

 

3.若在你部署了本地環境以後,測試時PayPal沒法請求到你的局域網,你能夠安裝localtunnel,它能夠幫助你
http://stackoverflow.com/questions/11469636/paypal-sandbox-test-tool-ipn-simulator-in-localhost
https://localtunnel.me/

4.如果你的代碼沒法請求到PayPal的API,那頗有可能你的通訊協議不是TLSv1.2,PayPal會在2016年六月底全面啓用TLSv1.2,這是一份聲明:https://github.com/paypal/TLS-update

解決辦法有兩個:

一,配置你的服務器支持TLSv1.2,jdk1.7是有的,但並未顯示支持,你須要配置jdk使之顯示支持TLSv1.2;你能夠參考
http://stackoverflow.com/questions/34963083/paypal-sandbox-api-javax-net-ssl-sslhandshakeexception-received-fatal-alert-h
http://stackoverflow.com/questions/9749339/does-tomcat-supports-tls-v1-2


二,升級至jdk8,默認支持,直接可用。

5.如果你的代碼報"peer not authenticated"這個錯誤,說明你的服務器證書不受信任。

解決辦法:增長以下方法,

HttpClient httpclient = new DefaultHttpClient();

httpclient = wrapClient(httpclient);

/**
* 獲取可信任https連接,以免不受信任證書出現peer not authenticated異常
*
* @param base
* @return
*/
public static HttpClient wrapClient(HttpClient base) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {

@Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1) throws CertificateException {
// TODO Auto-generated method stub

}

@Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1) throws CertificateException {
// TODO Auto-generated method stub

}

@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));

return new DefaultHttpClient(ccm, base.getParams());
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
相關文章
相關標籤/搜索