دمج طرق الدفع 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 . يجب أن تكون قادرًا على رؤية الصفحة على النحو التالي:

صفحة فهرس أولية بدون تكامل Stripe و PayPal
صفحة فهرس أولية بدون تكامل Stripe و PayPal

تخزين بيانات اعتماد بوابة الدفع

سيتم تخزين مفاتيح 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:

صفحة فهرس متكاملة مع حقول الإدخال الآمنة لـ Stripe.
صفحة فهرس متكاملة مع حقول الإدخال الآمنة لـ 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 .

نافذة PayPal المنبثقة ، حالة التحميل
نافذة PayPal المنبثقة ، حالة التحميل

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

الخطوة 3: لا يزال في createOrder ، نعيد هذا الرمز المميز الذي تم إنشاؤه في النهاية الخلفية لدينا ، وإذا كان كل شيء على ما يرام ، فإن نافذة PayPal المنبثقة ستعرض ما يلي ، وتطلب بيانات اعتماد المستخدم:

نافذة PayPal المنبثقة ، بيانات اعتماد المستخدم
نافذة PayPal المنبثقة ، بيانات اعتماد المستخدم

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

نافذة PayPal المنبثقة ، معاملة مصرح بها
نافذة PayPal المنبثقة ، معاملة مصرح بها

الخطوة 5: استدعاء وظيفة onApprove الآن. لقد حددناها على النحو التالي: onApprove: function(data) . سيحتوي كائن data على معلومات الدفع من أجل تنفيذه. في رد الاتصال هذا ، سيتم تنفيذ طلب آخر لوظيفة النهاية الخلفية لدينا هذه المرة لتمرير كائن البيانات من أجل تنفيذ طلب PayPal.

الخطوة 6: تقوم الجهة الخلفية لدينا بتنفيذ هذه المعاملة وإرجاع 200 (إذا نجحت).

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

لاحظ أنه ، على عكس Stripe ، هناك ثلاثة طلبات تم إجراؤها على نهايتنا الخلفية في هذه العملية. وسوف نحافظ على مزامنة حالة سجل الطلب وفقًا لذلك:

  • createOrder callback: يتم إنشاء معاملة ، كما يتم إنشاء سجل أمر ؛ لذلك ، فهو في حالة معلقة كافتراضي.
  • رد onApprove : تم تنفيذ المعاملة وسيتم تعيين طلبنا على أنه paypal_executed .
  • تم إرسال صفحة الطلب: تم ​​تنفيذ المعاملة بالفعل ، لذلك لم يتغير شيء. سجل الطلب سيغير حالته إلى مدفوعة .

يتم وصف هذه العملية برمتها في الرسم البياني التالي:

معاملات PayPal
معاملات PayPal

مدفوعات 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 calling PayPal::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 through PayPal::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 call execute 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 method create_payment . If that returns successfully, it will return the order token created by Orders::Paypal.create_payment .
  • The paypal_execute_payment method: Will call our service method execute_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:

Index page after integration with PayPal
Index page after integration with PayPal

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 أيضًا أثناء التطوير.