Tích hợp thanh toán online vào ứng dụng Rails(P2)
Bài đăng này đã không được cập nhật trong 4 năm
Ở phần trước mình đã chia sẻ với mọi người về vấn đề làm thế nào tích hợp payment vào trong rails
và chỉ dừng lại ở việc tích hợp Stripe
và thanh toán bằng hình thức thanh toán một lần Stripe charge
. Vậy nên hôm nay mình tiếp tục chia sẻ thêm một số hình thức thanh toán khác như thanh toán dài hạn(subscription) theo tuần, theo tháng,... Ngoài Stripe
thì mình sẽ viết thêm về Paypal
. Mọi người chưa đọc phần trước thì nên đọc nó ở đây trước khi vào phần 2 nhé.
Let's go ✊✊✊
Stripe
Stripe Subscriptions
Thực tế thì ngày nay việc mua bán trả góp hay trả nhiều lần được áp dụng rộng rải giúp đem lại lợi nhuận cho doanh nghiệp và mang lại sự thuận tiện cho người dùng. Vậy nên việc tích hợp thanh toán dài hạn vào các trang thương mại điện tử là điều cần thiết và Stripe Subscriptions
giúp ta làm việc đó.
Dưới đây là cơ chế hoạt động của stripe subscription
Mình giải thích sơ qua hình bên trên: Mỗi sản phẩm product
sẽ có các mức giá price
phải trả định kì theo tháng, năm,... Khi người dùng đăng kí thì sẽ tạo ra một Customer
đại diện cho chính người dùng đó, đồng thời cũng đăng kí một Subscription
như một chìa khoá cho phép người dùng sử dụng sản phẩm, mục đích là để khi người dùng không thanh toán đúng hạn hoặc có vấn đề liên quan thì người dùng không thể tiếp tục sử dụng sản phẩm. Tiếp đến là theo định kì thanh toán mà người dùng chọn khi mua sản phẩm thì sẽ có một Invoice
giống như lời nhắc nhở Customer
đến hạn thanh toán và PaymentIntent
là nơi lưu lại lịch sử bạn thanh toán của từng.
Để có thể hiểu rõ hơn thì mọi người có thể tìm hiểu thêm ở subscription document.
Giờ là đến lúc thực hành thôi ✊✊
Step1: Những thứ cần chuẩn bị
-
Chuẩn bị
Plan
:Plan
ở đây là kế hoạch thanh toán định kì của mỗi sản phẩmproduct
bao gồm tuần suất thanh toán(tuần, tháng,...), số tiền thanh toán mỗi lần- Để tạo
Plan
thì có 2 cách đó là mn có thể tạo trực tiếp ở Stripe dashboard hoặc có thể tạo ở local máy bằngrails c
thông quaStripe API
. Ở đây mình sẽ sử dụng cách 2 nhé:Stripe::Plan.create({ amount: 10000, interval: 'month', product: { name: 'Premium plan', }, currency: 'usd', id: 'premium-plan', })
-
Chuẩn bị
Product
: Mình sẽ tạoproduct
có trườngstripe_plan_name
dùng để lưuplan
củaproduct
đó nhé. Theo thực tế thì 1product
có thể có nhiềuplan
nhưng để đơn giản nên mình làm luôn 1product
chỉ cóplan
thôiProduct.create( price_cents: 10000, name: 'Premium Plan', stripe_plan_name: 'premium-plan' )
-
Vậy là chúng ta đã có
product
vàplan
củaproduct
đó
Step2: Migrate thêm một số column cần thiết
- Tiếp đến để có thể quản lí được sự đồng bộ giữa
user mua hàng
ở server chúng ta vớiCustomer
ởstripe
thì mình sẽ thêm một cộtstripe_customer_id
vào bảngUser
:rails generate migration AddStripeCustomerIdToUser stripe_customer_id:string rails db:migrate
Step3: Xử lí logic để đăng kí một subscription
-
Tiếp tục phần quan trọng là code chức năng xử lí đăng kí một
subscription
. -
Mọi người mở file
app/services/orders/stripe.rb
và thêm đoạn code phía dưới nhé:def self.execute_subscription(plan:, customer:) customer.subscriptions.create({ plan: plan }) end def self.find_or_create_customer(card_token:, customer_id:, email:) if customer_id stripe_customer = Stripe::Customer.retrieve({ id: customer_id }) if stripe_customer stripe_customer = Stripe::Customer.update(stripe_customer.id, { source: card_token}) end else stripe_customer = Stripe::Customer.create({ email: email, source: card_token }) end stripe_customer end
- Ở trên mình có 2 method:
excute_subscription
: Hàm này mục đích dùng để thực thi việc đăng kí mộtsubscription
customer
vớiplan
truyền vàofind_or_create_customer
: Hàm này mục đích là để tạocustomer
cho người dùng mua hàng- Để hiểu rõ hơn một số hàm có sẵn của Stripe như
Stripe::Customer
,Stripe::Plan
,... mn tham khảo thêm ở stripe api doc nhé
- Ở trên mình có 2 method:
-
Tiếp đến mình sửa lại hàm
excute
ở fileapp/services/orders/stripe.rb
, thay dòng comment#SUBSCRIPTIONS WILL BE HANDLED HERE
thành đoạn code phía dưới:customer = self.find_or_create_customer( card_token: order.token, customer_id: user.stripe_customer_id, email: user.email ) if customer user.update(stripe_customer_id: customer.id) order.customer_id = customer.id charge = self.execute_subscription(plan: product.stripe_plan_name, customer: customer)
- Ở đây mình kiểm tra xem
người dùng
đã đăng kíCustomer
ởstripe
chưa, nếu chưa thì mình sẽ đăng kíCustomer
thông qua hàmfind_or_create_customer
. Tiếp đến là thực thi đăng kísubscription
thông qua hàmexcute_subscription
.
- Ở đây mình kiểm tra xem
Step 4: Kiểm tra chức năng
Mọi người truy cập lại trang web của mình và chọn mua product có tên là Premium Plan
và sau đó kiểm tra tại payment dashboar để thấy kết quả thanh toán nhé.
Paypal
Cũng tương tự với stripe
để có thể tích hợp được paypal
vào dự án thì chúng ta cần thêm gem paypal-sdk-rest và tạo một tài khoản ở
paypal.com
Cấu hình Paypal
Step 1: Thêm gem paypal-sdk-rest
- Thêm vào
Gemfile
:gem 'paypal-sdk-rest'
- Chạy lại lệnh
bundle install
Step 2: Tạo Paypal API key
- Tạo một tài khoản Paypal ở developer.paypal.com.
- Khi mn vừa tạo tài khoản xong thì Paypal sẽ tự động generate ra 2 account test ở môi trường sandbox cho mn. Mn có thể truy câp https://developer.paypal.com/developer/accounts/ để kiểm tra.
- 1 tài khoản
Personal
: Đại diện cho người dùng thực tế, tới đây mình sẽ dùng tài khoản này để thử mua hàng. - 1 tài khoản
Business
: Đại diện cho tài khoản của cửa hàng, doanh nghiệp. Khi người dùng mua hàng thì tiền thanh toán sẽ được chuyển về cho tài khoản này
- 1 tài khoản
- Tạo
ứng dụng
: Cũng giống như facebook, google, Paypal cũng áp dụng việc tạo mộtứng dụng
giống như cái cổng cho phépdự án
của chúng ta kết nối vớiPaypal
thông quaAPI KEY
có được từứng dụng
. Mn truy cập applications và tạo một ứng dụng ở trang này. Sau đó sẽ nhận được như hình dưới
- Sau khi có được
Client ID
vàSecret
mọi người mở fileconfig/application.yml
và cập nhật giá trị cho các keysPAYPAL_CLIENT_ID
,PAYPAL_SECRET_KEY
vàPAYPAL_ENV
thì mọi người để là "sandbox"
Step 3: Config paypal
-
Tiếp đến là config ở phía server để cho server có thể tương tác được với
paypal
-
Mọi người tạo mới file
config/initializers/paypal.rb
như dưới:PayPal::SDK.configure( mode: ENV['PAYPAL_ENV'], client_id: ENV['PAYPAL_CLIENT_ID'], client_secret: ENV['PAYPAL_CLIENT_SECRET'], ) PayPal::SDK.logger.level = Logger::INFO
Step 4: Tích hợp paypal
vào giao diện
-
Thêm đoạn require
paypal js
vào ở đầu fileindex.html.slim
script src="https://www.paypal.com/sdk/js?client-id=#{ENV['PAYPAL_CLIENT_ID']}"
-
Khác với
Stripe
,Paypal
chỉ có mộtbutton
và khi người dùng click vào nó thì sẽ hiển thị một popup để người dùng login tài khoản và sau đó chọn hình thức thanh toán. -
Để làm được điều đó Paypal JS có
paypal.Buttons(PARAM1).render(PARAM2)
PARAM1
: là một object có keyenv
là môi trường thực thi, và 2 callbackcreateOrder
vàonApprove
PARAM2
: 1 string tên của class hoặc id của elemet html, là nơi màbutton
này hiển thị ở trên view.
-
Mọi người thêm đoạn code dưới vào phần
javascript
trong fileviews/orders/index.html.slim
(function setupPaypal() { function isPayment() { return $('[data-charges-and-payments-section] input[name="orders[product_id]"]:checked').length } function submitOrderPaypal(chargeID) { var $form = $("#order-details"); // Add a hidden input orders[charge_id] $form.append($('<input type="hidden" name="orders[charge_id]"/>').val(chargeID)); // Set order type $('#order-type').val('paypal'); $form.submit(); } paypal.Buttons({ env: "#{ENV['PAYPAL_ENV']}", createOrder: function() { }, onApprove: function(data) { } }).render('#submit-paypal'); }());
- Cuối cùng bạn truy cập local và chọn hình thức thanh toán là paypal và thấy có button
paypal
thì đã thành công
- Cuối cùng bạn truy cập local và chọn hình thức thanh toán là paypal và thấy có button
Cách hoạt động của Paypal
Khác với Stripe thì thanh toán bằng Paypal phức tạp hơn khá là nhiều từ chu trình thanh toán cho đến cách thức tích hợp nó vào dự án. Paypal chia luồng xử lí qua nhiều tầng nên việc request giữa client với server và bên thứ ba paypal
cũng khá là nhiều. Vậy nên mình sẽ thêm phần này mục đích để chia sẻ sơ qua cách thức hoạt động của thanh toán quapaypal
-
Step 1: Đầu tiên khi mọi người click vào button paypal thì sẽ có một
popup
hiển thị đang loading và lúc này sẽ nhảy vào hàmcreateOrder
và hàm này sẽ lấy các thông tin cần thanh toán như sản phẩm, tổng tiền,... và request lên server của mình. Lúc này server nhận thông tin và sử dụng hàmPayPal::SDK::REST::Payment.new
để tạo mộtpayment
và gửi lạipayment.token
cho client. Client sau khi từ server mộttoken
. -
Step 2: Sau khi nhận được
token
thì sẽ hiển thị một formlogin
paypal ởpopup
-
Step 3: Sau khi đăng nhập thành công thì sẽ hiển thị màn hình thanh toán. Khi mình click button
Pay now
lúc này methodonApprove
sẽ được gọi và hàm này gọi lên server của mình thực thi thanh toán thông qua hàmpayment = PayPal::SDK::REST::Payment.find(payment_id)
vàpayment.execute
. Nếupayment.execute
thành công thì mới tiến hành trigger button submit ở form của mình. Kết thúc thanh toán
Tiến hành thực thi payment
-
Step 1: Tạo 2 router
post 'orders/paypal/create_payment' => 'orders#paypal_create_payment', as: :paypal_create_payment post 'orders/paypal/execute_payment' => 'orders#paypal_execute_payment', as: :paypal_execute_payment
paypal_create_payment
dùng để tạopayment
và trả vềtoken
cho client tương ứng vớistep 1
mình giải thích ở trên nhépaypal_execute_payment
dùng để thực thi payment tương ứng vớistep 3
-
Step 2: Tạo một service thực thi thanh toán, tạo file
app/services/orders/paypal.rb
với đoạn code phía dưới:- Hàm
finish
: đơn giản chỉ là cập trang thái của order sau khi đã hoàn tất việc thanh toán - Hàm
create_payment
: Dùng để tạo payment và trả về mộttoken
- Hàm
execute_payment
: Dùng để thực thì payment qua hàmpayment.execute
class Orders::Paypal def self.finish(charge_id) order = Order.paypal_executed.find_by(charge_id: charge_id) return nil if order.nil? order.set_paid order end def self.create_payment(order:, product:) payment_price = (product.price_cents / 100.0).to_s currency = "USD" payment = PayPal::SDK::REST::Payment.new({ intent: "sale", payer: { payment_method: "paypal" }, redirect_urls: { return_url: "/", cancel_url: "/" }, transactions: [{ item_list: { items: [{ name: product.name, sku: product.name, price: payment_price, currency: currency, quantity: 1 } ] }, amount: { total: payment_price, currency: currency }, description: "Payment for: #{product.name}" }] }) if payment.create order.token = payment.token order.charge_id = payment.id return payment.token if order.save end end def self.execute_payment(payment_id:, payer_id:) order = Order.recently_created.find_by(charge_id: payment_id) return false unless order payment = PayPal::SDK::REST::Payment.find(payment_id) if payment.execute(payer_id: payer_id) order.set_paypal_executed return order.save end end end
- Hàm
-
Step 3: Gọi services ở controller
app/controllers/orders_controller.rb
- Thêm callback before_action
prepare_new_order
, hàm này tạo ở phần trước nên mọi người có thể xem lạiclass OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment] ... end
- Chỉnh sửa lại hàm
checkout
, gọi hàmfinish
để hoàn tất việc thanh toándef checkout if order_params[:payment_gateway] == "stripe" ... else @order = Order.find_by charge_id: order_params[:charge_id] @order.set_paid end ... end render html: "Failed" end
- Thêm 2 action
paypal_create_payment
vàpaypal_execute_payment
def paypal_create_payment result = Orders::Paypal.create_payment(order: @order, product: @product) if result render json: { token: result }, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end def paypal_execute_payment if Orders::Paypal.execute_payment(payment_id: params[:paymentID], payer_id: params[:payerID]) render json: {}, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end
- Cuối cùng mọi người thêm xử lí ở 2 callback
onCreateOrder
vàonApprove
:paypal.Buttons({ env: "#{ENV['PAYPAL_ENV']}", createOrder: function() { $('#order-type').val("paypal"); if (isPayment()) { return $.post("#{paypal_create_payment_url}", $('#order-details').serialize()).then(function(data) { return data.token; }); } else { } }, onApprove: function(data) { if (isPayment()) { return $.post("#{paypal_execute_payment_url}", { paymentID: data.paymentID, payerID: data.payerID }).then(function() { submitOrderPaypal(data.paymentID) }); } else { } } }).render('#submit-paypal');
- Thêm callback before_action
-
Step 4: Kiểm thử chức năng
- Truy cập trang order
- Chọn sản phẩm và chọn hình thức thanh toán
paypal
- Nhấn chọn button
Paypal
- Đăng nhập tài khoản
personal sanbox
mình tạo ở bước tạo tài khoản - Chọn địa điểm ship, hình thức thanh toán của
paypal
cung cấp và nhấnPay now
- Đăng nhập tài khoản
- Truy cập vào tài khoản
personal sanbox
ở paypal.com để kiểm tra xem thông tin thanh toán.
Hy vọng qua bài chia sẻ này thì mọi người ứng dụng được các hình thức thanh toán online vào trang web của mình một cách thành công. Nếu có thắc mắc trong quá trình thực hiện thì có thể cmt vào bài viết nhé.
Cảm ơn mn đã đọc hết bài viết của mình. 😁😁
Happy Coding!!!👍👍👍
All rights reserved