Tích hợp cổng thanh toán Paypal vào ứng dụng Android sử dụng PHP, MySQL (Phần 1)

Khi bạn đang xây dựng một ứng dụng thương mại điện tử, tích hợp cổng thanh toán là một trong những phần quan trọng nhất của ứng dụng. Hôm nay chúng ta sẽ tìm hiểu làm thế nào để tích hợp cổng thanh toán PayPal (là cổng thanh toán trực tuyến phổ biến nhất) vào ứng dụng Android của chúng ta.

Đối với một ứng dụng về thương mại điện tự, chỉ ứng dụng Android là không đủ. Chúng ta cần phải có một server side để duy quản lý hàng trong kho, khách hàng, giao dịch và nhiều thứ quan trọng khác nữa. Vì vậy bài hướng dẫn này sẽ được phân chia thành hai phần. Trong phần 1 này, chúng ta sẽ tìm hiểu làm thế nào để xây dựng server với PHP, MySQL và tích hợp PayPal REST API. Trong phần tiếp theo chúng ta sẽ học làm thế nào để xây dựng các ứng dụng Android và tích hợp Paypal.

Dưới đây là hình ảnh chụp màn hình của ứng dụng sau khi chúng ta hoàn thành bài 2 bài hướng dẫn

shoping_cart

payment_gateway

1. Tổng quan

PayPal cung cấp nhiều tùy chọn thanh toán như Single Payment (Nhận thanh toán ngay lập tức), Future Payments (có thể được sử dụng để thanh toán định kỳ) và Profile Sharing (nhận thông tin về khách hàng). Trong trường hợp của chúng ta đang xây dự thì việc tích hợp Single Payment là phù hợp nhất, điều này có nghĩa là chúng ta sẽ nhận được tiền thanh toán ngay khi khách hàng trả phí để mua sản phẩm

Sơ đồ dưới đây giải thích về cách thức mà ứng dụng sẽ hoạt động để có thể toàn tất một thanh toán.

overview

  1. Đầu tiên các ứng dụng Android kết nối với máy chủ và lấy về danh sách sản phẩm (định dạng dữ liệu json). Dữ liệu sẽ được phân tích sau đó hiển thị lên màn hình ứng dụng Android.
  2. Người mua hàng sẽ lựa chọn các sản phẩm và thực hiện thanh toán bằng cách sử dụng các tùy chọn với việc thanh toán thông qua PayPal.
  3. Sau khi thanh toán thành công, PayPal sẽ gửi lại dữ liệu dạng JSON chứa id thanh toán.
  4. Ứng dụng Android sẽ gửi id thanh toán cho server để xác minh.
  5. Server sẽ sử dụng PayPal REST API để gửi đi id thanh toán để xác minh việc thanh toán.
  6. PayPal sẽ trả lại một dự liệu dạng json, và chúng ta có thể kiểm tra “state”: “approved” (và một số )

2. Tạo PayPal App (Client ID & Secret)

Để thực hiện các cuộc gọi đến PayPal API chúng ta cần phải tạo ra một ứng dụng trong developer.paypal.com và có được khách hàng ID & Secret.

  1. Đăng nhập bằng tài khoản nhà phát triển vào trang PayPal’s Developer. Nếu bạn đang truy cập lần đầu tiên, hãy đăng ký và tạo một tài khoản mới.
  2. Sau khi đăng nhập, bạn sẽ được chuyển hướng đến trang các ứng dụng của tôi, ở đó bạn có thể tạo một ứng dụng mới .
  3. Điền tên ứng dụng, chọn tài khoản nhà phát triển và bấm vào tạo ứng dụng. Một khi các ứng dụng được tạo ra, bạn có thể nhận thấy Client id & Secret. Chúng ta sẽ cần các thành phần này cho đồng thời server và ứng dụng client.

create_new_app

client_id_secret

3. Tài khoản test Paypal Sandbox

Paypal cung cấp một môi trường thử nghiệm được gọi là sandbox để kiểm tra các ứng dụng trước khi đưa ra ngoài thực tế. Những tài khoản thử nghiệm dùng để kiếm tra việc thanh toán. Để có được thông tin tài khoản sandbox của bạn, hãy làm theo các bước dưới đây.

  1. Vào trang PayPal’s Developer rồi chọn Accounts bên dưới mục Sandbox tại menu bên trái.
  2. Ở bên phải bạn có thể thấy các tài khoản thử nghiệm sandbox. Chọn email người mua và click vào Profile.
  3. Tại cửa sổ popup, nhấp vào Change Password để thay đổi mật khẩu trong trường hợp nếu bạn không chắc chắn về mật khẩu bạn có.
  4. Trong cửa sổ popup, tab Funding kiểm tra số dư tài khoản.

Bạn phải sử dụng những ủy quyền khác quan trọng để kiểm thử một ứng dụng trong môi trường sandbox.

dev_account

change_password

account_money

4. Tải PayPal PHP Rest API SDK

Sẽ luôn là tốt khi sử dụng những SDK được cung cấp với những nhà cung cấp dịch vụ, việc sử dụng này sẽ tốt hơn là chúng ta tự xây dựng. Paypal cung cấp REST API SDK cho nhiều nền tảng. Và như chúng ta đã chọn thì chúng ta sẽ sử dụng PHP để code cho server side nên các bạn có thể tải gói tại đây PayPal-PHP-SDK.

Đây là liên kết trực tiếp cho PayPal-PHP-SDK-1.2.0.zip

5. Tải Slim Framework

Thư viện PHP Slim cho phép bạn phát triển REST API đơn giản và hiệu quả. Khuôn khổ này chúng tôi sử dụng ở đây để tạo responses data dạng json.

Tải về phiên bản mới nhất của Slim framework.

6. Tải & Cài đặt WAMP

Tải về và cài đặt WAMP từ http://www.wampserver.com/en/ và bắt đầu chương trình từ Start => All Programs. Khi bắt đầu, bạn sẽ có thể truy cập thông qua http://localhost/ bằng trình duyệt. Xem video dưới đây để biết làm thế nào để tải về và cài đặt WAMP.

7. Tạo cơ sở dữ liệu MySQL

Nhìn chung, chúng ta sẽ tạo ra bốn bảng. user(để lưu trữ các thông tin khách hàng), products (để lưu trữ các thông tin sản phẩm như tên, giá, mô tả), payments (để lưu trữ các giao dịch paypal) và sales (để giữ các sản phẩm đã được khách hàng thanh toán). Đây là thiết kế cơ sở dữ liệu rất đơn giản. Trong triển khai thực tế cơ sở dữ liệu sẽ phức tạp hơn nhiều so với cơ sở dữ liệu này.

database

Truy cập phpmyadmin bằng cách vào địa chỉ http://localhost/phpmyadmin và chạy đoạn lệnh SQL dưới đây để tạo cơ sở dữ liệu bà các bảng. Đồng tời tôi đã chèn thêm một người dùng và một vài sản phẩm để chạy thử nghiệm.

CREATE DATABASE IF NOT EXISTS `paypal` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `paypal`;

CREATE TABLE IF NOT EXISTS `payments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `paypalPaymentId` text NOT NULL,
  `create_time` text NOT NULL,
  `update_time` text NOT NULL,
  `state` varchar(15) NOT NULL,
  `amount` decimal(6,2) NOT NULL,
  `currency` varchar(3) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` text NOT NULL,
  `price` decimal(6,2) NOT NULL,
  `description` text NOT NULL,
  `image` text NOT NULL,
  `sku` text NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;

INSERT INTO `products` (`id`, `name`, `price`, `description`, `image`, `sku`, `created_at`) VALUES
(1, 'Google Nexus 6', '690.50', 'Midnight Blue, with 32 GB', 'http://api.androidhive.info/images/nexus5.jpeg', 'sku-2123wers100', '2015-02-04 23:19:42'),
(2, 'Sandisk Cruzer Blade 16 GB Flash Pendrive', '4.50', 'USB 2.0, 16 GB, Black & Red, Read 17.62 MB/sec, Write 4.42 MB/sec', 'http://api.androidhive.info/images/sandisk.jpeg', 'sku-78955545w', '2015-02-10 22:54:28'),
(3, 'Kanvas Katha Backpack', '11.25', '1 Zippered Pocket Outside at Front, Loop Handle, Dual Padded Straps at the Back, 1 Compartment', 'http://api.androidhive.info/images/backpack.jpeg', 'sku-8493948kk4', '2015-02-10 22:55:34'),
(4, 'Prestige Pressure Cooker', '30.00', 'Prestige Induction Starter Pack Deluxe Plus Pressure Cooker 5 L', 'http://api.androidhive.info/images/prestige.jpeg', 'sku-90903034ll', '2015-02-10 22:59:25');

CREATE TABLE IF NOT EXISTS `sales` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `paymentId` int(11) NOT NULL,
  `productId` int(11) NOT NULL,
  `state` varchar(15) NOT NULL,
  `salePrice` decimal(6,2) NOT NULL,
  `quantity` int(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `paymentId` (`paymentId`),
  KEY `productId` (`productId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Android Hive', '[email protected]');

ALTER TABLE `payments`
  ADD CONSTRAINT `payments_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

ALTER TABLE `sales`
  ADD CONSTRAINT `sales_ibfk_2` FOREIGN KEY (`productId`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `sales_ibfk_1` FOREIGN KEY (`paymentId`) REFERENCES `payments` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

8. Tạo PHP Project

Sau khi đã thực hiện xong việc tạo ra các cơ sở dữ liệu, bây giờ chúng ta sẽ tạo một PHP Project. Dưới đây là cấu trúc dự án mà chúng ta sẽ tạo ra bây giờ. Cẩn thận đặt các folders/files ở vị trí tương tự như sơ đồ dưới đây. Tôi đang sử dụng Netbeans IDE để phát triển các dự án PHP.

php_project

Trong dự án này, mục tiêu thao tác sẽ là trên các thư mục và file như sau:

include - Tất cả các lớp cấu hình & helper sẽ được đặt vào thư mục này libs - Tất cả các thư viện của bên thứ ba (Slim & PayPal) sẽ được đặt ở đây v1 - Nó là thư mục phiên bản của API của chúng ta

index.php - Tất cả các cài đặt gọi API sẽ được điều khiển trong file này .htaccess - File cấu hình Apache web server

  1. Di chuyển đến thư mục cài đặt wamp (thường WAMP sẽ được cài đặt tại C:\wamp). Và mở thư mục www. Đây là nơi mà tất cả các dự án php sẽ được đặt.
  2. Bên trong thư mục www, tạo một thư mục có tên PayPalServer. Đây là thư mục gốc cho các dự án của chúng tôi.
  3. Bây giờ, bên PayPalServer, tạo ba thư mục có tên bao gồm, libs và v1.
  4. Paste Slim Framework và PayPal SDK trong thư mục libs.
  5. Tạo một file tên Config.php trong thư mục include và thêm đoạn code dưới đây. Tập tin này có chứa các giá trị cấu hình như các thông tin cơ sở dữ liệu, paypal client id & secret và loại tiền tệ mặc định. Thay đổi tên username và password với các thông tin mysql của bạn.
<?php

/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'paypal');

define('DEFAULT_CURRENCY', 'USD');
define('PAYPAL_CLIENT_ID', 'AdOTNRDUqb6jBLfB1IaVrNHFqLKhWROWCNZiuGrPQBqI0h_Hbf6teycjptu0'); // Paypal client id
define('PAYPAL_SECRET', 'EP5sARCiqusS6XGQG3Y-DpZ5KRL9lagYy8Wg0cvMrnznTUGsen3HMzHqdkXZ'); // Paypal secret

?>

  1. Tạo một file khác có tên DBConnect.php trong thư mục include và paste đoạn code dưới đây. Lớp này đảm nhiệm việc mở kết nối đến cơ sở dữ liệu.
<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 */
class DbConnect {

    private $conn;

    function __construct() {

    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . '/Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
        }

        // returing connection resource
        return $this->conn;
    }

}

?>

  1. Tạo một lớp có tên DBHandler.php trong thư mục include. Lớp này chứa các thức để thực hiện các thao tác CRUD trên cơ sở dữ liệu.

getAllProducts() - Lấy tất cả các sản phẩm từ bảng sản phẩm

getProductBySku() - lấy một sản phẩm theo thông qua mã sku

storePayment() - Lưu trữ giao dịch thanh toán qua paypal.

storeSale() - Lưu trữ mua bán của một sản phẩm cụ thể.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 */
class DbHandler {

    private $conn;

    function __construct() {

        require_once dirname(__FILE__) . '/DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

    /**
     * Listing products
     *
     * */
    public function getAllProducts() {
        $stmt = $this->conn->prepare("SELECT * FROM products");
        $stmt->execute();
        $products = $stmt->get_result();
        $stmt->close();
        return $products;
    }

    /*
     * Fetches a product by its sku
     */
    public function getProductBySku($sku) {
        $stmt = $this->conn->prepare("SELECT * FROM products where sku = ?");
        $stmt->bind_param("s", $sku);

        if ($stmt->execute()) {
            $product = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $product;
        } else {
            $stmt->close();
            return NULL;
        }
    }

    /**
     * Stores paypal payment in payments table
     */
    public function storePayment($paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency) {
        $stmt = $this->conn->prepare("INSERT INTO payments(paypalPaymentId, userId, create_time, update_time, state, amount, currency) VALUES(?,?,?,?,?,?,?)") or die(mysql_error());
        $stmt->bind_param("sisssds", $paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            // task row created
            // now assign the task to user
            $payment_id = $this->conn->insert_id;
            return $payment_id;
        } else {
            // task failed to create
            return NULL;
        }
    }

    /**
     * Stores item sale in sales table
     */
    public function storeSale($payment_id, $product_id, $state, $salePrice, $quantity) {
        $stmt = $this->conn->prepare("INSERT INTO sales(paymentId, productId, state, salePrice, quantity) VALUES(?,?,?,?,?)");
        $stmt->bind_param("iisdi", $payment_id, $product_id, $state, $salePrice, $quantity);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            $sale_id = $this->conn->insert_id;
            return $sale_id;
        } else {
            // task failed to create
            return NULL;
        }
    }

}

?>

  1. Bây giờ, tạo một file có tên .htaccess trong thư mục v1 và thêm vào dưới đây quy tắc.

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]

  1. Cuối cùng tạo ra file index.php bên v1 và paste đoạn dưới đây. Đây là tập tin quan trọng nhất xử lý tất cả các yêu cầu sử dụng REST của Slim Framework.

Trong đoạn mã dưới đây

echoResponse() - In response data json khi gọi thông qua API

$app>get('/products'..) - lấy về tất cả các sản phẩm từ bảng sản phẩm và in ở định dạng JSON

$app>post('/verifyPayment'..) - Xác nhận việc thanh toán qua Paypal trên server là đã được hoàn thành trên ứng dụng di đông, Việc xác nhận của server này là rất quan trọng để tránh những giao dịch gian lận.

<?php

ini_set('display_errors', 1);
require_once '../include/DBHandler.php';
require '../libs/Slim/Slim.php';

require __DIR__ . '/../libs/PayPal/autoload.php';

use PayPal\Api\Payment;

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

$userId = 1;

/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoResponse($status_code, $response) {
    $app = \Slim\Slim::getInstance();
    // Http response code
    $app->status($status_code);

    // setting response content type to json
    $app->contentType('application/json');

    echo json_encode($response);
}

function authenticate(\Slim\Route $route) {
    // Implement your user authentication here
    // Check http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/
}

/**
 * Lists all products
 * method - GET
 */
$app->get('/products', 'authenticate', function() {
            $response = array();
            $db = new DbHandler();

            // fetching all products
            $result = $db->getAllProducts();

            $response["error"] = false;
            $response["products"] = array();

            // looping through result and preparing products array
            while ($task = $result->fetch_assoc()) {
                $tmp = array();
                $tmp["id"] = $task["id"];
                $tmp["name"] = $task["name"];
                $tmp["price"] = $task["price"];
                $tmp["description"] = $task["description"];
                $tmp["image"] = $task["image"];
                $tmp["sku"] = $task["sku"];
                $tmp["created_at"] = $task["created_at"];
                array_push($response["products"], $tmp);
            }

            echoResponse(200, $response);
        });

/**
 * verifying the mobile payment on the server side
 * method - POST
 * @param paymentId paypal payment id
 * @param paymentClientJson paypal json after the payment
 */
$app->post('/verifyPayment', 'authenticate', function() use ($app) {

            $response["error"] = false;
            $response["message"] = "Payment verified successfully";
            global $userId;

            require_once '../include/Config.php';

            try {
                $paymentId = $app->request()->post('paymentId');
                $payment_client = json_decode($app->request()->post('paymentClientJson'), true);

                $apiContext = new \PayPal\Rest\ApiContext(
                        new \PayPal\Auth\OAuthTokenCredential(
                        PAYPAL_CLIENT_ID, // ClientID
                        PAYPAL_SECRET      // ClientSecret
                        )
                );

                // Gettin payment details by making call to paypal rest api
                $payment = Payment::get($paymentId, $apiContext);

                // Verifying the state approved
                if ($payment->getState() != 'approved') {
                    $response["error"] = true;
                    $response["message"] = "Payment has not been verified. Status is " . $payment->getState();
                    echoResponse(200, $response);
                    return;
                }

                // Amount on client side
                $amount_client = $payment_client["amount"];

                // Currency on client side
                $currency_client = $payment_client["currency_code"];

                // Paypal transactions
                $transaction = $payment->getTransactions()[0];
                // Amount on server side
                $amount_server = $transaction->getAmount()->getTotal();
                // Currency on server side
                $currency_server = $transaction->getAmount()->getCurrency();
                $sale_state = $transaction->getRelatedResources()[0]->getSale()->getState();

                // Storing the payment in payments table
                $db = new DbHandler();
                $payment_id_in_db = $db->storePayment($payment->getId(), $userId, $payment->getCreateTime(), $payment->getUpdateTime(), $payment->getState(), $amount_server, $amount_server);

                // Verifying the amount
                if ($amount_server != $amount_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment amount doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the currency
                if ($currency_server != $currency_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment currency doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the sale state
                if ($sale_state != 'completed') {
                    $response["error"] = true;
                    $response["message"] = "Sale not completed";
                    echoResponse(200, $response);
                    return;
                }

                // storing the saled items
                insertItemSales($payment_id_in_db, $transaction, $sale_state);

                echoResponse(200, $response);
            } catch (\PayPal\Exception\PayPalConnectionException $exc) {
                if ($exc->getCode() == 404) {
                    $response["error"] = true;
                    $response["message"] = "Payment not found!";
                    echoResponse(404, $response);
                } else {
                    $response["error"] = true;
                    $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                    echoResponse(500, $response);
                }
            } catch (Exception $exc) {
                $response["error"] = true;
                $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                echoResponse(500, $response);
            }
        });

/**
 * method to store the saled items in sales table
 */
function insertItemSales($paymentId, $transaction, $state) {

    $item_list = $transaction->getItemList();

    $db = new DbHandler();

    foreach ($item_list->items as $item) {
        $sku = $item->sku;
        $price = $item->price;
        $quanity = $item->quantity;

        $product = $db->getProductBySku($sku);

        // inserting row into sales table
        $db->storeSale($paymentId, $product["id"], $state, $price, $quanity);
    }
}

$app->run();
?>

Vậy là chúng ta đã hoàn thành phần phía server. Dưới đây là các API mà ứng dụng Android của chúng ta cần sử dụng.

URL endpoints

|_.URL |_.Method |_.Parameters |_.Description|
| http://localhost/PayPalServer/v1/products | GET | Fetches all the products |
| http://localhost/PayPalServer/v1/verifyPaymen | POST | paymentId, paymentClientJson | Verifies paypal payment |

Nguồn tham khảo Android Integrating PayPal using PHP, MySQL – Part 1


All Rights Reserved