دمج طرق الدفع Stripe و PayPal في Ruby on Rails
نشرت: 2022-03-11الميزة الرئيسية لشركات التجارة الإلكترونية الضخمة مثل AliExpress و Ebay و Amazon هي طريقة آمنة للتعامل مع المدفوعات ، وهو أمر ضروري لأعمالهم. إذا فشلت هذه الميزة ، فإن العواقب ستكون مدمرة. ينطبق هذا على رواد الصناعة ومطوري Ruby on Rails الذين يعملون على تطبيقات التجارة الإلكترونية.
يعد الأمن السيبراني ضروريًا لمنع الهجمات ، وطريقة لجعل عملية المعاملات أكثر أمانًا هي مطالبة خدمة تابعة لجهة خارجية بمعالجتها. يعد تضمين بوابات الدفع في تطبيقك طريقة لتحقيق هذا الهدف ، حيث أنها توفر تفويضًا للمستخدم وتشفير البيانات ولوحة معلومات حتى تتمكن من متابعة حالة المعاملة على الفور.
هناك مجموعة متنوعة من خدمات بوابات الدفع على الويب ، ولكن في هذه المقالة ، سأركز على دمج Stripe و PayPal في تطبيق ريلز. على سبيل المثال لا الحصر: Amazon Payments أو Square أو SecurePay أو WorldPay أو Authorize.Net أو 2Checkout.com أو Braintree أو Amazon أو BlueSnap.
كيف يعمل تكامل بوابة الدفع

بشكل عام ، سيكون هناك نموذج / زر في التطبيق الخاص بك حيث يمكن للمستخدم تسجيل الدخول / إدخال بيانات بطاقة الائتمان. يجعل PayPal و Stripe بالفعل هذه الخطوة الأولى أكثر أمانًا باستخدام نماذج iframe
أو popups
التي تمنع تطبيقك من تخزين معلومات بطاقة ائتمان المستخدم الحساسة حيث أنها ستعيد رمزًا مميزًا يمثل هذه المعاملة. قد يشعر بعض المستخدمين أيضًا بثقة أكبر في معالجة المدفوعات من خلال معرفة أن خدمة جهة خارجية تتعامل مع عملية المعاملة ، لذلك يمكن أن يكون هذا أيضًا عامل جذب لتطبيقك.
بعد مصادقة معلومات المستخدم ، ستؤكد بوابة الدفع الدفع عن طريق الاتصال بمعالج الدفع الذي يتواصل مع البنوك من أجل تسوية المدفوعات. هذا يضمن أن المعاملة يتم خصمها / قيدها بشكل صحيح.
يستخدم Stripe نموذج بطاقة ائتمان يسأل عن رقم بطاقة الائتمان والسيرة الذاتية وتاريخ انتهاء الصلاحية. لذلك يجب على المستخدم ملء معلومات بطاقة الائتمان في مدخلات Stripe المضمونة. بعد تقديم هذه المعلومات ، يعالج تطبيقك الخلفي هذه الدفعة من خلال رمز مميز.
على عكس Stripe ، يعيد PayPal توجيه المستخدم إلى صفحة تسجيل الدخول إلى PayPal. يصرح المستخدم ويحدد طريقة الدفع من خلال PayPal ، ومرة أخرى ، ستتعامل النهاية الخلفية مع الرموز المميزة بدلاً من البيانات الحساسة للمستخدم.
من المهم الإشارة إلى أنه ، بالنسبة لبوابتَي الدفع هاتين ، يجب أن تطلب الجهة الخلفية الخاصة بك متابعة تنفيذ المعاملة من خلال واجهات برمجة تطبيقات Stripe أو PayPal والتي ستعطي استجابة OK / NOK ، لذلك يجب على التطبيق الخاص بك إعادة توجيه المستخدم إلى صفحة ناجحة أو صفحة خطأ وفقًا لذلك.
الهدف من هذه المقالة هو تقديم دليل سريع لدمج بوابتي الدفع هاتين في تطبيق واحد. بالنسبة لجميع الاختبارات ، سنستخدم صناديق الحماية وحسابات الاختبار المقدمة من Stripe و PayPal لمحاكاة المدفوعات.
اقامة
قبل دمج بوابات الدفع ، سنقوم بإعداد لتهيئة التطبيق عن طريق إضافة الأحجار الكريمة وجداول قاعدة البيانات وصفحة الفهرس. تم إنشاء هذا المشروع باستخدام الإصدار 5.2.3 من Rails و Ruby 2.6.3.
ملاحظة: يمكنك التحقق من ميزات ريلز 6 الجديدة في مقالتنا الأخيرة.
الخطوة 1: تهيئة تطبيق ريلز.
ابدأ المشروع عن طريق تشغيل تهيئة المشروع باستخدام الأمر rails
باسم التطبيق الخاص بك:
rails new YOUR_APP_NAME
cd
في مجلد التطبيق الخاص بك.
الخطوة الثانية: تثبيت الجواهر.
إلى جانب جواهر Stripe و PayPal ، تمت إضافة بعض الأحجار الكريمة الأخرى:
-
devise
: تستخدم لمصادقة المستخدم والترخيص -
haml
: أداة قوالب لعرض صفحات المستخدم -
jquery-rails
: لـjquery
في نصوص الواجهة الأمامية -
money-rails
: لعرض قيم الأموال المنسقة
أضف إلى Gemfile
الخاص بك:
gem "devise", ">= 4.7.1" gem "haml" gem "jquery-rails" gem "money-rails"
بعد إضافته ، قم بتشغيله في CLI الخاص بك:
bundle install
الخطوة 3: تهيئة الجواهر.
ستتطلب بعض هذه الأحجار الكريمة التهيئة إلى جانب تثبيتها من خلال bundle
.
تركيب الجهاز:
rails g devise:install
تهيئة money-rails
:
rails g money_rails:initializer
jquery-rails
من خلال إلحاق الجزء السفلي من app/assets/javascripts/application.js
بما يلي:
//= require jquery //= require jquery_ujs
الخطوة 4: الجداول والترحيلات
سيتم استخدام ثلاثة جداول في هذا المشروع المستخدمون والمنتجات والأوامر .
-
Users
: سيتم إنشاؤه من خلال ابتكار - أعمدة
Products
:-
name
-
price_cents
-
Stripe_plan_name
: معرف يمثل خطة اشتراك تم إنشاؤها في Stripe ، بحيث يمكن للمستخدمين الاشتراك فيها. هذا الحقل مطلوب فقط للمنتجات المرتبطة بخطة Stripe. -
paypal_plan_name
: نفس اسمstripe_plan_name
ولكن لـ PayPal
-
- أعمدة
Orders
:-
product_id
-
user_id
-
status
: سيُعلم هذا إذا كان الطلب معلقًا أو فاشلاً أو مدفوعًا. -
token
: هذا هو رمز تم إنشاؤه من واجهات برمجة التطبيقات (إما Stripe أو PayPal) من أجل تهيئة المعاملة. -
price_cents
: تشبه المنتج ، ولكنها تستخدم لجعل هذه القيمة ثابتة في سجل الطلب -
payment_gateway
: المتاجر التي تستخدم بوابة الدفع للطلب PayPal أو Stripe -
customer_id
: سيتم استخدام هذا في Stripe لتخزين عميل Stripe للاشتراك ، وسيتم شرحه بمزيد من التفاصيل في قسم لاحق.
-
لإنشاء هذه الجداول ، يجب إنشاء عدد قليل من عمليات الترحيل:
لإنشاء جدول المستخدمين . يركض:
rails g devise User
لإنشاء جدول المنتجات . قم بإنشاء ترحيل عن طريق تشغيل:
rails generate migration CreateProducts name:string stripe_plan_name:string paypal_plan_name:string
افتح ملف الترحيل الذي تم إنشاؤه ، والذي يجب أن يكون موجودًا على db/migrate/
، وقم بإجراء تغييرات لجعل الترحيل الخاص بك يبدو مشابهًا لما يلي:
class CreateProducts < ActiveRecord::Migration[5.2] def change create_table :products do |t| t.string :name t.string :stripe_plan_name t.string :paypal_plan_name end add_money :products, :price, currency: { present: true } end end
لإنشاء جدول الطلبات . قم بإنشاء ترحيل عن طريق تشغيل:
rails generate migration CreateOrders product_id:integer user_id:integer status:integer token:string charge_id:string error_message:string customer_id:string payment_gateway:integer
مرة أخرى ، افتح ملف الترحيل الذي تم إنشاؤه والذي يجب أن يكون موجودًا على db/migrate/
وقم بإجراء تغييرات على هذا الملف لجعله يبدو مشابهًا لما يلي:
class CreateOrders < ActiveRecord::Migration[5.2] def change create_table :orders do |t| t.integer :product_id t.integer :user_id t.integer :status, default: 0 t.string :token t.string :charge_id t.string :error_message t.string :customer_id t.integer :payment_gateway t.timestamps end add_money :orders, :price, currency: { present: false } end end
قم بتشغيل عمليات ترحيل قاعدة البيانات عن طريق تنفيذ:
rails db:migrate
الخطوة 5: إنشاء النماذج.
تم إنشاء نموذج المستخدم بالفعل من وضع التثبيت ، ولن تكون هناك حاجة لإجراء أي تغييرات عليه. بالإضافة إلى ذلك ، سيتم إنشاء نموذجين للمنتج والنظام .
المنتوج. أضف ملفًا جديدًا ، app/models/product.rb
، مع:
class Product < ActiveRecord::Base monetize :price_cents has_many :orders end
ترتيب. أضف ملفًا جديدًا ، app/models/order.rb
، مع:
class Order < ApplicationRecord enum status: { pending: 0, failed: 1, paid: 2, paypal_executed: 3} enum payment_gateway: { stripe: 0, paypal: 1 } belongs_to :product belongs_to :user scope :recently_created, -> { where(created_at: 1.minutes.ago..DateTime.now) } def set_paid self.status = Order.statuses[:paid] end def set_failed self.status = Order.statuses[:failed] end def set_paypal_executed self.status = Order.statuses[:paypal_executed] end end
الخطوة 6: ملء قاعدة البيانات.
سيتم إنشاء مستخدم ومنتجين في وحدة التحكم. سيتم إنشاء سجلات الطلبات وفقًا لاختبارات الدفع.
- تشغيل
rails s
- في المستعرض الخاص بك ، قم بزيارة
http://localhost:3000
- ستتم إعادة توجيهك إلى صفحة تسجيل.
- قم بتسجيل مستخدم عن طريق إدخال عنوان بريده الإلكتروني وكلمة المرور.
- في جهازك الطرفي ، ستتم مطالبتك بالسجلات التالية لتوضيح أن مستخدمًا قد تم إنشاؤه في قاعدة البيانات الخاصة بك:
User Create (0.1ms) INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?) …
- قم بإنشاء منتجين بدون اشتراكات عن طريق تشغيل
rails c
وإضافة:-
Product.create(name: "Awesome T-Shirt", price_cents: 3000)
-
Product.create(name: "Awesome Sneakers", price_cents: 5000)
-
الخطوة 7: قم بإنشاء صفحة فهرس
تتضمن الصفحة الرئيسية للمشروع اختيار المنتج للمشتريات أو الاشتراكات. بالإضافة إلى ذلك ، يحتوي أيضًا على قسم لاختيار طريقة الدفع (Stripe أو PayPal). يتم أيضًا استخدام زر إرسال لكل نوع من أنواع بوابة الدفع ، أما بالنسبة إلى PayPal ، فسنضيف تصميم الزر الخاص به من خلال مكتبة JavaScript الخاصة به.
أولاً ، أنشئ مسارات index
وأرسلها في config/routes.rb
submit
Rails.application.routes.draw do devise_for :users get '/', to: 'orders#index' post '/orders/submit', to: 'orders#submit' end
قم بإنشاء وإضافة index
الإجراءات submit
في app/controllers/orders_controller.rb
أوامر التحكم في الطلبات. يخزن إجراء orders#index
متغيرين ليتم استهلاكهما في الواجهة الأمامية: @products_purchase
الذي يحتوي على قائمة بالمنتجات بدون خطط و @products_subscription
الذي يحتوي على منتجات بها خطط PayPal و Stripe.
class OrdersController < ApplicationController before_action :authenticate_user! def index products = Product.all @products_purchase = products.where(stripe_plan_name:nil, paypal_plan_name:nil) @products_subscription = products - @products_purchase end def submit end end
قم بإنشاء ملف في app/views/orders/index.html.haml
. يحتوي هذا الملف على جميع المدخلات التي سنرسلها إلى نهايتنا الخلفية من خلال طريقة الإرسال والتفاعل لبوابات الدفع واختيار المنتج. فيما يلي بعض سمات اسم الإدخال:
- تخزن
Orders[product_id]
معرّف المنتج. - تحتوي
Orders[payment_gateway]
على بوابة الدفع بقيم Stripe أو PayPal للآخر.
%div %h1 List of products = form_tag({:controller => "orders", :action => "submit" }, {:id => 'order-details'}) do %input{id:'order-type', :type=>"hidden", :value=>"stripe", :name=>'orders[payment_gateway]'} .form_row %h4 Charges/Payments - @products_purchase.each do |product| %div{'data-charges-and-payments-section': true} = radio_button_tag 'orders[product_id]', product.id, @products_purchase.first == product %span{id: "radioButtonName#{product.id}"} #{product.name} %span{id: "radioButtonPrice#{product.id}", :'data-price' => "#{product.price_cents}"} #{humanized_money_with_symbol product.price} %br %h4 Subscriptions - @products_subscription.each do |product| %div = radio_button_tag 'orders[product_id]', product.id, false %span{id: "radioButtonName#{product.id}"} #{product.name} %span{id: "radioButtonPrice#{product.id}", :'data-price' => "#{product.price_cents}"} #{humanized_money_with_symbol product.price} %br %hr %h1 Payment Method .form_row %div = radio_button_tag 'payment-selection', 'stripe', true, onclick: "changeTab();" %span Stripe %br %div = radio_button_tag 'payment-selection', 'paypal', false, onclick: "changeTab();" %span Paypal %br %br %div{id:'tab-stripe', class:'paymentSelectionTab active'} %div{id:'card-element'} %div{id:'card-errors', role:"alert"} %br %br = submit_tag "Buy it!", id: "submit-stripe" %div{id:'tab-paypal', class:'paymentSelectionTab'} %div{id: "submit-paypal"} %br %br %hr :javascript function changeTab() { var newActiveTabID = $('input[name="payment-selection"]:checked').val(); $('.paymentSelectionTab').removeClass('active'); $('#tab-' + newActiveTabID).addClass('active'); } :css #card-element { width:500px; } .paymentSelectionTab { display: none; } .paymentSelectionTab.active { display: block !important; }
إذا قمت بتشغيل التطبيق الخاص بك باستخدام rails s
وقم بزيارة صفحتك في http://localhost:3000
. يجب أن تكون قادرًا على رؤية الصفحة على النحو التالي:

تخزين بيانات اعتماد بوابة الدفع
سيتم تخزين مفاتيح PayPal و Stripe في ملف لا يتتبعه Git. هناك نوعان من المفاتيح مخزنة في هذا الملف لكل بوابة دفع ، وفي الوقت الحالي ، سنستخدم قيمة وهمية لهما. يتم تقديم إرشادات إضافية لإنشاء هذه المفاتيح في أقسام أخرى.
الخطوة 1: أضف هذا .gitignore
.
/config/application.yml
الخطوة 2: قم بإنشاء ملف ببيانات الاعتماد الخاصة بك في config/application.yml
. يجب أن يحتوي على جميع مفاتيح وضع الحماية / الاختبار الخاصة بـ PayPal و Stripe للوصول إلى واجهات برمجة التطبيقات هذه.
test: &default PAYPAL_ENV: sandbox PAYPAL_CLIENT_ID: YOUR_CREDENTIAL_HERE PAYPAL_CLIENT_SECRET: YOUR_CREDENTIAL_HERE STRIPE_PUBLISHABLE_KEY: YOUR_CREDENTIAL_HERE STRIPE_SECRET_KEY: YOUR_CREDENTIAL_HERE development: <<: *default
الخطوة 3: من أجل تخزين المتغيرات من الملف config/application.yml
عند بدء التطبيق ، أضف هذه السطور في config/application.rb
داخل فئة Application
حتى تكون متاحة في ENV
.
config_file = Rails.application.config_for(:application) config_file.each do |key,value| ENV[key] = value end unless config_file.nil?
تكوين الشريط
سنضيف جوهرة لاستخدام Stripe API: stripe-rails
. مطلوب أيضًا إنشاء حساب Stripe حتى يمكن معالجة الرسوم والاشتراكات. إذا كان عليك ذلك ، يمكنك استشارة طرق API لـ Stripe API في الوثائق الرسمية.
الخطوة 1: أضف الأحجار الكريمة الشريطية إلى مشروعك.
ستوفر جوهرة القضبان الشريطية واجهة لجميع طلبات واجهة برمجة التطبيقات المستخدمة في هذا المشروع.
أضف هذا في ملف Gemfile
:
gem 'stripe-rails'
يركض:
bundle install
الخطوة 2: إنشاء مفاتيح API الخاصة بك.
من أجل الحصول على مفاتيح API للتواصل مع Stripe ، ستحتاج إلى إنشاء حساب في Stripe. لاختبار التطبيق ، من الممكن استخدام وضع الاختبار ، لذلك لا يلزم ملء معلومات عمل حقيقية في عملية إنشاء حساب Stripe.
- قم بإنشاء حساب في Stripe إذا لم يكن لديك حساب (https://dashboard.stripe.com/).
- أثناء وجودك في لوحة معلومات Stripe ، بعد تسجيل الدخول ، قم بتبديل عرض بيانات الاختبار .
- على https://dashboard.stripe.com/test/apikeys ،
YOUR_CREDENTIAL_HERE
بالقيمSTRIPE_PUBLISHABLE_KEY
وSTRIPE_SECRET_KEY
في/config/application.yml
بمحتوى منPublishable Key
Secret key
.
الخطوة 3: تهيئة وحدة الشريط
بالإضافة إلى استبدال المفاتيح ، ما زلنا بحاجة إلى تهيئة وحدة Stripe ، بحيث تستخدم المفاتيح التي تم تعيينها بالفعل في ENV
الخاص بنا.
أنشئ ملفًا في config/initializers/stripe.rb
باستخدام:
Rails.application.configure do config.stripe.secret_key = ENV["STRIPE_SECRET_KEY"] config.stripe.publishable_key = ENV["STRIPE_PUBLISHABLE_KEY"] end
الخطوة 4: دمج Stripe في الواجهة الأمامية.
سنضيف مكتبة Stripe JavaScript والمنطق لإرسال رمز يمثل معلومات بطاقة ائتمان المستخدم وستتم معالجته في النهاية الخلفية لدينا.
في ملف index.html.haml
، أضف هذا إلى أعلى ملفك. سيستخدم هذا وحدة Stripe (التي توفرها الأحجار الكريمة) لإضافة مكتبة Stripe javascript إلى صفحة المستخدم.
= stripe_javascript_tag
يستخدم Stripe حقول الإدخال الآمنة التي يتم إنشاؤها من خلال واجهة برمجة التطبيقات الخاصة بهم. نظرًا لأنه تم إنشاؤها في إطار iframe
تم إنشاؤه من خلال واجهة برمجة التطبيقات هذه ، فلا داعي للقلق بشأن نقاط الضعف المحتملة التي تتعامل مع معلومات بطاقة ائتمان المستخدم. بالإضافة إلى ذلك ، لن تتمكن الواجهة الخلفية من معالجة / تخزين أي بيانات حساسة للمستخدم ، وستتلقى فقط رمزًا مميزًا يمثل هذه المعلومات.
يتم إنشاء حقول الإدخال هذه عن طريق استدعاء stripe.elements().create('card')
. بعد ذلك ، يُطلب فقط استدعاء الكائن المُعاد باستخدام mount()
عن طريق تمرير مُعرّف / فئة عنصر HTML كوسيطة حيث يجب تثبيت هذه المدخلات. يمكن العثور على مزيد من المعلومات في Stripe.
عندما يضغط المستخدم على زر الإرسال باستخدام طريقة الدفع Stripe ، يتم تنفيذ استدعاء API آخر يعيد الوعد على عنصر بطاقة Stripe الذي تم إنشاؤه:
stripe.createToken(card).then(function(result)
متغير result
لهذه الوظيفة ، إذا لم يتم تعيين خطأ خاصية ، فسيكون له رمز مميز يمكن استرداده من خلال الوصول إلى السمة result.token.id
. سيتم إرسال هذا الرمز المميز إلى النهاية الخلفية.
لإجراء هذه التغييرات ، استبدل الكود المعلق // YOUR STRIPE AND PAYPAL CODE WILL BE HERE
in index.html.haml
بـ:
(function setupStripe() { //Initialize stripe with publishable key var stripe = Stripe("#{ENV['STRIPE_PUBLISHABLE_KEY']}"); //Create Stripe credit card elements. var elements = stripe.elements(); var card = elements.create('card'); //Add a listener in order to check if card.addEventListener('change', function(event) { //the div card-errors contains error details if any var displayError = document.getElementById('card-errors'); document.getElementById('submit-stripe').disabled = false; if (event.error) { // Display error displayError.textContent = event.error.message; } else { // Clear error displayError.textContent = ''; } }); // Mount Stripe card element in the #card-element div. card.mount('#card-element'); var form = document.getElementById('order-details'); // This will be called when the #submit-stripe button is clicked by the user. form.addEventListener('submit', function(event) { $('#submit-stripe').prop('disabled', true); event.preventDefault(); stripe.createToken(card).then(function(result) { if (result.error) { // Inform that there was an error. var errorElement = document.getElementById('card-errors'); errorElement.textContent = result.error.message; } else { // Now we submit the form. We also add a hidden input storing // the token. So our back-end can consume it. var $form = $("#order-details"); // Add a hidden input orders[token] $form.append($('<input type="hidden" name="orders[token]"/>').val(result.token.id)); // Set order type $('#order-type').val('stripe'); $form.submit(); } }); return false; }); }()); //YOUR PAYPAL CODE WILL BE HERE
إذا قمت بزيارة صفحتك ، فيجب أن تبدو كما يلي مع حقول الإدخال الآمنة الجديدة لـ Stripe:

الخطوة 5: اختبر طلبك.
املأ نموذج بطاقة الائتمان ببطاقة اختبار (https://stripe.com/docs/testing) وأرسل الصفحة. تحقق مما إذا كان يتم استدعاء إجراء submit
مع جميع المعلمات ( معرّف_المنتج ، وبوابة الدفع ، والرمز المميز ) في إخراج الخادم الخاص بك.
رسوم شريطية
تمثل الرسوم الشريطية المعاملات لمرة واحدة. لذلك ، بعد معاملة رسوم Stripe ، ستتلقى أموالًا من العميل مباشرةً. هذا مثالي لبيع المنتجات التي لا ترتبط بالخطط. في قسم لاحق ، سأوضح كيفية القيام بنفس نوع المعاملة مع PayPal ، لكن اسم PayPal لهذا النوع من المعاملات هو الدفع .
في هذا القسم ، سأوفر أيضًا جميع الهياكل الأساسية للتعامل مع الطلب وتقديمه. نقوم بإنشاء طلب في إجراء submit
عند تقديم نموذج Stripe. سيكون لهذا الطلب في البداية الحالة المعلقة ، لذلك إذا حدث خطأ ما أثناء معالجة هذا الطلب ، فسيظل الطلب معلقًا .
إذا نشأ أي خطأ من استدعاءات Stripe API ، فإننا نضبط الأمر في حالة فاشلة ، وإذا اكتملت الرسوم بنجاح ، فستكون في الحالة المدفوعة . يتم أيضًا إعادة توجيه المستخدم وفقًا لاستجابة Stripe API كما هو موضح في الرسم البياني التالي:

بالإضافة إلى ذلك ، عند تنفيذ رسوم Stripe ، يتم إرجاع المعرف. سنقوم بتخزين هذا المعرف حتى تتمكن من البحث عنه لاحقًا في لوحة معلومات Stripe إذا لزم الأمر. يمكن أيضًا استخدام هذا المعرف إذا كان لا بد من استرداد قيمة الطلب. مثل هذا الشيء لن يتم استكشافه في هذه المقالة.
الخطوة 1: قم بإنشاء خدمة Stripe.
سنستخدم فئة واحدة لتمثيل عمليات Stripe باستخدام Stripe API. من أجل إنشاء رسوم ، يتم استدعاء الطريقة Stripe::Charge.create
، وسيتم تخزين سمة معرف الكائن المرتجع في سجل الطلب charge_id
. يتم استدعاء وظيفة create
هذه عن طريق تمرير الرمز المميز الذي تم إنشاؤه في الواجهة الأمامية وسعر الأمر والوصف.
لذلك ، قم بإنشاء app/services/orders
جديدة للمجلد ، وأضف خدمة Stripe: app/services/orders/stripe.rb
تحتوي على Orders::Stripe
singleton class ، والذي يحتوي على إدخال في طريقة execute
.
class Orders::Stripe INVALID_STRIPE_OPERATION = 'Invalid Stripe Operation' def self.execute(order:, user:) product = order.product # Check if the order is a plan if product.stripe_plan_name.blank? charge = self.execute_charge(price_cents: product.price_cents, description: product.name, card_token: order.token) else #SUBSCRIPTIONS WILL BE HANDLED HERE end unless charge&.id.blank? # If there is a charge with id, set order paid. order.charge_id = charge.id order.set_paid end rescue Stripe::StripeError => e # If a Stripe error is raised from the API, # set status failed and an error message order.error_message = INVALID_STRIPE_OPERATION order.set_failed end private def self.execute_charge(price_cents:, description:, card_token:) Stripe::Charge.create({ amount: price_cents.to_s, currency: "usd", description: description, source: card_token }) end end
الخطوة 2: قم بتنفيذ إجراء الإرسال واستدعاء خدمة Stripe.
في orders_controller.rb
، أضف ما يلي في إجراء submit
، والذي سيستدعي بشكل أساسي Orders::Stripe.execute
. لاحظ أنه تمت إضافة وظيفتين خاصتين جديدتين: prepare_new_order
و order_params
.
def submit @order = nil #Check which type of order it is if order_params[:payment_gateway] == "stripe" prepare_new_order Orders::Stripe.execute(order: @order, user: current_user) elsif order_params[:payment_gateway] == "paypal" #PAYPAL WILL BE HANDLED HERE end ensure if @order&.save if @order.paid? # Success is rendered when order is paid and saved return render html: SUCCESS_MESSAGE elsif @order.failed? && [email protected]_message.blank? # Render error only if order failed and there is an error_message return render html: @order.error_message end end render html: FAILURE_MESSAGE end private # Initialize a new order and and set its user, product and price. def prepare_new_order @order = Order.new(order_params) @order.user_id = current_user.id @product = Product.find(@order.product_id) @order.price_cents = @product.price_cents end def order_params params.require(:orders).permit(:product_id, :token, :payment_gateway, :charge_id) end
الخطوة 3: اختبر طلبك.
تحقق مما إذا كان إجراء الإرسال ، عند استدعائه ببطاقة اختبار صالحة ، يؤدي إلى إعادة توجيه إلى رسالة ناجحة. بالإضافة إلى ذلك ، تحقق من لوحة معلومات Stripe الخاصة بك إذا تم عرض الطلب أيضًا.
اشتراكات الشريط
يمكن إنشاء اشتراكات أو خطط للمدفوعات المتكررة. باستخدام هذا النوع من المنتجات ، يتم فرض رسوم على المستخدم يوميًا أو أسبوعيًا أو شهريًا أو سنويًا تلقائيًا وفقًا لتكوين الخطة. في هذا القسم ، سنستخدم الحقل الخاص بالمنتج stripe_plan_name
من أجل تخزين معرف الخطة - في الواقع ، من الممكن لنا اختيار المعرف ، وسنسميها premium-plan
التي سيتم استخدامها لإنشاء customer <-> subscription
.
سننشئ أيضًا عمودًا جديدًا لجدول المستخدمين يسمى stripe_customer_id
والذي سيتم ملؤه بخاصية معرف كائن عميل Stripe. يتم إنشاء عميل Stripe عند استدعاء الوظيفة Stripe::Customer.create
، ويمكنك أيضًا التحقق من العملاء الذين تم إنشاؤهم وربطهم بحسابك في (https://dashboard.stripe.com/test/customers). يتم إنشاء العملاء عن طريق تمرير معلمة source
والتي ، في حالتنا ، هي الرمز المميز الذي تم إنشاؤه في الواجهة الأمامية والذي يتم إرساله عند تقديم النموذج.
يتم أيضًا استخدام كائن العميل الذي تم الحصول عليه من استدعاء Stripe API المذكور ، لإنشاء اشتراك يتم عن طريق استدعاء customer.subscriptions.create
وتمرير معرف الخطة كمعامل.
بالإضافة إلى ذلك ، توفر جوهرة stripe-rails
واجهة لاسترداد وتحديث عميل من Stripe ، ويتم ذلك عن طريق استدعاء Stripe::Customer.retrieve
و Stripe::Customer.update
، على التوالي.
لذلك ، عندما يحتوي سجل المستخدم بالفعل على stripe_customer_id
، فبدلاً من إنشاء عميل جديد باستخدام Stripe::Customer.create
، سنقوم باستدعاء Stripe::Customer.retrieve
تمرير stripe_customer_id
، متبوعًا بـ Stripe::Customer.update
، وفي هذه الحالة ، تمرير الرمز المميز معلمة.
أولاً ، سننشئ خطة باستخدام Stripe API حتى نتمكن من إنشاء منتج اشتراك جديد باستخدام الحقل stripe_plan_name
. بعد ذلك ، سنقوم بإجراء تعديلات في خدمة orders_controller
و Stripe حتى يتم التعامل مع إنشاء اشتراكات Stripe وتنفيذها.
الخطوة 1: قم بإنشاء خطة باستخدام Stripe API.
افتح وحدة التحكم الخاصة بك باستخدام rails c
. إنشاء اشتراك لحساب Stripe الخاص بك مع:

Stripe::Plan.create({ amount: 10000, interval: 'month', product: { name: 'Premium plan', }, currency: 'usd', id: 'premium-plan', })
إذا كانت النتيجة التي تم إرجاعها في هذه الخطوة صحيحة ، فهذا يعني أنه تم إنشاء الخطة بنجاح ، ويمكنك الوصول إليها في لوحة بيانات Stripe.
الخطوة 2: قم بإنشاء منتج في قاعدة البيانات مع مجموعة الحقول stripe_plan_name
.
الآن ، قم بإنشاء منتج مع تعيين stripe_plan_name
premium-plan
في قاعدة البيانات:
Product.create(price_cents: 10000, name: 'Premium Plan', stripe_plan_name: 'premium-plan')
الخطوة 3: قم بإنشاء ترحيل لإضافة عمود stripe_customer_id
في جدول users
.
قم بتشغيل ما يلي في المحطة:
rails generate migration AddStripeCustomerIdToUser stripe_customer_id:string rails db:migrate
الخطوة 4: تنفيذ منطق الاشتراك في فئة خدمة Stripe.
أضف وظيفتين أخريين في الأساليب app/services/orders/stripe.rb
: execute_subscription
مسؤول عن إنشاء الاشتراكات في كائن العميل. الوظيفة find_or_create_customer
هي المسؤولة عن إرجاع العميل الذي تم إنشاؤه بالفعل أو عن طريق إرجاع عميل تم إنشاؤه حديثًا.
def self.execute_subscription(plan:, token:, 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
أخيرًا ، في وظيفة execute
في نفس الملف ( app/services/orders/stripe.rb
execute_subscription
find_or_create_customer
لذلك ، استبدل التعليق #SUBSCRIPTIONS WILL BE HANDLED HERE
في طريقة execute
بالرمز التالي:
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)
الخطوة 5: اختبر طلبك.
قم بزيارة موقع الويب الخاص بك ، وحدد الاشتراك في Premium Plan
، واملأ بطاقة اختبار صالحة. بعد الإرسال ، يجب أن يعيد توجيهك إلى صفحة ناجحة. بالإضافة إلى ذلك ، تحقق من لوحة معلومات Stripe الخاصة بك إذا تم إنشاء الاشتراك بنجاح.
تكوين PayPal
كما فعلنا في Stripe ، سنضيف أيضًا جوهرة لاستخدام PayPal API: paypal-sdk-rest
، كما يلزم إنشاء حساب PayPal. يمكن الرجوع إلى سير عمل وصفي لـ PayPal باستخدام هذه الأحجار الكريمة في وثائق PayPal API الرسمية.
الخطوة 1: أضف جوهرة paypal-sdk-rest
إلى مشروعك.
أضف هذا في ملف Gemfile
:
gem 'paypal-sdk-rest'
يركض:
bundle install
الخطوة 2: إنشاء مفاتيح API الخاصة بك.
من أجل الحصول على مفاتيح API للتواصل مع PayPal ، ستحتاج إلى إنشاء حساب PayPal. وبالتالي:
- أنشئ حسابًا (أو استخدم حساب PayPal الخاص بك) على https://developer.paypal.com/.
- مازلت مسجلاً الدخول إلى حسابك ، وأنشئ حسابين في وضع الحماية على https://developer.paypal.com/developer/accounts/:
- شخصي (حساب المشتري) - سيتم استخدام هذا في اختباراتك لإجراء المدفوعات والاشتراكات.
- الأعمال (حساب التاجر) - سيتم ربط هذا بالتطبيق ، والذي سيحتوي على مفاتيح API التي نبحث عنها. إلى جانب ذلك ، يمكن متابعة جميع المعاملات في هذا الحساب.
- أنشئ تطبيقًا على https://developer.paypal.com/developer/applications باستخدام حساب رمل الأعمال السابق.
- بعد هذه الخطوة ، ستتلقى مفتاحين لـ PayPal:
Client ID
Secret
. - في
config/application.yml
،YOUR_CREDENTIAL_HERE
منPAYPAL_CLIENT_ID
وPAYPAL_CLIENT_SECRET
بالمفاتيح التي تلقيتها للتو.
الخطوة 3: تهيئة وحدة PayPal.
على غرار Stripe ، إلى جانب استبدال المفاتيح في application.yml
، ما زلنا بحاجة إلى تهيئة وحدة PayPal حتى تتمكن من استخدام المفاتيح التي تم تعيينها بالفعل في متغير ENV
الخاص بنا. لهذا الغرض ، أنشئ ملفًا في config/initializers/paypal.rb
باستخدام:
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
الخطوة 4: دمج PayPal في الواجهة الأمامية.
في index.html.haml
أضف هذا إلى أعلى الملف:
%script(src="https://www.paypal.com/sdk/js?client-id=#{ENV['PAYPAL_CLIENT_ID']}")
على عكس Stripe ، لا يستخدم PayPal سوى زر يفتح عند النقر عليه نافذة منبثقة آمنة حيث يمكن للمستخدم تسجيل الدخول والمتابعة إلى الدفع / الاشتراك. يمكن عرض هذا الزر باستدعاء الطريقة paypal.Button(PARAM1).render(PARAM2)
.
-
PARAM1
هو كائن مع تكوين البيئة ووظيفتين لرد الاتصال كخصائص:createOrder
وonApprove
. - يشير
PARAM2
إلى معرف عنصر HTML الذي يجب إرفاق زر PayPal به.
لذلك ، لا يزال في نفس الملف ، استبدل الرمز المعلق الذي سيظهر رمز YOUR PAYPAL CODE WILL BE HERE
بـ:
(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'); }());
الخطوة 5: اختبر طلبك.
قم بزيارة صفحتك وتحقق من ظهور زر PayPal عند تحديد PayPal كطريقة دفع.
معاملات PayPal
يعد منطق معاملات PayPal ، على عكس Stripe ، أكثر تعقيدًا بعض الشيء من خلال تضمين المزيد من الطلبات التي تم إنشاؤها من الواجهة الأمامية إلى النهاية الخلفية. لهذا السبب يوجد هذا القسم. سأشرح أكثر أو أقل (بدون أي رمز) كيف سيتم تنفيذ الوظائف الموصوفة في أساليب createOrder
و onApprove
وما هو متوقع في عمليات النهاية الخلفية أيضًا.
الخطوة 1: عندما ينقر المستخدم على زر إرسال PayPal ، تكون نافذة PayPal المنبثقة التي تطلب بيانات اعتماد المستخدم مفتوحة ولكن في حالة تحميل. يتم استدعاء دالة رد الاتصال createOrder
.

الخطوة 2: في هذه الوظيفة ، سنقوم بتنفيذ طلب إلى نهايتنا الخلفية والذي سينشئ دفعًا / اشتراكًا. هذه هي بداية المعاملة ، ولن يتم تطبيق أي رسوم حتى الآن ، وبالتالي فإن المعاملة في الواقع في حالة معلقة . يجب أن تعيد لنا نهايتنا الخلفية رمزًا ، والذي سيتم إنشاؤه باستخدام وحدة PayPal (يتم توفيرها من خلال جوهرة paypal-rest-sdk
).
الخطوة 3: لا يزال في createOrder
، نعيد هذا الرمز المميز الذي تم إنشاؤه في النهاية الخلفية لدينا ، وإذا كان كل شيء على ما يرام ، فإن نافذة PayPal المنبثقة ستعرض ما يلي ، وتطلب بيانات اعتماد المستخدم:

الخطوة 4: بعد أن يقوم المستخدم بتسجيل الدخول واختيار طريقة الدفع ، ستغير النافذة المنبثقة حالتها إلى ما يلي:

الخطوة 5: استدعاء وظيفة onApprove
الآن. لقد حددناها على النحو التالي: onApprove: function(data)
. سيحتوي كائن data
على معلومات الدفع من أجل تنفيذه. في رد الاتصال هذا ، سيتم تنفيذ طلب آخر لوظيفة النهاية الخلفية لدينا هذه المرة لتمرير كائن البيانات من أجل تنفيذ طلب PayPal.
الخطوة 6: تقوم الجهة الخلفية لدينا بتنفيذ هذه المعاملة وإرجاع 200 (إذا نجحت).
الخطوة 7: عندما تعود النهاية الخلفية لدينا ، نرسل النموذج. هذا هو الطلب الثالث الذي نتقدم به إلى نهايتنا الخلفية.
لاحظ أنه ، على عكس Stripe ، هناك ثلاثة طلبات تم إجراؤها على نهايتنا الخلفية في هذه العملية. وسوف نحافظ على مزامنة حالة سجل الطلب وفقًا لذلك:
-
createOrder
callback: يتم إنشاء معاملة ، كما يتم إنشاء سجل أمر ؛ لذلك ، فهو في حالة معلقة كافتراضي. - رد
onApprove
: تم تنفيذ المعاملة وسيتم تعيين طلبنا على أنه paypal_executed . - تم إرسال صفحة الطلب: تم تنفيذ المعاملة بالفعل ، لذلك لم يتغير شيء. سجل الطلب سيغير حالته إلى مدفوعة .
يتم وصف هذه العملية برمتها في الرسم البياني التالي:

مدفوعات PayPal
تتبع مدفوعات PayPal نفس منطق رسوم Stripe ، لذا فهي تمثل معاملات لمرة واحدة ، ولكن كما ذكر في القسم السابق ، لها منطق تدفق مختلف. هذه هي التغييرات التي يجب إجراؤها للتعامل مع مدفوعات PayPal:
الخطوة 1: إنشاء طرق جديدة لـ PayPal وتنفيذ المدفوعات.
Add the following routes in config/routes.rb
:
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
This will create two new routes for creating and executing payments which will be handled in the paypal_create_payment
and paypal_execute_payment
orders controller methods.
Step 2: Create the PayPal service.
Add the singleton class Orders::Paypal
at: app/services/orders/paypal.rb
.
This service will initially have three responsibilities:
- The
create_payment
method creates a payment by callingPayPal::SDK::REST::Payment.new
. A token is generated and returned to the front-end. - The
execute_payment
method executes the payment by first finding the previous created payment object throughPayPal::SDK::REST::Payment.find(payment_id)
which uses the payment_id as an argument which has the same value as the charge_id stored in the previous step in the order object. After that, we callexecute
in the payment object with a given payer as the parameter. This payer is given by the front end after the user has provided credentials and selected a payment method in the popup. - The
finish
method finds an order by a specific charge_id querying for recently created orders in the paypal_executed state. If a record is found, it is marked as paid.
class Orders::Paypal def self.finish(charge_id) order = Order.paypal_executed.recently_created.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
Step 3: Call the PayPal service in the controller in the submit action.
Add a callback for prepare_new_order
before the action paypal_create_payment
(which will be added in the next step) is requested by adding the following in the file app/controllers/orders_controller.rb
:
class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment] ...
Again, in the same file, call PayPal service in the submit action by replacing the commented code #PAYPAL WILL BE HANDLED HERE.
كالآتي:
... elsif order_params[:payment_gateway] == "paypal" @order = Orders::Paypal.finish(order_params[:token]) end ...
Step 4: Create the actions for handling requests.
Still, in the app/controllers/orders_controller.rb
file, create two new actions (which should be public) for handling requests to paypal_create_payment
and paypal_execute_payment
routes:
- The
paypal_create_payment
method: Will call our service methodcreate_payment
. If that returns successfully, it will return the order token created byOrders::Paypal.create_payment
. - The
paypal_execute_payment
method: Will call our service methodexecute_payment
(which executes our payments). If the payment is performed successfully, it returns 200.
... 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 ...
Step 5: Implement the front-end callback functions for createOrder
and onApprove
.
Make your paypal.Button.render
call look like this:
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');
As mentioned in the previous section, we call paypal_create_payment_url
for the createOrder
callback and paypal_execute_payment_url
for the onApprove
callback. Notice that if the last request returns success, we submit the order, which is the third request made to the server.
In the createOrder
function handler, we return a token (obtained from the back end). In the onApprove
callback, we have two properties passed down to our back-end paymentID
and payerID
. These will be used in order to execute the payment.
Finally, notice that we have two empty else
clauses as I'm leaving room for the next section where we will be adding PayPal subscriptions.
If you visit your page after integrating the front-end JavaScript section and select PayPal as the payment method, it should look like the following:

Step 6: Test your application.
- قم بزيارة صفحة الفهرس.
- حدد منتج الدفع / الشحن و PayPal كطريقة الدفع.
- انقر فوق الزر إرسال PayPal.
- في نافذة PayPal المنبثقة:
- استخدم بيانات الاعتماد لحساب المشتري الذي أنشأته.
- تسجيل الدخول وتأكيد طلبك.
- يجب إغلاق النافذة المنبثقة.
- تحقق مما إذا تمت إعادة توجيهك إلى صفحة النجاح.
- أخيرًا ، تحقق مما إذا كان قد تم تنفيذ الطلب في حساب PayPal عن طريق تسجيل الدخول باستخدام حساب شركتك على https://www.sandbox.paypal.com/signin والتحقق من لوحة القيادة https://www.sandbox.paypal.com/listing / المعاملات.
اشتراكات PayPal
تتبع خطط / اتفاقيات / اشتراكات PayPal نفس منطق اشتراكات Stripe ، ويتم إنشاؤها للدفعات المتكررة. باستخدام هذا النوع من المنتجات ، يتم تحصيل رسوم من المستخدم يوميًا أو أسبوعيًا أو شهريًا أو سنويًا تلقائيًا وفقًا لتكوينه.
سنستخدم الحقل الخاص بالمنتج paypal_plan_name
، من أجل تخزين معرف الخطة المقدم من PayPal. في هذه الحالة ، بشكل مختلف عن Stripe ، لا نختار المعرف ، ويقوم PayPal بإرجاع هذه القيمة التي سيتم استخدامها لتحديث آخر منتج تم إنشاؤه في قاعدة البيانات الخاصة بنا.
لإنشاء اشتراك ، لا يلزم أي معلومات عن customer
في أي خطوة ، حيث من المحتمل أن تتعامل الطريقة onApprove
مع هذا الارتباط في تنفيذه الأساسي. لذلك ستبقى جداولنا كما هي.
الخطوة 1: قم بإنشاء خطة باستخدام PayPal API.
افتح وحدة التحكم الخاصة بك باستخدام rails c
. قم بإنشاء اشتراك لحساب PayPal الخاص بك باستخدام:
plan = PayPal::SDK::REST::Plan.new({ name: 'Premium Plan', description: 'Premium Plan', type: 'fixed', payment_definitions: [{ name: 'Premium Plan', type: 'REGULAR', frequency_interval: '1', frequency: 'MONTH', cycles: '12', amount: { currency: 'USD', value: '100.00' } }], merchant_preferences: { cancel_url: 'http://localhost:3000/', return_url: 'http://localhost:3000/', max_fail_attempts: '0', auto_bill_amount: 'YES', initial_fail_amount_action: 'CONTINUE' } }) plan.create plan_update = { op: 'replace', path: '/', value: { state: 'ACTIVE' } } plan.update(plan_update)
الخطوة 2: قم بتحديث المنتج الأخير في قاعدة البيانات paypal_plan_name
plan.id
تم إرجاعها.
يركض:
Product.last.update(paypal_plan_name: plan.id)
الخطوة 3: أضف مسارات لاشتراك PayPal.
أضف مسارين جديدين في config/routes.rb
:
post 'orders/paypal/create_subscription' => 'orders#paypal_create_subscription', as: :paypal_create_subscription post 'orders/paypal/execute_subscription' => 'orders#paypal_execute_subscription', as: :paypal_execute_subscription
الخطوة 4: التعامل مع الإنشاء والتنفيذ في خدمة PayPal.
أضف وظيفتين أخريين لإنشاء وتنفيذ الاشتراكات في Orders::Paypal
app/services/orders/paypal.rb
:
def self.create_subscription(order:, product:) agreement = PayPal::SDK::REST::Agreement.new({ name: product.name, description: "Subscription for: #{product.name}", start_date: (Time.now.utc + 1.minute).iso8601, payer: { payment_method: "paypal" }, plan: { id: product.paypal_plan_name } }) if agreement.create order.token = agreement.token return agreement.token if order.save end end def self.execute_subscription(token:) order = Order.recently_created.find_by(token: token) return false unless order agreement = PayPal::SDK::REST::Agreement.new agreement.token = token if agreement.execute order.charge_id = agreement.id order.set_paypal_executed return order.charge_id if order.save end end
في create_subscription
، نقوم بتهيئة اتفاقية عن طريق استدعاء الطريقة PayPal::SDK::REST::Agreement.new
وتمرير product.paypal_plan_name
كأحد سماتها. بعد ذلك ، نقوم بإنشائه ، والآن سيتم تعيين رمز مميز لهذا الكائن الأخير. نعيد أيضًا الرمز المميز إلى الواجهة الأمامية.
في execute_subscription
، نجد سجل order
الذي تم إنشاؤه في الاستدعاء السابق. بعد ذلك ، نقوم بتهيئة اتفاقية جديدة ، ونقوم بتعيين الرمز المميز لهذا الكائن السابق ونقوم بتنفيذه. إذا تم تنفيذ هذه الخطوة الأخيرة بنجاح ، فسيتم تعيين حالة الطلب على paypal_executed . والآن نعود إلى الواجهة الأمامية معرف الاتفاقية الذي تم تخزينه أيضًا order.chager_id
.
الخطوة 5: إضافة إجراءات لإنشاء وتنفيذ الاشتراكات في orders_controller
.
قم بتغيير app/controllers/orders_controller.rb
. في الجزء العلوي من الفصل الدراسي ، أولاً ، ثم قم بتحديث رد الاتصال prepare_new_order
ليتم تنفيذه أيضًا قبل paypal_create_subscription
:
class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment, :paypal_create_subscription]
أيضًا ، في الملف نفسه ، أضف الوظيفتين العامتين بحيث يستدعيان خدمة Orders::Paypal
بتدفق مشابه لما لدينا بالفعل في مدفوعات PayPal:
... def paypal_create_subscription result = Orders::Paypal.create_subscription(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_subscription result = Orders::Paypal.execute_subscription(token: params[:subscriptionToken]) if result render json: { id: result}, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end ...
الخطوة 6: إضافة معالجات الاشتراك لعمليات رد نداء createOrder
و onApprove
في الواجهة الأمامية.
أخيرًا ، في index.html.haml
، استبدل وظيفة paypal.Buttons
بما يلي ، والذي سيملأ الوظيفتين else
اللتين كانت لدينا من قبل:
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 { return $.post("#{paypal_create_subscription_url}", $('#order-details').serialize()).then(function(data) { return data.token; }); } }, onApprove: function(data) { if (isPayment()) { return $.post("#{paypal_execute_payment_url}", { paymentID: data.paymentID, payerID: data.payerID }).then(function() { submitOrderPaypal(data.paymentID) }); } else { return $.post("#{paypal_execute_subscription_url}", { subscriptionToken: data.orderID }).then(function(executeData) { submitOrderPaypal(executeData.id) }); } } }).render('#submit-paypal');
إنشاء وتنفيذ الاشتراكات له نفس المنطق المستخدم في المدفوعات. يتمثل أحد الاختلافات في أنه عند تنفيذ المدفوعات ، فإن البيانات من وظيفة رد الاتصال onApprove
تحتوي بالفعل على charge_id
paymentID
النموذج من خلال submitOrderPaypal(data.paymentID)
. بالنسبة للاشتراكات ، نحصل على charge_id
فقط بعد تنفيذه عن طريق طلب POST
على paypal_execute_subscription_url
، حتى نتمكن من استدعاء submitOrderPaypal(executeData.id)
.
الخطوة 7: اختبر طلبك.
- قم بزيارة صفحة الفهرس.
- حدد منتج اشتراك و PayPal كطريقة دفع.
- انقر فوق الزر إرسال PayPal.
- في نافذة PayPal المنبثقة:
- استخدم بيانات الاعتماد لحساب المشتري الذي أنشأته.
- تسجيل الدخول وتأكيد طلبك.
- يجب إغلاق النافذة المنبثقة.
- تحقق مما إذا تمت إعادة توجيهك إلى صفحة النجاح.
- أخيرًا ، تحقق مما إذا تم تنفيذ الطلب في حساب PayPal عن طريق تسجيل الدخول باستخدام حساب شركتك على https://www.sandbox.paypal.com/signin والتحقق من لوحة القيادة https://www.sandbox.paypal.com/listing/ المعاملات.
خاتمة
بعد قراءة هذه المقالة ، يجب أن تكون قادرًا على دمج المدفوعات / الرسوم بالإضافة إلى معاملات الاشتراكات لـ PayPal و Stripe في تطبيق Rails الخاص بك. هناك الكثير من النقاط التي يمكن تحسينها والتي لم أقم بإضافتها في هذا المقال من أجل الإيجاز. لقد نظمت كل شيء بناءً على افتراض الصعوبة:
- أسهل:
- استخدم بروتوكول أمان طبقة النقل (TLS) حتى تستخدم طلباتك HTTPS.
- تنفيذ تكوينات بيئة الإنتاج لكل من PayPal و Stripe.
- أضف صفحة جديدة حتى يتمكن المستخدمون من الوصول إلى محفوظات الطلبات السابقة.
- متوسط:
- استرداد أو إلغاء الاشتراكات.
- قدّم حلاً لمدفوعات المستخدم غير المسجّل.
- أصعب:
- وفر طريقة لإزالة الحسابات والاحتفاظ برمزها المميز ومعرّف العميل إذا كان المستخدم يرغب في العودة. ولكن بعد فترة معينة من الأيام ، قم بإزالة هذه البيانات بحيث يكون تطبيقك أكثر توافقًا مع PCI.
- انتقل إلى PayPal version 2 API في جانب الخادم (https://developer.paypal.com/docs/api/payments/v2/) الجوهرة التي استخدمناها في هذا البرنامج التعليمي paypal-sdk-rest ، لها إصدار تجريبي فقط للإصدار 2 ، بحيث يمكن استخدام ذلك بحذر (https://github.com/paypal/PayPal-Ruby-SDK/tree/2.0-beta).
- قم بتضمين الطلبات العاطلة.
- الشريط: https://stripe.com/docs/api/idempotent_requests
- باي بال: https://developer.paypal.com/docs/api-basics/#api-idempotency
أوصي أيضًا بقراءة عنصر Stripe Checkout ، وهي طريقة أخرى لدمج Stripe في الواجهة الأمامية. بخلاف Stripe Elements ، التي استخدمناها في هذا البرنامج التعليمي ، يفتح Stripe Checkout نافذة منبثقة بعد النقر على زر (مشابه لـ PayPal) حيث يملأ المستخدم معلومات بطاقة الائتمان أو يختار الدفع باستخدام Google Pay / Apple Pay https://stripe.com / مستندات / ويب.
توصية القراءة الثانية هي صفحات الأمان لكل من بوابات الدفع.
- لشريط
- لـ PayPal
أخيرا ، شكرا لقراءة هذا المقال! يمكنك أيضًا التحقق من مشروع GitHub المستخدم لعينة المشروع هذه. هناك ، أضفت اختبارات rspec أيضًا أثناء التطوير.