Create Laravel Project
Laravel new AllPay |
Let’s initialize git, it’s a must be!
git init
Download AllPay SDK, in this article, we will use AllPay PHP SDK
git clone https://github.com/o-pay/Payment_PHP
Move SDK Laravel’s app folder
cp Payment_PHP/sdk/AllPay.Payment.Integration.php AllPay/app/
Create a testing controller
php artisan make:controller PaymentsController
Move the example in the SDK package to our Controller as follows:
/** |
Let’s move some sensitive information into .env file
$obj->HashKey = env('HASHKEY'); |
- In .env:
ALLPAYRETURNURL=https://163be100.ngrok.io/api/paymentsResponse
HASHKEY=5294y06JbISpM5x9
HASHIV=v77hoKGq4kWxNNIS
MERCHANTID=2000132
Use use
to replace include
Delete
include('AllPay.Payment.Integration.php');
Add it in AllPay.Payment.Integration.php到composer.json file
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
},
"files": [
"app/Helpers.php",
"app/AllPay.Payment.Integration.php"
]In terminal, under AllPay project
composer dump-autoload
In the PaymentsController, use all those required classes
namespace App\Http\Controllers;
use AllInOne;
use EncryptType;
use Exception;
use Illuminate\Http\Request;
use PaymentMethod;
Create payment order (It depends on your need.)
$totalAmount = Order::getTotalAmountForPayments($orders); |
Create an API for PaymentsController
Add new route in api.php under routes folder
Route::post('pay', 'PaymentsController@pay');
Create a simplest HTML
You could simply revise the content of default welcome.blade page as follows:
<!DOCTYPE html>
<html>
<head>
<title>Facebook Login JavaScript Example</title>
<meta charset="UTF-8">
</head>
<body>
// 這邊需輸入PaymentsController的API
<form action="/api/pay" method="POST">
@csrf()
<input type="checkbox" value="1" name="order_id[]">
<input type="checkbox" value="2" name="order_id[]">
<input type="checkbox" value="3" name="order_id[]">
<input type="hidden" value="https://64b30ea0.ngrok.io/" name="ClintBackURL">
<button type="submit">Submit</button>
</form>
</body>
</html>
Simple test
Now we are on the default page of Laravel, it should change to the simple form we just created
Check nothing, go summit
Yes, we successfully arrived payment page
Create a log
In order to get what AllPay will send to us after the payment is made, we need to use Log to see what we will receive.
Is there some place that all requests and responses will have to go through where we could have full accessibility? It seems to be a perfect one for logging
We could make a middleware, and use Log function of Laravel therein to log whatever it goes through
Make a middleware, in the terminal under the AllPay project
php artisan make:middleware TestLog
註冊middleware
In
/app/Http/Kernel.php
,register the middleware we just madeprotected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\TestLog::class,
];
Create Log
In the TestLog file that we just made, and added:
$response = $next($request);
log::info([
$request->header(),
$request->getMethod(),
$request->getRequestUri(),
$request->all(),
$response->getStatusCode(),
$response->getContent()
]);
return $response;Actually, what you might log varies upon different cases
Create a public url
We need to get the response from third party, so we need a public url to get it
We could use ngrok to create the public url
Install ngrok, please refer to its official website
Change ngrok to be globally executable
- mv ngrok /usr/local/bin
Get public url
ngrok http 8000
In terminal under AllPay project
php artisan serve 8000
Copy the public url produced by ngrok
Create receive function
We are going to catch the response from AllPay, firstly we need to create a function, and after catching it, we could do things there.
- In PaymentsController, we create a function
receive
as follows:public function receive()
{
}
- In PaymentsController, we create a function
Make an API for it
Route::post('pay', 'PaymentsController@pay');
Route::post('receive', 'PaymentsController@receive');
Set up client return url
In .env, if you’ve been following what I taught, there should be this parameter, just simply paste the public url there
ALLPAYRETURNURL=https://163be100.ngrok.io/api/recevie
- Your link will be different from mine, don’t paste mine.
Test payment
Let’s connect AllPay payment page again
Login the testing buyer account provided by AllPay
- Account:stageuser001
- Password:test1234
Use the testing credit card provided by AllPay
- Number:4311-9522-2222-2222
- Expiry Date:12 / 20 or later than current date
- Security number:222
After payment, let’s check log to see if there is a response from AllPay, in terminal under AllPay project
cat storage/logs/laravel-2019-02-08.log
On, we’ve got the response.
'MerchantID' => '2000132',
'MerchantTradeNo' => 'Test1549597724',
'PayAmt' => '2000',
'PaymentDate' => '2019/02/08 11:49:03',
'PaymentType' => 'Credit_CreditCard',
'PaymentTypeChargeFee' => '20',
'RedeemAmt' => '0',
'RtnCode' => '1',
'RtnMsg' => '交易成功',
'SimulatePaid' => '0',
'TradeAmt' => '2000',
'TradeDate' => '2019/02/08 11:48:44',
'TradeNo' => '1902081148440800',
'CheckMacValue' => '5B1EE24B0E9D600C65578DD82D3168E2ED56799453577E17E1EBEFC536BD7EAF',
Validation
Think about it, if your API was accidentally leaked, and some developer knew it. He bought some items from your service, and called your API, how could you tell?
So, there is a specific mechanism that only applicable between you and third party payment service
The validation mechanism is like a formula every column goes in and out will be calculated with which is only applicable between you and third party, if you’ve been paying attention, you should notice that in the last column of the response we’ve received from AllPay.
If you are interested in the formula in detail, you could check it on AllPay’s website.
Since in this article, we use AllPay SDK, so we are going to share how to use official SDK to validate the information.
Firstly, let’s check a class called
CheckMacValue
inapp/AllPay.Payment.Integration.php
Secondly, you could find a function called
generate
, and you could check it, which it the formula of the CheckMacValueSo, we could calculate the received information with this formula, and it should exactly the same as what we’ve got from third party
Let’s get all those information except for
CheckMacValue
from AllPay, we could use the following code.$parameters = $paymentResponse->except('CheckMacValue');
And then, we assign a variable with the
CheckMacValue
we’ve got from AllPay$receivedCheckMacValue = $paymentResponse->CheckMacValue;
Then, we calculate the $parameters with function generate to produce correct
CheckMacValue
$calculatedCheckMacValue = CheckMacValue::generate($parameters, env('HASHKEY'), env('HASHIV'), EncryptType::ENC_SHA256);
Finally, we compare if two values are identical, if so, it proves the validity of its source. If not, we shouldn’t give any credibility to this information
if($receivedCheckMacValue == $calculatedCheckMacValue)
return true;
return false;
What’s next?
After validating the source, we could do things accordingly
For example, if payment is successfully made, what we are going to do, and if not, what then?
In this article, we mark the orders as paid and notify the buyers via email after the payment is made.
if (PaymentServiceOrders::checkIfCheckMacValueCorrect($request) && PaymentServiceOrders::checkIfPaymentPaid($request->RtnCode))
{
$paymentServiceOrder = (new PaymentServiceOrders)->where('MerchantTradeNo', $request->MerchantTradeNo)->first();
$paymentServiceOrder->update(['status' => 1, 'expiry_time' => null]);
$orderRelations = $paymentServiceOrder->where('MerchantTradeNo', $request->MerchantTradeNo)->first()->orderRelations;
Order::updateStatus($orderRelations);
$payerEmail = $paymentServiceOrder->user->email;
if ($payerEmail !== null)
Mail::to($payerEmail)->send(new PaymentReceived($paymentServiceOrder, $orderRelations));
return '1|OK';
}At the end, don’t forget to return ‘1|OK’ to let AllPay knows that we’ve received the message.
Those I didn’t mentioned
With the testing buyer account provided by AllPay, except for credit card, is also supports multiple payment method
With convenient store pay or bank transferring method, you could login with a testing backed account provided by AllPay, which could simulate making the payment.
- Account:StageTest
- Password:test1234
Refund
Refund example as follows:
public static function refund($order, $paymentServiceInstance, $orderRelation)
{
try
{
$obj = new AllInOne();
$obj->ServiceURL = "https://payment-stage.opay.tw/Cashier/AioChargeback";
// endpoint
$obj->HashKey = env('HASHKEY');
// Hash key provided by AllPay
$obj->HashIV = env('HASHIV');
// Hash IV provided by AllPay
$obj->MerchantID = env('MERCHANTID');
// Merchant ID provided by AllPay
$obj->EncryptType = EncryptType::ENC_SHA256;
// CheckMacValue type. Stay 1 as SHA256
$obj->ChargeBack['MerchantTradeNo'] = $paymentServiceInstance->MerchantTradeNo;
// The trade number you provided to AllPay
$obj->ChargeBack['TradeNo'] = $paymentServiceInstance->TradeNo;
// The trade no provided by AllPay
$obj->ChargeBack['ChargeBackTotalAmount'] = $order->total_amount;
// Refunded amount
$obj->AioChargeback();
} catch (Exception $e)
{
// If something wrong, return.
return Helpers::result(true, 'Something wrong happened', 200);
// Debug mode, print out the error
echo $e->getMessage();
}
}You could refer to official document for required parameters
Comments