Tìm hiểu cổng thanh toán điện từ PAY.JP (Phần I)

Giới thiệu chung

PAY.JP là một cổng thanh toán điện tử sử dụng API được xây dựng trên cơ sở RESTFULL, PAY.JP cung cấp rất nhiều phương thức thanh toán hữu ích và mềm dẻo trong hoạt động kinh doanh như:

  • Có thể thanh toán mọi lúc mọi nơi
  • Hỗ trợ rất nhiều ngôn ngữ lập trình.
  • Có thể tạo plan dùng để thanh toán lặp lại nhiều lần
  • Quản lý thông tin khách hàng

Xác thực

Để sử dụng API của PAY.JP bước đầu tiên bạn cần phải đăng ký một tài khoản developer trên trang chủ PAY.JP và lấy khóa API gồm:

  • Public key: Khóa công khai tạo ra các mã thông báo được nhúng trong HTML
  • Private key: Khóa bí mật có tác dụng như là xác thực người dùng từ các reuqest tới từ phía server.

Việc xác thực sẽ được thực hiện liên tục thông qua các xác thực cơ bản, lưu ý rằng Khóa bí mật là một chìa khóa quan trọng và cần được bảo mật tuyệt đối. Một sameple của private key sau khi đăng ký tài khoản

require "payjp"
Payjp.api_key = "sk_test_c62fade9d045b54cd76d7036"

Giao thức

Vì lý do bảo mật tất cả API đến từ PAY.JP đều được thực hiển bới giao thức HTTPS

Method

Khi thực hiện thanh tóan sử dụng API của PAY.JP bạn có thể sử dụng những method HTTP: GET, POST, DELETE

Response Format

Tất cả dữ liệu trả về của API đều sử dụng kiểu dữ liệu JSON

Thời gian

Thời gian được sử dụng trên PAY.JP là giờ UTC, đơn vị nhỏ nhất là giây.

Errors

Tất cả xử lý nếu gây lỗi đều sẽ được bắt, errors sẽ được trả về gôm mã lỗi dạng 4xx cho tới 5xx cùng với lý do gây lôi. Thông thường mã lỗi này chỉ phục vụ doanh nghiệp phát triển ứng dụng không nên show ra cho người dùng cuối. Danh sách mã lỗi đầy đủ bạn có thể tham khảo tại đây

Dưới đây là một ví dụ để bắt mã lỗi trong ruby

begin
  # Use Payjp's library to make requests...
rescue Payjp::CardError => e
  # Since it's a decline, Payjp::CardError will be caught
  body = e.json_body
  err  = body[:error]

  puts "Status is: #{e.http_status}"
  puts "Type is: #{err[:type]}"
  puts "Code is: #{err[:code]}"
  # param is '' in this case
  puts "Param is: #{err[:param]}"
  puts "Message is: #{err[:message]}"
rescue Payjp::InvalidRequestError => e
  # Invalid parameters were supplied to Payjp's API
rescue Payjp::AuthenticationError => e
  # Authentication with Payjp's API failed
  # (maybe you changed API keys recently)
rescue Payjp::APIConnectionError => e
  # Network communication with Payjp failed
rescue Payjp::PayjpError => e
  # Display a very generic error to the user, and maybe send
  # yourself an email
rescue => e
  # Something else happened, completely unrelated to Payjp
end

Phân trang

Trong dữ liệu trả về của PAY.JP cũng hỗ trợ việc phân trang nếu lượng dữ liệu quá lớn.

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd76d7036'
customer = Payjp::Customer.retrieve('cus_4df4b5ed720933f4fb9e2857517')
customer.subscriptions.all(limit: 3, offset: 10)

Trong đó:

  • Limit: Số lượng item lấy ra trên 1 trang, thường có giá trị từ 1 tới 100
  • Offset: Vị trí bắt đầu lấy dữ liệu.

Charge (payment)

Create payment

Mỗi một giao dịch thanh toán được gắn với Token ID được sinh ra từ thẻ của khách hàng. Bạn cũng có thể sử dụng đối tượng Customer để thực hiện thanh toán, trong trường hợp đó thẻ được gán là mặc định của Customer sẽ được sử dụng. Một lưu ý nhỏ là số tiền thanh toán bạn có thể giữ nó trong thẻ của khách hàng trong một khoảng thời gian nhất định bằng thuộc tính capture, thời gian giữ tiền có thể kéo dài từ 1 tới 60 ngày, sau khi thời gian này kết thúc mà không được thanh toán thì số tiền trên sẽ được giải phóng cho chủ tài khoản. Ví dụ:

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd76d7036'
charge = Payjp::Charge.create(
  :amount => 3500,
  :card => 'tok_76e202b409f3da51a0706605ac81',
  :currency => 'jpy',
)

Dữ liệu trả về tương ứng.

{
  "amount": 3500,
  "amount_refunded": 0,
  "captured": true,
  "captured_at": 1433127983,
  "card": {
    "address_city": null,
    "address_line1": null,
    "address_line2": null,
    "address_state": null,
    "address_zip": null,
    "address_zip_check": "unchecked",
    "brand": "Visa",
    "country": null,
    "created": 1433127983,
    "customer": null,
    "cvc_check": "unchecked",
    "exp_month": 2,
    "exp_year": 2020,
    "fingerprint": "e1d8225886e3a7211127df751c86787f",
    "id": "car_d0e44730f83b0a19ba6caee04160",
    "last4": "4242",
    "name": null,
    "object": "card"
  },
  "created": 1433127983,
  "currency": "jpy",
  "customer": null,
  "description": null,
  "expired_at": null,
  "failure_code": null,
  "failure_message": null,
  "id": "ch_fa990a4c10672a93053a774730b0a",
  "livemode": false,
  "metadata": null,
  "object": "charge",
  "paid": true,
  "refund_reason": null,
  "refunded": false,
  "subscription": null
}

Trong trường hợp thanh toán xảy ra lỗi sẽ trả về errors theo format sau:

{
  "error": {
    "code": "invalid_number",
    "message": "Your card number is invalid.",
    "param": "card[number]",
    "status": 400,
    "type": "card_error"
  }
}

Các thuộc tính trả về cụ thể sẽ được giải thích tại đây

Payment information

Để xem thông tin payments bạn chỉ cần sử dụng method retrieve với id của payment

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd76d7036'
Payjp::Charge.retrieve('ch_fa990a4c10672a93053a774730b0a')

Tương ứng kết quả trả về:

{
  "amount": 3500,
  "amount_refunded": 0,
  "captured": true,
  "captured_at": 1433127983,
  "card": {
    "address_city": null,
    "address_line1": null,
    "address_line2": null,
    "address_state": null,
    "address_zip": null,
    "address_zip_check": "unchecked",
    "brand": "Visa",
    "country": null,
    "created": 1433127983,
    "customer": null,
    "cvc_check": "unchecked",
    "exp_month": 2,
    "exp_year": 2020,
    "fingerprint": "e1d8225886e3a7211127df751c86787f",
    "id": "car_d0e44730f83b0a19ba6caee04160",
    "last4": "4242",
    "name": null,
    "object": "card"
  },
  "created": 1433127983,
  "currency": "jpy",
  "customer": null,
  "description": null,
  "expired_at": null,
  "failure_code": null,
  "failure_message": null,
  "id": "ch_fa990a4c10672a93053a774730b0a",
  "livemode": false,
  "metadata": null,
  "object": "charge",
  "paid": true,
  "refund_reason": null,
  "refunded": false,
  "subscription": null
}

Nếu có errors trong quá trình lấy thông tin errors trả về dưới dạng sau:

{
  "error": {
    "message": "There is no charge with ID: dummy",
    "param": "id",
    "status": 404,
    "type": "client_error"
  }
}

Update payments infomation

Bạn cũng có thể update một số thông tin của payments bằng cách sau:

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd7d7036'
charge = Payjp::Charge.retrieve('ch_fa990a4c10672a93053a774730b0a')
charge.description='Updated'
charge.save

Dữ liệu trả về và errors khi có cũng tương tự như trên.

Refund

PAY.JP cũng cung cấp một phương thức khá hữu ích là trả lại số tiền đã thanh toán trước đó cho chủ tài khoản nếu có yêu cầu, số tiền trả lại sẽ là số tiền được khai báo khi thanh toán.

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd76d7036'
charge = Payjp::Charge.retrieve('ch_fa990a4c10672a93053a774730b0a')
charge.refund

Dữ liệu trả về dạng.

{
  "amount": 3500,
  "amount_refunded": 3500,
  "captured": true,
  "captured_at": 1433127983,
  "card": {
    "address_city": null,
    "address_line1": null,
    "address_line2": null,
    "address_state": null,
    "address_zip": null,
    "address_zip_check": "unchecked",
    "brand": "Visa",
    "country": null,
    "created": 1433127983,
    "customer": null,
    "cvc_check": "unchecked",
    "exp_month": 2,
    "exp_year": 2020,
    "fingerprint": "e1d8225886e3a7211127df751c86787f",
    "id": "car_d0e44730f83b0a19ba6caee04160",
    "last4": "4242",
    "name": null,
    "object": "card"
  },
  "created": 1433127983,
  "currency": "jpy",
  "customer": null,
  "description": "Updated",
  "expired_at": null,
  "failure_code": null,
  "failure_message": null,
  "id": "ch_fa990a4c10672a93053a774730b0a",
  "livemode": false,
  "metadata": null,
  "object": "charge",
  "paid": true,
  "refund_reason": null,
  "refunded": true,
  "subscription": null
}

Payments list

Bạn cũng có thể lấy ra thông tin của thất cả các yêu cầu thanh toán đang có trong hệ thống bằng cách:

require 'payjp'
Payjp.api_key = 'sk_test_c62fade9d045b54cd76d7036'
Payjp::Charge.all(limit: 3, offset: 10)

Dữ liệu trả về dạng.

{
  "count": 3,
  "data": [
    {
      "amount": 1000,
      "amount_refunded": 0,
      "captured": true,
      "captured_at": 1432965397,
      "card": {
        "address_city": "\u8d64\u5742",
        "address_line1": "7-4",
        "address_line2": "203",
        "address_state": "\u6e2f\u533a",
        "address_zip": "1070050",
        "address_zip_check": "passed",
        "brand": "Visa",
        "country": "JP",
        "created": 1432965397,
        "cvc_check": "passed",
        "customer": null,
        "exp_month": 12,
        "exp_year": 2016,
        "fingerprint": "e1d8225886e3a7211127df751c86787f",
        "id": "car_7a79b41fed704317ec0deb4ebf93",
        "last4": "4242",
        "name": "Test Hodler",
        "object": "card"
      },
      "created": 1432965397,
      "currency": "jpy",
      "customer": "cus_67fab69c14d8888bba941ae2009b",
      "description": "test charge",
      "expired_at": null,
      "failure_code": null,
      "failure_message": null,
      "id": "ch_6421ddf0e12a5e5641d7426f2a2c9",
      "livemode": false,
      "metadata": null,
      "object": "charge",
      "paid": true,
      "refund_reason": null,
      "refunded": false,
      "subscription": null
    },
    {...},
    {...}
  ],
  "has_more": true,
  "object": "list",
  "url": "/v1/charges"
}

Định dạng errors trả về nếu có cũng tương tự như trên.

Kết luận

Thật đơn giản phải không? Trong phần này mình chỉ đề cập một số phương thức cơ bản của PAY.JP các phần còn lại như Customer, Plan, Token, Events .... mình xin viết tiếp ở phần sau. Thanks for reading !!!