PayPal checkout 金流串接實現自動履行訂單
最近幫專題增加金流功能,使用了PayPal提供的方案,順便做點心得紀錄
PayPal提供了多種API供選擇,可惜文件寫得有點分散且不夠詳盡,讓第一次接觸金流API這部分的我吃了不少苦頭
欲實現目的
使用者在網站付款後自動實現充值
環境與方案
前端付款方案:PayPal Checkout (https://developer.paypal.com/docs/checkout/)
收款通知方案:Instant Payment Notificatio(IPN)(https://developer.paypal.com/docs/classic/products/instant-payment-notification/)
前端:ReactJS
後端:Laravel
這邊不得不提一下PayPal提供了多種的前端付款方案除了本文使用的PayPal Checkout還有Html form方案目的都是幫助開法者快速建立付款按鈕,似乎還有單純使用API建立帳單的方式,看文件的時候沒注意真的會很容易被搞混...
在收到款項通知部分本以為可以有API去打,查閱開發文件後似乎是沒有這種選項,必須自己在伺服器建立IPN聆聽器來接收付款通知
流程邏輯
使用者發起付款(包含一組識別用invoice)->在自己伺服器建立紀錄
->在paypal建立帳單->引導至paypal付款->paypal將付款結果通知IPN
->將付款通知中的invoice與自己記錄的invoce進行match並且更新資料庫
->完成
建立付款按鈕
開始首要建立付款按鈕,在React部分我參考了這篇,若非使用React的則可參考官方文件,整體來說差異不大
而checkout button所需的token則可以在https://developer.paypal.com/找到
※DASHBOARD>My Apps & Credentials>REST API apps>Create App或是點擊已存在App
在React建立好的checkout button大概長這樣
-
-
元件使用。此例中使用亂數字串當作發票號碼
-
-
訂單追蹤
接著看回React checkout button範例中的line:62,我在這邊示意你要在發起paypal訂單前紀錄自記產生的發票號碼(invoice)與購買資訊,當然也必須將這組發票號碼交由paypal(line:74),
我這邊使用較為簡單的做法(但是可能會產生竄改風險),直接在前端call paypal create payment api,由於我的專題僅賣一種商品,所以我只需知道有帳單進來,並且更新資料庫即可,若有多種商品與數量則建議參考官方文件在後端執行這個動作
建立IPN
官方有提供範例程式
自己在測試的時候曾經嘗試整合在laravel中,但是在IPN simulator中會發生錯誤,我沒找到解決方法,但是估計是laravel中間處理了太多的東西導致資訊變動所以失敗,最我將sample code放置在public資料夾下成功收到IPN訊息,在以call api方式更新資料庫
由於IPN程式無法藉由頁面debug,所以必須察看或是自己寫log
linux & apache下 PHP的錯誤會被記錄在/var/log/apache2
然後我們可以將收到的IPN訊息寫入log,linux下記得將讀寫權限開放chmod -R 777 your_directory_name
修改後的ipn(PHP)
-
-
注意line:30 有時候ipn發生錯誤是沒辦法使用正確的驗證檔案,我在這邊調用了usePHPCerts()解決這個問題(官方範例預設未使用)
更多變數參考https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/
之後若有通知進來則會寫入log/paypal下
IPN付款通知
要打開付款通知需要先前往https://www.sandbox.paypal.com或是https://www.paypal.com
設定>我的銷售工具>即時付款通知>你的IPN網址>Turn on IPN
收尾
最後可以使用payapl官方提供的IPN simulator進行測試(需登入),然後在收到IPN訊息且狀態為成功的狀況下比對invoice(自訂的)來履行訂單
PayPal提供了多種API供選擇,可惜文件寫得有點分散且不夠詳盡,讓第一次接觸金流API這部分的我吃了不少苦頭
欲實現目的
使用者在網站付款後自動實現充值
環境與方案
前端付款方案:PayPal Checkout (https://developer.paypal.com/docs/checkout/)
收款通知方案:Instant Payment Notificatio(IPN)(https://developer.paypal.com/docs/classic/products/instant-payment-notification/)
前端:ReactJS
後端:Laravel
這邊不得不提一下PayPal提供了多種的前端付款方案除了本文使用的PayPal Checkout還有Html form方案目的都是幫助開法者快速建立付款按鈕,似乎還有單純使用API建立帳單的方式,看文件的時候沒注意真的會很容易被搞混...
在收到款項通知部分本以為可以有API去打,查閱開發文件後似乎是沒有這種選項,必須自己在伺服器建立IPN聆聽器來接收付款通知
流程邏輯
使用者發起付款(包含一組識別用invoice)->在自己伺服器建立紀錄
->在paypal建立帳單->引導至paypal付款->paypal將付款結果通知IPN
->將付款通知中的invoice與自己記錄的invoce進行match並且更新資料庫
->完成
建立付款按鈕
開始首要建立付款按鈕,在React部分我參考了這篇,若非使用React的則可參考官方文件,整體來說差異不大
而checkout button所需的token則可以在https://developer.paypal.com/找到
※DASHBOARD>My Apps & Credentials>REST API apps>Create App或是點擊已存在App
在React建立好的checkout button大概長這樣
-
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import ReactDOM from 'react-dom' | |
import scriptLoader from 'react-async-script-loader'; | |
class PayPalBtn extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
showButton: false | |
} | |
window.React = React; | |
window.ReactDOM = ReactDOM; | |
} | |
componentDidMount() { | |
const { | |
isScriptLoaded, | |
isScriptLoadSucceed | |
} = this.props; | |
if (isScriptLoaded && isScriptLoadSucceed) { | |
this.setState({ showButton: true }); | |
} | |
} | |
componentWillReceiveProps(nextProps) { | |
const { | |
isScriptLoaded, | |
isScriptLoadSucceed, | |
} = nextProps; | |
const isLoadedButWasntLoadedBefore = | |
!this.state.showButton && | |
!this.props.isScriptLoaded && | |
isScriptLoaded; | |
if (isLoadedButWasntLoadedBefore) { | |
if (isScriptLoadSucceed) { | |
this.setState({ showButton: true }); | |
} | |
} | |
} | |
render() { | |
const { | |
total, | |
currency, | |
env, | |
commit, | |
client, | |
onSuccess, | |
onError, | |
onCancel, | |
invoice | |
} = this.props; | |
const { | |
showButton, | |
} = this.state; | |
const payment = () =>{ | |
//create payment here | |
//http post to my api with unique "invoice" | |
//... | |
//make a new payment | |
return paypal.rest.payment.create(env, client, { | |
transactions: [ | |
{ | |
amount: { | |
total, | |
currency, | |
}, | |
invoice_number:invoice, //we can track tranction with invoice | |
}, | |
], | |
}); | |
} | |
const onAuthorize = (data, actions) =>{ | |
console.log('data',data); | |
actions.payment.execute() | |
.then(() => { | |
const onSuccessPayment = { | |
paid: true, | |
cancelled: false, | |
payerID: data.payerID, | |
paymentID: data.paymentID, | |
paymentToken: data.paymentToken, | |
returnUrl: data.returnUrl, | |
}; | |
onSuccess(onSuccessPayment); | |
}); | |
} | |
return ( | |
<div> | |
{showButton && <paypal.Button.react | |
env={env} | |
client={client} | |
commit={commit} | |
payment={payment} | |
onAuthorize={onAuthorize} | |
onCancel={onCancel} | |
onError={onError} | |
/>} | |
</div> | |
); | |
} | |
} | |
export default connect()(scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PayPalBtn)); |
元件使用。此例中使用亂數字串當作發票號碼
-
訂單追蹤
接著看回React checkout button範例中的line:62,我在這邊示意你要在發起paypal訂單前紀錄自記產生的發票號碼(invoice)與購買資訊,當然也必須將這組發票號碼交由paypal(line:74),
我這邊使用較為簡單的做法(但是可能會產生竄改風險),直接在前端call paypal create payment api,由於我的專題僅賣一種商品,所以我只需知道有帳單進來,並且更新資料庫即可,若有多種商品與數量則建議參考官方文件在後端執行這個動作
建立IPN
官方有提供範例程式
自己在測試的時候曾經嘗試整合在laravel中,但是在IPN simulator中會發生錯誤,我沒找到解決方法,但是估計是laravel中間處理了太多的東西導致資訊變動所以失敗,最我將sample code放置在public資料夾下成功收到IPN訊息,在以call api方式更新資料庫
由於IPN程式無法藉由頁面debug,所以必須察看或是自己寫log
linux & apache下 PHP的錯誤會被記錄在/var/log/apache2
然後我們可以將收到的IPN訊息寫入log,linux下記得將讀寫權限開放chmod -R 777 your_directory_name
修改後的ipn(PHP)
-
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php namespace Listener; | |
use PaypalIPN; | |
require('PaypalIPN.php'); | |
function write_log($str,$status,$data_array) //傳入資料夾名 想寫近的狀態 資料 | |
{ | |
$textname = $str.date("Ymd").".txt"; //檔名 filename | |
$URL = "./log/".$str."/"; //路徑 Path | |
if(!is_dir($URL)) // 路徑中的$str 資料夾是否存在 Folder exists in the path | |
mkdir($URL,0700); | |
$URL .= $textname; //完整路徑與檔名 The full path and filename | |
$time = $str.$status.":".date("H:i:s"); //時間 Time | |
$writ_tmp = ''; | |
foreach ($data_array as $key => $value) //將陣列資料讀出 To read array data | |
{ | |
$writ_tmp .= ",".$key."=".$value; | |
} | |
$write_data = $time.$writ_tmp."\n"; | |
$fileopen = fopen($URL, "a+"); | |
fseek($fileopen, 0); | |
fwrite($fileopen,$write_data); //寫資料進去 write data | |
fclose($fileopen); | |
} | |
$ipn = new PaypalIPN(); | |
// Use the sandbox endpoint during testing. | |
$ipn->usePHPCerts(); | |
$ipn->useSandbox(); | |
$verified = $ipn->verifyIPN(); | |
if ($verified) { | |
/* | |
* Process IPN | |
* A list of variables is available here: | |
* https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/ | |
*/ | |
write_log('payapl','test',[ | |
'item_name'=>$_POST["item_name"], | |
'payment_status'=>$_POST['payment_status'], | |
'pending_reason'=>$_POST['pending_reason'], | |
'invoice'=>$_POST['invoice'], | |
'txn_id'=>$_POST['txn_id'], | |
]); | |
if($_POST['payment_status']=='Completed'){ | |
//call api 履行訂單 | |
} | |
} | |
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly. | |
header("HTTP/1.1 200 OK"); |
注意line:30 有時候ipn發生錯誤是沒辦法使用正確的驗證檔案,我在這邊調用了usePHPCerts()解決這個問題(官方範例預設未使用)
更多變數參考https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/
之後若有通知進來則會寫入log/paypal下
IPN付款通知
要打開付款通知需要先前往https://www.sandbox.paypal.com或是https://www.paypal.com
設定>我的銷售工具>即時付款通知>你的IPN網址>Turn on IPN
收尾
最後可以使用payapl官方提供的IPN simulator進行測試(需登入),然後在收到IPN訊息且狀態為成功的狀況下比對invoice(自訂的)來履行訂單
留言
張貼留言