├── spec ├── test_app │ ├── log │ │ └── .keep │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── public │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── application_controller.rb │ │ │ └── pages_controller.rb │ │ ├── helpers │ │ │ └── application_helper.rb │ │ └── views │ │ │ ├── pages │ │ │ ├── done.html.erb │ │ │ └── credit_card_payment_button.html.erb │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── package.json │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ ├── rails │ │ ├── yarn │ │ ├── update │ │ └── setup │ ├── config │ │ ├── spring.rb │ │ ├── environment.rb │ │ ├── initializers │ │ │ ├── mime_types.rb │ │ │ ├── application_controller_renderer.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── assets.rb │ │ │ ├── inflections.rb │ │ │ └── spgateway.rb │ │ ├── cable.yml │ │ ├── routes.rb │ │ ├── boot.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── application.rb │ │ ├── secrets.yml │ │ ├── environments │ │ │ ├── test.rb │ │ │ ├── development.rb │ │ │ └── production.rb │ │ └── puma.rb │ ├── config.ru │ └── Rakefile ├── lib │ ├── spgateway_spec.rb │ └── spgateway │ │ ├── sha256_spec.rb │ │ └── mpg_form_spec.rb ├── integration_spec_helper.rb ├── support │ └── wait_for.rb ├── features │ └── credit_card_payment_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── .rspec ├── lib ├── spgateway │ ├── version.rb │ ├── rails.rb │ ├── rails │ │ └── engine.rb │ ├── attr_key_helper.rb │ ├── engine.rb │ ├── sha256.rb │ ├── config.rb │ ├── response.rb │ └── mpg_form.rb ├── tasks │ └── spgateway │ │ └── rails_tasks.rake ├── spgateway.rb └── generators │ └── spgateway │ ├── install_generator.rb │ └── templates │ └── spgateway_initializer.rb ├── Appraisals ├── config ├── initializers │ └── inflections.rb └── routes.rb ├── gemfiles ├── rails_4.gemfile ├── rails_5.gemfile ├── rails_4.gemfile.lock └── rails_5.gemfile.lock ├── .gitignore ├── app ├── jobs │ └── spgateway │ │ └── rails │ │ └── application_job.rb ├── controllers │ └── spgateway │ │ ├── notify_callbacks_controller.rb │ │ ├── mpg_callbacks_controller.rb │ │ ├── payment_code_callbacks_controller.rb │ │ └── application_controller.rb └── helpers │ └── spgateway │ └── application_helper.rb ├── .rubocop.yml ├── Rakefile ├── Gemfile ├── .travis.yml ├── bin └── rails ├── spgateway-rails.gemspec ├── MIT-LICENSE ├── .rubocop_todo.yml ├── Gemfile.lock └── README.md /spec/test_app/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/test_app/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/test_app/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/test_app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/test_app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/test_app/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /spec/test_app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /lib/spgateway/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | VERSION = '0.1.0' 4 | end 5 | -------------------------------------------------------------------------------- /spec/test_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_app", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /spec/test_app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rails-4" do 2 | gem "rails", "4.2" 3 | end 4 | 5 | appraise "rails-5" do 6 | gem "rails", "5.1" 7 | end 8 | -------------------------------------------------------------------------------- /lib/spgateway/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spgateway' 3 | 4 | module Spgateway 5 | module Rails 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | ActiveSupport::Inflector.inflections(:en) do |inflect| 3 | inflect.acronym 'MPG' 4 | end 5 | -------------------------------------------------------------------------------- /gemfiles/rails_4.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "4.2" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_5.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "5.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /spec/test_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/test_app/config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /spec/test_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/test_app/db/*.sqlite3 5 | spec/test_app/db/*.sqlite3-journal 6 | spec/test_app/log/*.log 7 | spec/test_app/tmp/ 8 | coverage/* 9 | -------------------------------------------------------------------------------- /lib/tasks/spgateway/rails_tasks.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # desc "Explaining what the task does" 3 | # task :spgateway_rails do 4 | # # Task goes here 5 | # end 6 | -------------------------------------------------------------------------------- /spec/test_app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/test_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /app/jobs/spgateway/rails/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | module Rails 4 | class ApplicationJob < ActiveJob::Base 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/test_app/app/views/pages/done.html.erb: -------------------------------------------------------------------------------- 1 |

DONE

2 | 3 | <% if @data %> 4 |

5 | Data: 6 |

7 |
 8 | <%= @data %>
 9 |   
10 | <% end %> 11 | -------------------------------------------------------------------------------- /lib/spgateway/rails/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spgateway/engine' 3 | 4 | module Spgateway 5 | module Rails 6 | class Engine < Spgateway::Engine 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/test_app/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: test_app_production 11 | -------------------------------------------------------------------------------- /spec/test_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | mount Spgateway::Engine => '/spgateway' 3 | 4 | get 'pages/credit_card_payment_button', to: 'pages#credit_card_payment_button' 5 | get 'pages/done', to: 'pages#done' 6 | end 7 | -------------------------------------------------------------------------------- /spec/test_app/app/views/pages/credit_card_payment_button.html.erb: -------------------------------------------------------------------------------- 1 | <%= spgateway_pay_button 'Go pay', payment_methods: [:credit_card], order_number: SecureRandom.hex(10), item_description: 'Test order', amount: '1000', payer_email: 'test@example.com' %> 2 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /lib/spgateway/attr_key_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | module AttrKeyHelper 4 | def convert_to_attr_key(term) 5 | term.to_s.gsub(/(^|_)(url|id|[a-z0-9])/) { Regexp.last_match[2].upcase } 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/test_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/test_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Spgateway::Engine.routes.draw do 3 | post 'mpg_callbacks', to: 'mpg_callbacks#proceed' 4 | post 'payment_code_callbacks', to: 'payment_code_callbacks#proceed' 5 | post 'notify_callbacks', to: 'notify_callbacks#proceed' 6 | end 7 | -------------------------------------------------------------------------------- /lib/spgateway/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | class Engine < ::Rails::Engine 4 | isolate_namespace Spgateway 5 | 6 | config.to_prepare do 7 | ::ApplicationController.helper(Spgateway::Engine.helpers) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/test_app/app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def credit_card_payment_button 3 | end 4 | 5 | def done 6 | if params[:data] 7 | @data = JSON.pretty_generate(JSON.parse(params[:data])) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/spgateway_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | require 'spgateway/version' 4 | 5 | RSpec.describe Spgateway do 6 | describe '::VERSION' do 7 | it 'is a string' do 8 | expect(Spgateway::VERSION).to be_a_kind_of(String) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | TargetRubyVersion: 2.4 4 | Include: 5 | - Rakefile 6 | - lib/**/*.rake 7 | Exclude: 8 | - Gemfile 9 | - Appraisals 10 | - spgateway-rails.gemspec 11 | - lib/generators/**/* 12 | - spec/test_app/**/* 13 | Rails: 14 | Enabled: true 15 | inherit_from: .rubocop_todo.yml 16 | -------------------------------------------------------------------------------- /spec/test_app/bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/test_app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestApp 5 | <%= csrf_meta_tags %> 6 | 7 | 8 | 9 | <% if flash.any? %> 10 | <% flash.each do |type, message| %> 11 | <%= "#{type}: #{message}" %> 12 | <% end %> 13 | <% end %> 14 | 15 | <%= yield %> 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/spgateway.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spgateway/version' 3 | require 'spgateway/config' 4 | require 'spgateway/engine' 5 | require 'spgateway/sha256' 6 | require 'spgateway/response' 7 | require 'spgateway/mpg_form' 8 | 9 | module Spgateway 10 | def self.configure 11 | yield config 12 | end 13 | 14 | def self.config 15 | @config ||= Config.new 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /lib/spgateway/sha256.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | module SHA256 4 | def self.hash( 5 | data, 6 | hash_key: Spgateway.config.hash_key, 7 | hash_iv: Spgateway.config.hash_iv, 8 | hash_iv_first: false 9 | ) 10 | if hash_iv_first 11 | Digest::SHA256.hexdigest("HashIV=#{hash_iv}&#{data}&HashKey=#{hash_key}").upcase 12 | else 13 | Digest::SHA256.hexdigest("HashKey=#{hash_key}&#{data}&HashIV=#{hash_iv}").upcase 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | 8 | require 'rdoc/task' 9 | 10 | RDoc::Task.new(:rdoc) do |rdoc| 11 | rdoc.rdoc_dir = 'rdoc' 12 | rdoc.title = 'Spgateway::Rails' 13 | rdoc.options << '--line-numbers' 14 | rdoc.rdoc_files.include('README.md') 15 | rdoc.rdoc_files.include('lib/**/*.rb') 16 | end 17 | 18 | load 'rails/tasks/statistics.rake' 19 | 20 | require 'bundler/gem_tasks' 21 | -------------------------------------------------------------------------------- /app/controllers/spgateway/notify_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_dependency 'spgateway/application_controller' 3 | 4 | module Spgateway 5 | class NotifyCallbacksController < ApplicationController 6 | def proceed 7 | raise NotImplementedError, 'Spgateway.config.notify_callback is not a proc.' unless Spgateway.config.notify_callback.is_a? Proc 8 | instance_exec(spgateway_response, self, ::Rails.application.routes.url_helpers, &Spgateway.config.notify_callback) 9 | head 200 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/integration_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../test_app/config/environment', __FILE__) 4 | abort('The Rails environment is running in production mode!') if Rails.env.production? 5 | require 'rails_helper' 6 | require 'capybara/rspec' 7 | require 'capybara/poltergeist' 8 | require 'ngrok/rspec' 9 | Capybara.server_port = 3003 10 | Capybara.javascript_driver = :poltergeist 11 | Ngrok::Rspec.tunnel = { port: Capybara.server_port } 12 | RSpec.configure { |config| config.include Ngrok::Rspec } 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Declare your gem's dependencies in spgateway-rails.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # To use a debugger 14 | # gem 'byebug', group: [:development, :test] 15 | -------------------------------------------------------------------------------- /app/controllers/spgateway/mpg_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_dependency 'spgateway/application_controller' 3 | 4 | module Spgateway 5 | class MPGCallbacksController < ApplicationController 6 | skip_before_action :verify_authenticity_token 7 | 8 | def proceed 9 | raise NotImplementedError, 'Spgateway.config.mpg_callback is not a proc.' unless Spgateway.config.mpg_callback.is_a? Proc 10 | instance_exec(spgateway_response, self, ::Rails.application.routes.url_helpers, &Spgateway.config.mpg_callback) 11 | redirect_to '/' unless performed? 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 2.2.0 5 | - 2.3.0 6 | - 2.4.0 7 | gemfile: 8 | - gemfiles/rails_4.gemfile 9 | - gemfiles/rails_5.gemfile 10 | matrix: 11 | exclude: 12 | - rvm: 2.2.0 13 | gemfile: gemfiles/rails_5.gemfile 14 | - rvm: 2.3.0 15 | gemfile: gemfiles/rails_5.gemfile 16 | - rvm: 2.4.0 17 | gemfile: gemfiles/rails_4.gemfile 18 | before_install: 19 | - "gem update bundler" 20 | - "mkdir -p ~/bin" 21 | - "cd ~/bin" 22 | - "curl https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip -o ngrok.zip" 23 | - "unzip ngrok.zip" 24 | - "cd -" 25 | script: "bundle exec rspec spec" 26 | -------------------------------------------------------------------------------- /spec/support/wait_for.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module WaitFor 3 | def wait_for_ajax(max_wait_time: Capybara.default_max_wait_time) 4 | Timeout.timeout(max_wait_time) do 5 | loop until finished_all_ajax_requests? 6 | end 7 | end 8 | 9 | def wait_for_content(text, max_wait_time: Capybara.default_max_wait_time) 10 | Timeout.timeout(max_wait_time) do 11 | loop until page.has_content? text 12 | end 13 | end 14 | 15 | def finished_all_ajax_requests? 16 | page.evaluate_script('jQuery.active').zero? 17 | end 18 | end 19 | 20 | RSpec.configure do |config| 21 | config.include WaitFor, type: :feature 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/spgateway/payment_code_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_dependency 'spgateway/application_controller' 3 | 4 | module Spgateway 5 | class PaymentCodeCallbacksController < ApplicationController 6 | skip_before_action :verify_authenticity_token 7 | 8 | def proceed 9 | raise NotImplementedError, 'Spgateway.config.payment_code_callback is not a proc.' unless Spgateway.config.payment_code_callback.is_a? Proc 10 | instance_exec(spgateway_response, self, ::Rails.application.routes.url_helpers, &Spgateway.config.payment_code_callback) 11 | redirect_to '/' unless performed? 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/spgateway/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'rails/generators/base' 3 | 4 | module Spgateway 5 | module Generators 6 | class InstallGenerator < ::Rails::Generators::Base 7 | source_root File.expand_path('../templates', __FILE__) 8 | 9 | desc 'Creates the Spgateway initializer and mounts Spgateway::Rails::Engine.' 10 | 11 | def copy_initializer_file 12 | template 'spgateway_initializer.rb', ::Rails.root.join('config', 'initializers', 'spgateway.rb') 13 | end 14 | 15 | def mount_engine 16 | route "mount Spgateway::Engine => '/spgateway'" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # This command will automatically be run when you run "rails" with Rails gems 4 | # installed from the root of your application. 5 | 6 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 7 | ENGINE_PATH = File.expand_path('../../lib/spgateway/rails/engine', __FILE__) 8 | APP_PATH = File.expand_path('../../spec/test_app/config/application', __FILE__) 9 | 10 | # Set up gems listed in the Gemfile. 11 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 12 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 13 | 14 | require 'action_controller/railtie' 15 | require 'action_mailer/railtie' 16 | require 'active_job/railtie' 17 | 18 | require 'rails/engine/commands' 19 | -------------------------------------------------------------------------------- /spec/test_app/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Removing old logs and tempfiles ==" 22 | system! 'bin/rails log:clear tmp:clear' 23 | 24 | puts "\n== Restarting application server ==" 25 | system! 'bin/rails restart' 26 | end 27 | -------------------------------------------------------------------------------- /spec/test_app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | puts "\n== Removing old logs and tempfiles ==" 26 | system! 'bin/rails log:clear tmp:clear' 27 | 28 | puts "\n== Restarting application server ==" 29 | system! 'bin/rails restart' 30 | end 31 | -------------------------------------------------------------------------------- /lib/spgateway/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | class Config 4 | include ActiveSupport::Configurable 5 | config_accessor :merchant_id 6 | config_accessor :hash_iv, :hash_key 7 | config_accessor :api_url, :mpg_gateway_url 8 | config_accessor :mpg_callback, :notify_callback, :payment_code_callback 9 | 10 | def mpg_callback(&block) 11 | if block 12 | config.mpg_callback = block 13 | else 14 | config.mpg_callback 15 | end 16 | end 17 | 18 | def notify_callback(&block) 19 | if block 20 | config.notify_callback = block 21 | else 22 | config.notify_callback 23 | end 24 | end 25 | 26 | def payment_code_callback(&block) 27 | if block 28 | config.payment_code_callback = block 29 | else 30 | config.payment_code_callback 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/spgateway.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Spgateway.configure do |config| 3 | config.merchant_id = 'MS11591073' 4 | config.hash_key = '7KZsaNdslfbViAsPIz2gaEw4CjIHwdXE' 5 | config.hash_iv = 'XX67KkA6zM2SmuH4' 6 | 7 | config.api_url = 'https://ccore.spgateway.com/API' 8 | config.mpg_gateway_url = 'https://ccore.spgateway.com/MPG/mpg_gateway' 9 | 10 | config.mpg_callback do |spgateway_response| 11 | # Pass the response into a mocked module so we can inspect it in the test 12 | MPGCallbackTesting.response(spgateway_response) 13 | 14 | flash[:notice] = "Payment has been proceeded." 15 | 16 | redirect_to pages_done_path( 17 | data: { 18 | spgateway_response: spgateway_response 19 | }.to_json 20 | ) 21 | end 22 | 23 | config.notify_callback do |spgateway_response| 24 | # Pass the response into a mocked module so we can inspect it in the test 25 | NotifyCallbackTesting.response(spgateway_response) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/test_app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /spec/test_app/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | # Pick the frameworks you want: 4 | # require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_view/railtie" 7 | # require "action_mailer/railtie" 8 | require "active_job/railtie" 9 | # require "action_cable/engine" 10 | # require "rails/test_unit/railtie" 11 | require "sprockets/railtie" 12 | 13 | Bundler.require(*Rails.groups) 14 | require "spgateway/rails" 15 | 16 | module TestApp 17 | class Application < Rails::Application 18 | puts "Ruby Version: #{RUBY_VERSION}" 19 | puts "Rails Version: #{Rails::VERSION::STRING}" 20 | 21 | # Initialize configuration defaults for originally generated Rails version. 22 | # config.load_defaults 5.1 23 | 24 | # Settings in config/environments/* take precedence over those specified here. 25 | # Application configuration should go into files in config/initializers 26 | # -- all .rb files in that directory are automatically loaded. 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /app/controllers/spgateway/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | class ApplicationController < ActionController::Base 4 | protect_from_forgery with: :null_session 5 | 6 | private 7 | 8 | def spgateway_response 9 | return @spgateway_response if @spgateway_response 10 | return nil unless params[:JSONData] 11 | @spgateway_response = Spgateway::Response.new(params[:JSONData]) 12 | raise InvalidResponseError unless @spgateway_response.result.valid? 13 | @spgateway_response 14 | end 15 | 16 | class InvalidResponseError < StandardError 17 | end 18 | 19 | def respond_to_missing?(method) 20 | super || ::Rails.application.routes.url_helpers.respond_to?(method) 21 | end 22 | 23 | def method_missing(method, *args) 24 | if ::Rails.application.routes.url_helpers.respond_to?(method) 25 | ::Rails.application.routes.url_helpers.try(method, *args) 26 | else 27 | super 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/lib/spgateway/sha256_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | require 'spgateway/sha256' 4 | 5 | RSpec.describe Spgateway::SHA256 do 6 | describe '.hash' do 7 | it 'work as expected' do 8 | data = 'Amt=200&MerchantID=123456&MerchantOrderNo=20140901001&TimeStamp=1403243286&Version=1.1' 9 | hash_key = '1A3S21DAS3D1AS65D1' 10 | hash_iv = '1AS56D1AS24D' 11 | hash = Spgateway::SHA256.hash(data, hash_key: hash_key, hash_iv: hash_iv) 12 | 13 | expect(hash).to eq('841F57D750FB4B04B62DDC3ECDC26F1F4028410927DD28BD5B2E34791CC434D2') 14 | end 15 | 16 | it 'work as expected with hash_iv_first: true' do 17 | data = 'Amt=100&MerchantID=1422967&MerchantOrderNo=840f022&TradeNo=14061313541640927' 18 | hash_key = 'abcdefg' 19 | hash_iv = '1234567' 20 | hash = Spgateway::SHA256.hash(data, hash_key: hash_key, hash_iv: hash_iv, hash_iv_first: true) 21 | 22 | expect(hash).to eq('62C687AF6409E46E79769FAF54F54FE7E75AAE50BAF0767752A5C337670B8EDB') 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spgateway-rails.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "spgateway/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "spgateway-rails" 9 | s.version = Spgateway::VERSION 10 | s.authors = ["zetavg"] 11 | s.email = ["mail@zeta.vg"] 12 | # s.homepage = "TODO" 13 | s.summary = "API wrapper for www.spgateway.com." 14 | # s.description = "TODO: Description of Spgateway." 15 | s.license = "MIT" 16 | 17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 18 | 19 | s.add_dependency "rails", ">= 4.0", "< 5.2" 20 | 21 | s.add_development_dependency "rspec-rails" 22 | s.add_development_dependency "capybara" 23 | s.add_development_dependency "poltergeist" 24 | s.add_development_dependency "appraisal" 25 | s.add_development_dependency "ngrok-rspec" 26 | s.add_development_dependency "simplecov" 27 | s.add_development_dependency "coveralls" 28 | end 29 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 zetavg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spec/lib/spgateway/mpg_form_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | require 'spgateway/mpg_form' 4 | 5 | RSpec.describe Spgateway::MPGForm do 6 | describe 'initialize' do 7 | it 'raise an error if the parameter is not a Hash' do 8 | expect { Spgateway::MPGForm.new('hi') }.to raise_error(ArgumentError, /must pass a hash/) 9 | end 10 | 11 | it 'raise an error if required parameter is missing' do 12 | expect { Spgateway::MPGForm.new(MerchantID: 'abc', MerchantOrderNo: 1, ItemDesc: 2) }.to raise_error(ArgumentError, /'Amt' is missing/) 13 | end 14 | end 15 | 16 | describe '#*' do 17 | it 'sets or gets the attrs' do 18 | form = Spgateway::MPGForm.new(MerchantID: 'abc', MerchantOrderNo: 1, ItemDesc: 2, Amt: 123) 19 | form.Abc = 1 20 | expect(form.Abc).to eq(1) 21 | end 22 | end 23 | 24 | describe '#set_attr' do 25 | it 'sets the attr for the form' do 26 | form = Spgateway::MPGForm.new(MerchantID: 'abc', MerchantOrderNo: 1, ItemDesc: 2, Amt: 123) 27 | form.set_attr('Abc', 1) 28 | expect(form.Abc).to eq(1) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/test_app/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: f3363516e3053311e9267b3c5501d1e751a2b1c2be34d2b07ebfaf072246b04bd9544bf7e52990f79ae54bdfb2337a096ba244b6fbc147572b0c70f7a639164d 22 | 23 | test: 24 | secret_key_base: 3a8eb88e83fd7479c3e92cc90d40d9d683e8655c86fcf73c477a6a26f87d9c30132de070a2caf1c32469cbad493f89f4bf8df4650c2c06a6b1ab43bb6ddd5dc6 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2017-05-12 13:23:20 +0800 using RuboCop version 0.46.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 3 10 | Metrics/AbcSize: 11 | Max: 34 12 | 13 | # Offense count: 1 14 | Metrics/CyclomaticComplexity: 15 | Max: 8 16 | 17 | # Offense count: 20 18 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 19 | # URISchemes: http, https 20 | Metrics/LineLength: 21 | Max: 182 22 | 23 | # Offense count: 3 24 | # Configuration parameters: CountComments. 25 | Metrics/MethodLength: 26 | Max: 29 27 | 28 | # Offense count: 1 29 | Metrics/PerceivedComplexity: 30 | Max: 8 31 | 32 | # Offense count: 1 33 | # Cop supports --auto-correct. 34 | Style/BlockComments: 35 | Exclude: 36 | - 'spec/spec_helper.rb' 37 | 38 | # Offense count: 11 39 | Style/Documentation: 40 | Exclude: 41 | - 'spec/**/*' 42 | - 'test/**/*' 43 | - 'app/controllers/spgateway/application_controller.rb' 44 | - 'app/controllers/spgateway/mpg_callbacks_controller.rb' 45 | - 'app/helpers/spgateway/application_helper.rb' 46 | - 'lib/spgateway.rb' 47 | - 'lib/spgateway/config.rb' 48 | - 'lib/spgateway/engine.rb' 49 | - 'lib/spgateway/mpg_form.rb' 50 | - 'lib/spgateway/rails.rb' 51 | - 'lib/spgateway/response.rb' 52 | - 'lib/spgateway/sha256.rb' 53 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | # config.public_file_server.enabled = true 17 | # config.public_file_server.headers = { 18 | # 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | # } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Print deprecation notices to the stderr. 32 | config.active_support.deprecation = :stderr 33 | 34 | # Raises error for missing translations 35 | # config.action_view.raise_on_missing_translations = true 36 | end 37 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Print deprecation notices to the Rails logger. 30 | config.active_support.deprecation = :log 31 | 32 | # Debug mode disables concatenation and preprocessing of assets. 33 | # This option may cause significant delays in view rendering with a large 34 | # number of complex assets. 35 | config.assets.debug = true 36 | 37 | # Suppress logger output for asset requests. 38 | config.assets.quiet = true 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | 43 | # Use an evented file watcher to asynchronously detect changes in source code, 44 | # routes, locales, etc. This feature depends on the listen gem. 45 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker 46 | end 47 | -------------------------------------------------------------------------------- /spec/test_app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/test_app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/test_app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/spgateway/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spgateway/attr_key_helper' 3 | 4 | module Spgateway 5 | class Response 6 | attr_reader :status 7 | attr_reader :message 8 | attr_reader :result 9 | 10 | def initialize(data) 11 | data = JSON.parse(data) if data.is_a? String 12 | 13 | @status = data['Status'] 14 | @message = data['Message'] 15 | 16 | result = data['Result'] 17 | 18 | result = JSON.parse(result) if result.is_a? String 19 | 20 | @result = Result.new(result) 21 | end 22 | 23 | class Result 24 | include AttrKeyHelper 25 | 26 | def initialize(data) 27 | @data = data 28 | end 29 | 30 | def valid? 31 | try(:check_value) == expected_check_value || 32 | try(:check_code) == expected_check_code 33 | end 34 | 35 | def expected_check_value 36 | data = "Amt=#{@data['Amt']}&MerchantID=#{@data['MerchantID']}&MerchantOrderNo=#{@data['MerchantOrderNo']}&TimeStamp=#{@data['TimeStamp']}&Version=#{@data['Version']}" 37 | Spgateway::SHA256.hash(data) 38 | end 39 | 40 | def expected_check_code 41 | data = "Amt=#{@data['Amt']}&MerchantID=#{@data['MerchantID']}&MerchantOrderNo=#{@data['MerchantOrderNo']}&TradeNo=#{@data['TradeNo']}" 42 | Spgateway::SHA256.hash(data, hash_iv_first: true) 43 | end 44 | 45 | private 46 | 47 | def method_missing(method_name, *args) 48 | data_key = data_key_for(method_name) 49 | if data_key 50 | @data[data_key] 51 | else 52 | super 53 | end 54 | end 55 | 56 | def respond_to_missing?(method_name, _include_private = false) 57 | !data_key_for(method_name).nil? 58 | end 59 | 60 | def data_key_for(name) 61 | possible_data_keys_for(name).find { |k| @data.key?(k) } 62 | end 63 | 64 | def possible_data_keys_for(key) 65 | [key.to_s, convert_to_attr_key(key)] 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/features/credit_card_payment_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'integration_spec_helper' 3 | 4 | RSpec.describe 'credit card payment', type: :feature, js: true, ngrok: true do 5 | before do 6 | NotifyCallbackTesting = double 7 | allow(NotifyCallbackTesting).to receive(:response) { |response| @notify_callback_response = response } 8 | end 9 | before do 10 | MPGCallbackTesting = double 11 | allow(MPGCallbackTesting).to receive(:response) { |response| @mpg_callback_response = response } 12 | end 13 | 14 | scenario 'User pays with credit card' do 15 | page.driver.browser.js_errors = false 16 | 17 | visit pages_credit_card_payment_button_path 18 | click_button 'Go pay' 19 | 20 | expect(find(:css, '#payer_mail').value).to eq('test@example.com') 21 | 22 | fill_in 'card_1', with: '4000' 23 | fill_in 'card_2', with: '2211' 24 | fill_in 'card_3', with: '1111' 25 | fill_in 'card_4', with: '1111' 26 | 27 | select '01', from: 'expire_m' 28 | select '32', from: 'expire_y' 29 | 30 | fill_in 'cvc', with: '111' 31 | 32 | check 'confirm_order' 33 | 34 | find(:css, '.put_send_btn .btn').click 35 | 36 | wait_for_ajax 37 | 38 | wait_for_content 'DONE', max_wait_time: 10 39 | 40 | expect(@notify_callback_response.status).to eq('SUCCESS') 41 | expect(@notify_callback_response.result.Card6No).to eq('400022') 42 | expect(@notify_callback_response.result.Card4No).to eq('1111') 43 | expect(@notify_callback_response.result.Exp).to eq('3201') 44 | 45 | expect(@mpg_callback_response.status).to eq('SUCCESS') 46 | expect(@mpg_callback_response.result.Card6No).to eq('400022') 47 | expect(@mpg_callback_response.result.Card4No).to eq('1111') 48 | expect(@mpg_callback_response.result.Exp).to eq('3201') 49 | 50 | expect(page).to have_content('Payment has been proceeded.') 51 | 52 | data = JSON.parse(find(:css, '#data').text) 53 | 54 | expect(data['spgateway_response']['status']).to eq('SUCCESS') 55 | expect(data['spgateway_response']['result']['data']['Card6No']).to eq('400022') 56 | expect(data['spgateway_response']['result']['data']['Card4No']).to eq('1111') 57 | expect(data['spgateway_response']['result']['data']['Exp']).to eq('3201') 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/test_app/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /lib/spgateway/mpg_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spgateway/attr_key_helper' 3 | 4 | module Spgateway 5 | class MPGForm 6 | include AttrKeyHelper 7 | 8 | REQUIRED_ATTRS = %w(Version MerchantID MerchantOrderNo ItemDesc Amt TimeStamp RespondType).freeze 9 | 10 | def initialize(attrs) 11 | unless attrs.is_a? Hash 12 | raise ArgumentError, "When initializing #{self.class.name}, you must pass a hash as an argument." 13 | end 14 | 15 | @attrs = {} 16 | missing_attrs = REQUIRED_ATTRS.map(&:clone) 17 | 18 | attrs.each_pair do |k, v| 19 | key = k.to_s 20 | value = v.to_s 21 | missing_attrs.delete(key) 22 | @attrs[key] = value 23 | end 24 | 25 | if @attrs['Version'].nil? 26 | @attrs['Version'] = '1.2' 27 | missing_attrs.delete('Version') 28 | end 29 | 30 | if @attrs['MerchantID'].nil? 31 | @attrs['MerchantID'] = Spgateway.config.merchant_id 32 | missing_attrs.delete('MerchantID') 33 | end 34 | 35 | if @attrs['TimeStamp'].nil? 36 | @attrs['TimeStamp'] = Time.now.to_i 37 | missing_attrs.delete('TimeStamp') 38 | end 39 | 40 | if @attrs['RespondType'].nil? 41 | @attrs['RespondType'] = 'JSON' 42 | missing_attrs.delete('RespondType') 43 | end 44 | 45 | return self if missing_attrs.count.zero? 46 | raise ArgumentError, "The required attrs: #{missing_attrs.map { |s| "'#{s}'" }.join(', ')} #{missing_attrs.count > 1 ? 'are' : 'is'} missing." 47 | end 48 | 49 | def set_attr(name, value) 50 | @attrs[name] = value 51 | end 52 | 53 | def sorted_attrs 54 | @attrs.sort 55 | end 56 | 57 | def to_s 58 | sorted_attrs.map { |k, v| "#{k}=#{v}" }.join('&') 59 | end 60 | 61 | def check_value 62 | data = "Amt=#{@attrs['Amt']}&MerchantID=#{@attrs['MerchantID']}&MerchantOrderNo=#{@attrs['MerchantOrderNo']}&TimeStamp=#{@attrs['TimeStamp']}&Version=#{@attrs['Version']}" 63 | Spgateway::SHA256.hash(data) 64 | end 65 | 66 | alias CheckValue check_value 67 | 68 | private 69 | 70 | def method_missing(method_name, *args) 71 | attr_key = convert_to_attr_key(method_name) 72 | 73 | if @attrs.key?(attr_key) 74 | @attrs[attr_key] 75 | elsif attr_key.end_with?('=') && args[0] 76 | @attrs[attr_key.chomp('=')] = args[0] 77 | else 78 | super 79 | end 80 | end 81 | 82 | def respond_to_missing?(method_name, _include_private = false) 83 | attr_key = convert_to_attr_key(method_name) 84 | 85 | attr_key.end_with?('=') || @attrs.key?(attr_key) || sup 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # This file is copied to spec/ when you run 'rails generate rspec:install' 3 | require 'spec_helper' 4 | ENV['RAILS_ENV'] ||= 'test' 5 | # require File.expand_path('../../config/environment', __FILE__) 6 | # Prevent database truncation if the environment is production 7 | abort('The Rails environment is running in production mode!') if Rails.env.production? 8 | require 'rspec/rails' 9 | # Add additional requires below this line. Rails is not loaded until this point! 10 | 11 | # Requires supporting ruby files with custom matchers and macros, etc, in 12 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 13 | # run as spec files by default. This means that files in spec/support that end 14 | # in _spec.rb will both be required and run as specs, causing the specs to be 15 | # run twice. It is recommended that you do not name files matching this glob to 16 | # end with _spec.rb. You can configure this pattern with the --pattern 17 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 18 | # 19 | # The following line is provided for convenience purposes. It has the downside 20 | # of increasing the boot-up time by auto-requiring all files in the support 21 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 22 | # require only the support files necessary. 23 | # 24 | # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 25 | 26 | # Checks for pending migration and applies them before tests are run. 27 | # If you are not using ActiveRecord, you can remove this line. 28 | # ActiveRecord::Migration.maintain_test_schema! 29 | 30 | RSpec.configure do |config| 31 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 32 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 33 | 34 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 35 | # examples within a transaction, remove the following line or assign false 36 | # instead of true. 37 | config.use_transactional_fixtures = true 38 | 39 | # RSpec Rails can automatically mix in different behaviours to your tests 40 | # based on their file location, for example enabling you to call `get` and 41 | # `post` in specs under `spec/controllers`. 42 | # 43 | # You can disable this behaviour by removing the line below, and instead 44 | # explicitly tag your specs with their type, e.g.: 45 | # 46 | # RSpec.describe UsersController, :type => :controller do 47 | # # ... 48 | # end 49 | # 50 | # The different available types are documented in the features, such as in 51 | # https://relishapp.com/rspec/rspec-rails/docs 52 | config.infer_spec_type_from_file_location! 53 | 54 | # Filter lines from Rails gems in backtraces. 55 | config.filter_rails_from_backtrace! 56 | # arbitrary gems may also be filtered via: 57 | # config.filter_gems_from_backtrace("gem name") 58 | end 59 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`. 18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or 19 | # `config/secrets.yml.key`. 20 | config.read_encrypted_secrets = true 21 | 22 | # Disable serving static files from the `/public` folder by default since 23 | # Apache or NGINX already handles this. 24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 25 | 26 | # Compress CSS. 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.action_controller.asset_host = 'http://assets.example.com' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 40 | 41 | # Mount Action Cable outside main process or domain 42 | # config.action_cable.mount_path = nil 43 | # config.action_cable.url = 'wss://example.com/cable' 44 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 45 | 46 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 47 | # config.force_ssl = true 48 | 49 | # Use the lowest log level to ensure availability of diagnostic information 50 | # when problems arise. 51 | config.log_level = :debug 52 | 53 | # Prepend all log lines with the following tags. 54 | config.log_tags = [ :request_id ] 55 | 56 | # Use a different cache store in production. 57 | # config.cache_store = :mem_cache_store 58 | 59 | # Use a real queuing backend for Active Job (and separate queues per environment) 60 | # config.active_job.queue_adapter = :resque 61 | # config.active_job.queue_name_prefix = "test_app_#{Rails.env}" 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Use default logging formatter so that PID and timestamp are not suppressed. 71 | config.log_formatter = ::Logger::Formatter.new 72 | 73 | # Use a different logger for distributed setups. 74 | # require 'syslog/logger' 75 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 76 | 77 | if ENV["RAILS_LOG_TO_STDOUT"].present? 78 | logger = ActiveSupport::Logger.new(STDOUT) 79 | logger.formatter = config.log_formatter 80 | config.logger = ActiveSupport::TaggedLogging.new(logger) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /app/helpers/spgateway/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Spgateway 3 | module ApplicationHelper 4 | def spgateway_mpg_form_for( 5 | mpg_form_object, 6 | submit: 'Go', 7 | submit_class: '', 8 | mpg_gateway_url: Spgateway.config.mpg_gateway_url 9 | ) 10 | unless mpg_form_object.is_a? Spgateway::MPGForm 11 | raise ArgumentError, "The first argument for 'pay2goo_form_for' must be a Spgateway::MPGForm." 12 | end 13 | 14 | form_tag(mpg_gateway_url, method: :post) do 15 | mpg_form_object.sorted_attrs.each do |param_pair| 16 | name, value = param_pair 17 | concat hidden_field_tag name, value 18 | end 19 | 20 | concat hidden_field_tag :CheckValue, mpg_form_object.check_value 21 | 22 | concat submit_tag submit, class: submit_class 23 | end 24 | end 25 | 26 | def spgateway_pay_button(title, options) 27 | raise ArgumentError, 'Missing required argument: order_number.' unless options[:order_number] 28 | raise ArgumentError, 'Missing required argument: item_description.' unless options[:item_description] 29 | raise ArgumentError, 'Missing required argument: amount.' unless options[:amount] 30 | 31 | form_attributes = options.except( 32 | :order_number, 33 | :item_description, 34 | :amount, 35 | :payer_email, 36 | :payment_methods, 37 | :class, 38 | :mpg_gateway_url 39 | ) 40 | 41 | form_attributes[:MerchantOrderNo] = options[:order_number] 42 | form_attributes[:ItemDesc] = options[:item_description] 43 | form_attributes[:Amt] = options[:amount] 44 | form_attributes[:Email] = options[:payer_email] 45 | 46 | form = Spgateway::MPGForm.new(form_attributes) 47 | 48 | form.return_url = 49 | Spgateway::Engine.routes.url_helpers.mpg_callbacks_url(host: request.host, port: request.port) 50 | 51 | if [80, 443].include?(request.port) && Spgateway.config.payment_code_callback 52 | form.customer_url = 53 | Spgateway::Engine.routes.url_helpers.payment_code_callbacks_url(host: request.host, port: request.port) 54 | end 55 | 56 | if [80, 443].include?(request.port) && Spgateway.config.notify_callback 57 | form.notify_url = 58 | Spgateway::Engine.routes.url_helpers.notify_callbacks_url(host: request.host, port: request.port) 59 | end 60 | 61 | if options[:payment_methods].is_a? Array 62 | options[:payment_methods].each do |payment_method| 63 | case payment_method.to_sym 64 | when :credit 65 | form.set_attr 'CREDIT', '1' 66 | when :credit_card 67 | form.set_attr 'CREDIT', '1' 68 | when :inst_flag 69 | form.set_attr 'InstFlag', '1' 70 | when :installment 71 | form.set_attr 'InstFlag', '1' 72 | when :credit_red 73 | form.set_attr 'CreditRed', '1' 74 | when :unionpay 75 | form.set_attr 'UNIONPAY', '1' 76 | when :webatm 77 | form.set_attr 'WEBATM', '1' 78 | when :vacc 79 | form.set_attr 'VACC', '1' 80 | when :cvs 81 | form.set_attr 'CVS', '1' 82 | when :barcode 83 | form.set_attr 'BARCODE', '1' 84 | else 85 | form.set_attr payment_method, '1' 86 | end 87 | end 88 | end 89 | 90 | spgateway_mpg_form_for( 91 | form, 92 | submit: title, 93 | submit_class: options[:class], 94 | mpg_gateway_url: options[:mpg_gateway_url] || Spgateway.config.mpg_gateway_url 95 | ) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/generators/spgateway/templates/spgateway_initializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Spgateway.configure do |config| 3 | # Spgateway merchant ID. 4 | # You can get your ID on https://www.spgateway.com/shop/member_shop/shop_list 5 | # (or https://cwww.spgateway.com/shop/member_shop/shop_list for development). 6 | config.merchant_id = 'place_your_merchant_id_here' 7 | 8 | # The HashKey and HashIV of the merchant on Spgateway. This can be found in 9 | # the「銷售中心 › 商店管理 › 商店資料設定,詳細資料 › API 串接金鑰」section of the 10 | # Spgateway member centre. 11 | config.hash_key = 'place_your_hash_key_here' 12 | config.hash_iv = 'place_your_hash_iv_here' 13 | 14 | # Spgateway API URL. 15 | # Normally will be 'https://ccore.spgateway.com/API' for development and 16 | # 'https://core.spgateway.com/API' for production. 17 | config.api_url = 'https://ccore.spgateway.com/API' 18 | 19 | # MPG gateway URL (i.e. the "action" attribute in user's purchasing form). 20 | # Normally this will be 'https://ccore.spgateway.com/MPG/mpg_gateway' for 21 | # development and 'https://core.spgateway.com/MPG/mpg_gateway' for 22 | # production. 23 | config.mpg_gateway_url = 'https://ccore.spgateway.com/MPG/mpg_gateway' 24 | 25 | # Callback after the user has been redirect back from Spgateway MPG gateway. 26 | config.mpg_callback do |spgateway_response| 27 | raise "Please configure mpg_callback in #{__FILE__}" 28 | # Put the trade result user facing logic here. 29 | # 30 | # Be careful not to repeat the order data updating work if you've configure 31 | # notify_callback to do it - because both notify_callback and mpg_callback 32 | # will be called when the user has done with the payment (notify_callback) 33 | # and then be redirected back to the website immediately (mpg_callback), 34 | # unexpected results might happen if you do the same thing twice at the 35 | # same time. 36 | # 37 | # Implementation example: 38 | # (this only shows the results to the user while we assume that you want 39 | # notify_callback - placed at the next section of this file - to do the 40 | # real business logic) 41 | # 42 | # if spgateway_response.status == 'SUCCESS' 43 | # flash[:success] = spgateway_response.message 44 | # else 45 | # flash[:error] = spgateway_response.message 46 | # end 47 | # 48 | # redirect_to orders_path 49 | end 50 | 51 | # Callback triggered by Spgateway after an order has been paid. 52 | # You can ignore this config if you're not using offline payment methods and 53 | # had done everything in mpg_callback. 54 | # config.notify_callback do |spgateway_response| 55 | # # Put the trade result proceeding logic here. 56 | # # 57 | # # Implementation example: 58 | # 59 | # if spgateway_response.status == 'SUCCESS' 60 | # Order.find_by(serial: spgateway_response.result.merchant_order_no) 61 | # .update_attributes!(paid: true) 62 | # else 63 | # Rails.logger.info "Spgateway Payment Not Succeed: #{spgateway_response.status}: #{spgateway_response.message} (#{spgateway_response.result.to_json})" 64 | # end 65 | # end 66 | 67 | # Callback when received the ATM transfer account, convenience store payment 68 | # code or barcode. 69 | # If you want Spgateway to display these payment instructions directly to 70 | # your customers directly, simply ignore this config. 71 | # config.payment_code_callback do |spgateway_response| 72 | # # Put the trade result proceeding logic here. 73 | # # 74 | # # Implementation example: 75 | # # 76 | # if spgateway_response.status == 'SUCCESS' && 77 | # spgateway_response.result.payment_type == 'VACC' 78 | 79 | # bank_code = spgateway_response.result.bank_code 80 | # account_number = spgateway_response.result.code_no 81 | # expired_at = 82 | # DateTime.parse("#{spgateway_response.result.expire_date} #{spgateway_response.result.expire_time} UTC+8") 83 | # Order.find_by(serial: spgateway_response.result.merchant_order_no) 84 | # .update_attributes!(bank_code: bank_code, account_number: account_number, expired_at: expired_at) 85 | # flash[:info] = 86 | # "Please transfer the money to bank code #{bank_code}, account number #{account_number} before #{I18n.l(expired_at)}" 87 | # else 88 | # Rails.logger.error "Spgateway Payment Code Receive Not Succeed: #{spgateway_response.status}: #{spgateway_response.message} (#{spgateway_response.result.to_json})" 89 | # flash[:error] = "Our apologies, but an unexpected error occured, please try again" 90 | # end 91 | 92 | # redirect_to orders_path 93 | # end 94 | end 95 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | spgateway-rails (0.1.0) 5 | rails (>= 4.0, < 5.2) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.1.0) 11 | actionpack (= 5.1.0) 12 | nio4r (~> 2.0) 13 | websocket-driver (~> 0.6.1) 14 | actionmailer (5.1.0) 15 | actionpack (= 5.1.0) 16 | actionview (= 5.1.0) 17 | activejob (= 5.1.0) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.1.0) 21 | actionview (= 5.1.0) 22 | activesupport (= 5.1.0) 23 | rack (~> 2.0) 24 | rack-test (~> 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.1.0) 28 | activesupport (= 5.1.0) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.1.0) 34 | activesupport (= 5.1.0) 35 | globalid (>= 0.3.6) 36 | activemodel (5.1.0) 37 | activesupport (= 5.1.0) 38 | activerecord (5.1.0) 39 | activemodel (= 5.1.0) 40 | activesupport (= 5.1.0) 41 | arel (~> 8.0) 42 | activesupport (5.1.0) 43 | concurrent-ruby (~> 1.0, >= 1.0.2) 44 | i18n (~> 0.7) 45 | minitest (~> 5.1) 46 | tzinfo (~> 1.1) 47 | addressable (2.5.1) 48 | public_suffix (~> 2.0, >= 2.0.2) 49 | appraisal (2.2.0) 50 | bundler 51 | rake 52 | thor (>= 0.14.0) 53 | arel (8.0.0) 54 | builder (3.2.3) 55 | capybara (2.14.0) 56 | addressable 57 | mime-types (>= 1.16) 58 | nokogiri (>= 1.3.3) 59 | rack (>= 1.0.0) 60 | rack-test (>= 0.5.4) 61 | xpath (~> 2.0) 62 | cliver (0.3.2) 63 | concurrent-ruby (1.0.5) 64 | coveralls (0.7.1) 65 | multi_json (~> 1.3) 66 | rest-client 67 | simplecov (>= 0.7) 68 | term-ansicolor 69 | thor 70 | diff-lcs (1.3) 71 | docile (1.1.5) 72 | domain_name (0.5.20161129) 73 | unf (>= 0.0.5, < 1.0.0) 74 | erubi (1.6.0) 75 | globalid (0.4.0) 76 | activesupport (>= 4.2.0) 77 | http-cookie (1.0.3) 78 | domain_name (~> 0.5) 79 | i18n (0.8.1) 80 | json (2.0.4) 81 | loofah (2.0.3) 82 | nokogiri (>= 1.5.9) 83 | mail (2.6.5) 84 | mime-types (>= 1.16, < 4) 85 | method_source (0.8.2) 86 | mime-types (3.1) 87 | mime-types-data (~> 3.2015) 88 | mime-types-data (3.2016.0521) 89 | mini_portile2 (2.1.0) 90 | minitest (5.10.2) 91 | multi_json (1.12.1) 92 | netrc (0.11.0) 93 | ngrok-rspec (1.1.18) 94 | capybara (>= 2.4) 95 | ngrok-tunnel (~> 2.0) 96 | rspec (>= 2.99) 97 | ngrok-tunnel (2.1.0) 98 | nio4r (2.0.0) 99 | nokogiri (1.7.2) 100 | mini_portile2 (~> 2.1.0) 101 | poltergeist (1.15.0) 102 | capybara (~> 2.1) 103 | cliver (~> 0.3.1) 104 | websocket-driver (>= 0.2.0) 105 | public_suffix (2.0.5) 106 | rack (2.0.2) 107 | rack-test (0.6.3) 108 | rack (>= 1.0) 109 | rails (5.1.0) 110 | actioncable (= 5.1.0) 111 | actionmailer (= 5.1.0) 112 | actionpack (= 5.1.0) 113 | actionview (= 5.1.0) 114 | activejob (= 5.1.0) 115 | activemodel (= 5.1.0) 116 | activerecord (= 5.1.0) 117 | activesupport (= 5.1.0) 118 | bundler (>= 1.3.0, < 2.0) 119 | railties (= 5.1.0) 120 | sprockets-rails (>= 2.0.0) 121 | rails-dom-testing (2.0.2) 122 | activesupport (>= 4.2.0, < 6.0) 123 | nokogiri (~> 1.6) 124 | rails-html-sanitizer (1.0.3) 125 | loofah (~> 2.0) 126 | railties (5.1.0) 127 | actionpack (= 5.1.0) 128 | activesupport (= 5.1.0) 129 | method_source 130 | rake (>= 0.8.7) 131 | thor (>= 0.18.1, < 2.0) 132 | rake (12.0.0) 133 | rest-client (2.0.0) 134 | http-cookie (>= 1.0.2, < 2.0) 135 | mime-types (>= 1.16, < 4.0) 136 | netrc (~> 0.8) 137 | rspec (3.6.0) 138 | rspec-core (~> 3.6.0) 139 | rspec-expectations (~> 3.6.0) 140 | rspec-mocks (~> 3.6.0) 141 | rspec-core (3.6.0) 142 | rspec-support (~> 3.6.0) 143 | rspec-expectations (3.6.0) 144 | diff-lcs (>= 1.2.0, < 2.0) 145 | rspec-support (~> 3.6.0) 146 | rspec-mocks (3.6.0) 147 | diff-lcs (>= 1.2.0, < 2.0) 148 | rspec-support (~> 3.6.0) 149 | rspec-rails (3.6.0) 150 | actionpack (>= 3.0) 151 | activesupport (>= 3.0) 152 | railties (>= 3.0) 153 | rspec-core (~> 3.6.0) 154 | rspec-expectations (~> 3.6.0) 155 | rspec-mocks (~> 3.6.0) 156 | rspec-support (~> 3.6.0) 157 | rspec-support (3.6.0) 158 | simplecov (0.13.0) 159 | docile (~> 1.1.0) 160 | json (>= 1.8, < 3) 161 | simplecov-html (~> 0.10.0) 162 | simplecov-html (0.10.0) 163 | sprockets (3.7.1) 164 | concurrent-ruby (~> 1.0) 165 | rack (> 1, < 3) 166 | sprockets-rails (3.2.0) 167 | actionpack (>= 4.0) 168 | activesupport (>= 4.0) 169 | sprockets (>= 3.0.0) 170 | term-ansicolor (1.4.0) 171 | tins (~> 1.0) 172 | thor (0.19.4) 173 | thread_safe (0.3.6) 174 | tins (1.13.2) 175 | tzinfo (1.2.3) 176 | thread_safe (~> 0.1) 177 | unf (0.1.4) 178 | unf_ext 179 | unf_ext (0.0.7.2) 180 | websocket-driver (0.6.5) 181 | websocket-extensions (>= 0.1.0) 182 | websocket-extensions (0.1.2) 183 | xpath (2.0.0) 184 | nokogiri (~> 1.3) 185 | 186 | PLATFORMS 187 | ruby 188 | 189 | DEPENDENCIES 190 | appraisal 191 | capybara 192 | coveralls 193 | ngrok-rspec 194 | poltergeist 195 | rspec-rails 196 | simplecov 197 | spgateway-rails! 198 | 199 | BUNDLED WITH 200 | 1.13.7 201 | -------------------------------------------------------------------------------- /gemfiles/rails_4.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | spgateway-rails (0.1.0) 5 | rails (>= 4.0, < 5.2) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionmailer (4.2.0) 11 | actionpack (= 4.2.0) 12 | actionview (= 4.2.0) 13 | activejob (= 4.2.0) 14 | mail (~> 2.5, >= 2.5.4) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | actionpack (4.2.0) 17 | actionview (= 4.2.0) 18 | activesupport (= 4.2.0) 19 | rack (~> 1.6.0) 20 | rack-test (~> 0.6.2) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.1) 23 | actionview (4.2.0) 24 | activesupport (= 4.2.0) 25 | builder (~> 3.1) 26 | erubis (~> 2.7.0) 27 | rails-dom-testing (~> 1.0, >= 1.0.5) 28 | rails-html-sanitizer (~> 1.0, >= 1.0.1) 29 | activejob (4.2.0) 30 | activesupport (= 4.2.0) 31 | globalid (>= 0.3.0) 32 | activemodel (4.2.0) 33 | activesupport (= 4.2.0) 34 | builder (~> 3.1) 35 | activerecord (4.2.0) 36 | activemodel (= 4.2.0) 37 | activesupport (= 4.2.0) 38 | arel (~> 6.0) 39 | activesupport (4.2.0) 40 | i18n (~> 0.7) 41 | json (~> 1.7, >= 1.7.7) 42 | minitest (~> 5.1) 43 | thread_safe (~> 0.3, >= 0.3.4) 44 | tzinfo (~> 1.1) 45 | addressable (2.5.1) 46 | public_suffix (~> 2.0, >= 2.0.2) 47 | appraisal (2.2.0) 48 | bundler 49 | rake 50 | thor (>= 0.14.0) 51 | arel (6.0.4) 52 | builder (3.2.3) 53 | capybara (2.14.0) 54 | addressable 55 | mime-types (>= 1.16) 56 | nokogiri (>= 1.3.3) 57 | rack (>= 1.0.0) 58 | rack-test (>= 0.5.4) 59 | xpath (~> 2.0) 60 | cliver (0.3.2) 61 | concurrent-ruby (1.0.5) 62 | coveralls (0.7.1) 63 | multi_json (~> 1.3) 64 | rest-client 65 | simplecov (>= 0.7) 66 | term-ansicolor 67 | thor 68 | diff-lcs (1.3) 69 | docile (1.1.5) 70 | domain_name (0.5.20161129) 71 | unf (>= 0.0.5, < 1.0.0) 72 | erubis (2.7.0) 73 | globalid (0.4.0) 74 | activesupport (>= 4.2.0) 75 | http-cookie (1.0.3) 76 | domain_name (~> 0.5) 77 | i18n (0.8.1) 78 | json (1.8.6) 79 | loofah (2.0.3) 80 | nokogiri (>= 1.5.9) 81 | mail (2.6.5) 82 | mime-types (>= 1.16, < 4) 83 | mime-types (3.1) 84 | mime-types-data (~> 3.2015) 85 | mime-types-data (3.2016.0521) 86 | mini_portile2 (2.1.0) 87 | minitest (5.10.2) 88 | multi_json (1.12.1) 89 | netrc (0.11.0) 90 | ngrok-rspec (1.1.18) 91 | capybara (>= 2.4) 92 | ngrok-tunnel (~> 2.0) 93 | rspec (>= 2.99) 94 | ngrok-tunnel (2.1.0) 95 | nokogiri (1.7.2) 96 | mini_portile2 (~> 2.1.0) 97 | poltergeist (1.15.0) 98 | capybara (~> 2.1) 99 | cliver (~> 0.3.1) 100 | websocket-driver (>= 0.2.0) 101 | public_suffix (2.0.5) 102 | rack (1.6.6) 103 | rack-test (0.6.3) 104 | rack (>= 1.0) 105 | rails (4.2.0) 106 | actionmailer (= 4.2.0) 107 | actionpack (= 4.2.0) 108 | actionview (= 4.2.0) 109 | activejob (= 4.2.0) 110 | activemodel (= 4.2.0) 111 | activerecord (= 4.2.0) 112 | activesupport (= 4.2.0) 113 | bundler (>= 1.3.0, < 2.0) 114 | railties (= 4.2.0) 115 | sprockets-rails 116 | rails-deprecated_sanitizer (1.0.3) 117 | activesupport (>= 4.2.0.alpha) 118 | rails-dom-testing (1.0.8) 119 | activesupport (>= 4.2.0.beta, < 5.0) 120 | nokogiri (~> 1.6) 121 | rails-deprecated_sanitizer (>= 1.0.1) 122 | rails-html-sanitizer (1.0.3) 123 | loofah (~> 2.0) 124 | railties (4.2.0) 125 | actionpack (= 4.2.0) 126 | activesupport (= 4.2.0) 127 | rake (>= 0.8.7) 128 | thor (>= 0.18.1, < 2.0) 129 | rake (12.0.0) 130 | rest-client (2.0.0) 131 | http-cookie (>= 1.0.2, < 2.0) 132 | mime-types (>= 1.16, < 4.0) 133 | netrc (~> 0.8) 134 | rspec (3.6.0) 135 | rspec-core (~> 3.6.0) 136 | rspec-expectations (~> 3.6.0) 137 | rspec-mocks (~> 3.6.0) 138 | rspec-core (3.6.0) 139 | rspec-support (~> 3.6.0) 140 | rspec-expectations (3.6.0) 141 | diff-lcs (>= 1.2.0, < 2.0) 142 | rspec-support (~> 3.6.0) 143 | rspec-mocks (3.6.0) 144 | diff-lcs (>= 1.2.0, < 2.0) 145 | rspec-support (~> 3.6.0) 146 | rspec-rails (3.6.0) 147 | actionpack (>= 3.0) 148 | activesupport (>= 3.0) 149 | railties (>= 3.0) 150 | rspec-core (~> 3.6.0) 151 | rspec-expectations (~> 3.6.0) 152 | rspec-mocks (~> 3.6.0) 153 | rspec-support (~> 3.6.0) 154 | rspec-support (3.6.0) 155 | simplecov (0.13.0) 156 | docile (~> 1.1.0) 157 | json (>= 1.8, < 3) 158 | simplecov-html (~> 0.10.0) 159 | simplecov-html (0.10.0) 160 | sprockets (3.7.1) 161 | concurrent-ruby (~> 1.0) 162 | rack (> 1, < 3) 163 | sprockets-rails (3.2.0) 164 | actionpack (>= 4.0) 165 | activesupport (>= 4.0) 166 | sprockets (>= 3.0.0) 167 | term-ansicolor (1.4.0) 168 | tins (~> 1.0) 169 | thor (0.19.4) 170 | thread_safe (0.3.6) 171 | tins (1.13.2) 172 | tzinfo (1.2.3) 173 | thread_safe (~> 0.1) 174 | unf (0.1.4) 175 | unf_ext 176 | unf_ext (0.0.7.2) 177 | websocket-driver (0.6.5) 178 | websocket-extensions (>= 0.1.0) 179 | websocket-extensions (0.1.2) 180 | xpath (2.0.0) 181 | nokogiri (~> 1.3) 182 | 183 | PLATFORMS 184 | ruby 185 | 186 | DEPENDENCIES 187 | appraisal 188 | capybara 189 | coveralls 190 | ngrok-rspec 191 | poltergeist 192 | rails (= 4.2) 193 | rspec-rails 194 | simplecov 195 | spgateway-rails! 196 | 197 | BUNDLED WITH 198 | 1.13.7 199 | -------------------------------------------------------------------------------- /gemfiles/rails_5.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | spgateway-rails (0.1.0) 5 | rails (>= 4.0, < 5.2) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.1.0) 11 | actionpack (= 5.1.0) 12 | nio4r (~> 2.0) 13 | websocket-driver (~> 0.6.1) 14 | actionmailer (5.1.0) 15 | actionpack (= 5.1.0) 16 | actionview (= 5.1.0) 17 | activejob (= 5.1.0) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.1.0) 21 | actionview (= 5.1.0) 22 | activesupport (= 5.1.0) 23 | rack (~> 2.0) 24 | rack-test (~> 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.1.0) 28 | activesupport (= 5.1.0) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.1.0) 34 | activesupport (= 5.1.0) 35 | globalid (>= 0.3.6) 36 | activemodel (5.1.0) 37 | activesupport (= 5.1.0) 38 | activerecord (5.1.0) 39 | activemodel (= 5.1.0) 40 | activesupport (= 5.1.0) 41 | arel (~> 8.0) 42 | activesupport (5.1.0) 43 | concurrent-ruby (~> 1.0, >= 1.0.2) 44 | i18n (~> 0.7) 45 | minitest (~> 5.1) 46 | tzinfo (~> 1.1) 47 | addressable (2.5.1) 48 | public_suffix (~> 2.0, >= 2.0.2) 49 | appraisal (2.2.0) 50 | bundler 51 | rake 52 | thor (>= 0.14.0) 53 | arel (8.0.0) 54 | builder (3.2.3) 55 | capybara (2.14.0) 56 | addressable 57 | mime-types (>= 1.16) 58 | nokogiri (>= 1.3.3) 59 | rack (>= 1.0.0) 60 | rack-test (>= 0.5.4) 61 | xpath (~> 2.0) 62 | cliver (0.3.2) 63 | concurrent-ruby (1.0.5) 64 | coveralls (0.7.1) 65 | multi_json (~> 1.3) 66 | rest-client 67 | simplecov (>= 0.7) 68 | term-ansicolor 69 | thor 70 | diff-lcs (1.3) 71 | docile (1.1.5) 72 | domain_name (0.5.20161129) 73 | unf (>= 0.0.5, < 1.0.0) 74 | erubi (1.6.0) 75 | globalid (0.4.0) 76 | activesupport (>= 4.2.0) 77 | http-cookie (1.0.3) 78 | domain_name (~> 0.5) 79 | i18n (0.8.1) 80 | json (2.0.4) 81 | loofah (2.0.3) 82 | nokogiri (>= 1.5.9) 83 | mail (2.6.5) 84 | mime-types (>= 1.16, < 4) 85 | method_source (0.8.2) 86 | mime-types (3.1) 87 | mime-types-data (~> 3.2015) 88 | mime-types-data (3.2016.0521) 89 | mini_portile2 (2.1.0) 90 | minitest (5.10.2) 91 | multi_json (1.12.1) 92 | netrc (0.11.0) 93 | ngrok-rspec (1.1.18) 94 | capybara (>= 2.4) 95 | ngrok-tunnel (~> 2.0) 96 | rspec (>= 2.99) 97 | ngrok-tunnel (2.1.0) 98 | nio4r (2.0.0) 99 | nokogiri (1.7.2) 100 | mini_portile2 (~> 2.1.0) 101 | poltergeist (1.15.0) 102 | capybara (~> 2.1) 103 | cliver (~> 0.3.1) 104 | websocket-driver (>= 0.2.0) 105 | public_suffix (2.0.5) 106 | rack (2.0.2) 107 | rack-test (0.6.3) 108 | rack (>= 1.0) 109 | rails (5.1.0) 110 | actioncable (= 5.1.0) 111 | actionmailer (= 5.1.0) 112 | actionpack (= 5.1.0) 113 | actionview (= 5.1.0) 114 | activejob (= 5.1.0) 115 | activemodel (= 5.1.0) 116 | activerecord (= 5.1.0) 117 | activesupport (= 5.1.0) 118 | bundler (>= 1.3.0, < 2.0) 119 | railties (= 5.1.0) 120 | sprockets-rails (>= 2.0.0) 121 | rails-dom-testing (2.0.2) 122 | activesupport (>= 4.2.0, < 6.0) 123 | nokogiri (~> 1.6) 124 | rails-html-sanitizer (1.0.3) 125 | loofah (~> 2.0) 126 | railties (5.1.0) 127 | actionpack (= 5.1.0) 128 | activesupport (= 5.1.0) 129 | method_source 130 | rake (>= 0.8.7) 131 | thor (>= 0.18.1, < 2.0) 132 | rake (12.0.0) 133 | rest-client (2.0.0) 134 | http-cookie (>= 1.0.2, < 2.0) 135 | mime-types (>= 1.16, < 4.0) 136 | netrc (~> 0.8) 137 | rspec (3.6.0) 138 | rspec-core (~> 3.6.0) 139 | rspec-expectations (~> 3.6.0) 140 | rspec-mocks (~> 3.6.0) 141 | rspec-core (3.6.0) 142 | rspec-support (~> 3.6.0) 143 | rspec-expectations (3.6.0) 144 | diff-lcs (>= 1.2.0, < 2.0) 145 | rspec-support (~> 3.6.0) 146 | rspec-mocks (3.6.0) 147 | diff-lcs (>= 1.2.0, < 2.0) 148 | rspec-support (~> 3.6.0) 149 | rspec-rails (3.6.0) 150 | actionpack (>= 3.0) 151 | activesupport (>= 3.0) 152 | railties (>= 3.0) 153 | rspec-core (~> 3.6.0) 154 | rspec-expectations (~> 3.6.0) 155 | rspec-mocks (~> 3.6.0) 156 | rspec-support (~> 3.6.0) 157 | rspec-support (3.6.0) 158 | simplecov (0.13.0) 159 | docile (~> 1.1.0) 160 | json (>= 1.8, < 3) 161 | simplecov-html (~> 0.10.0) 162 | simplecov-html (0.10.0) 163 | sprockets (3.7.1) 164 | concurrent-ruby (~> 1.0) 165 | rack (> 1, < 3) 166 | sprockets-rails (3.2.0) 167 | actionpack (>= 4.0) 168 | activesupport (>= 4.0) 169 | sprockets (>= 3.0.0) 170 | term-ansicolor (1.4.0) 171 | tins (~> 1.0) 172 | thor (0.19.4) 173 | thread_safe (0.3.6) 174 | tins (1.13.2) 175 | tzinfo (1.2.3) 176 | thread_safe (~> 0.1) 177 | unf (0.1.4) 178 | unf_ext 179 | unf_ext (0.0.7.2) 180 | websocket-driver (0.6.5) 181 | websocket-extensions (>= 0.1.0) 182 | websocket-extensions (0.1.2) 183 | xpath (2.0.0) 184 | nokogiri (~> 1.3) 185 | 186 | PLATFORMS 187 | ruby 188 | 189 | DEPENDENCIES 190 | appraisal 191 | capybara 192 | coveralls 193 | ngrok-rspec 194 | poltergeist 195 | rails (= 5.1) 196 | rspec-rails 197 | simplecov 198 | spgateway-rails! 199 | 200 | BUNDLED WITH 201 | 1.13.7 202 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 3 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 4 | # The generated `.rspec` file contains `--require spec_helper` which will cause 5 | # this file to always be loaded, without a need to explicitly require it in any 6 | # files. 7 | # 8 | # Given that it is always loaded, you are encouraged to keep this file as 9 | # light-weight as possible. Requiring heavyweight dependencies from this file 10 | # will add to the boot time of your test suite on EVERY test run, even for an 11 | # individual file that may not need all of that loaded. Instead, consider making 12 | # a separate helper file that requires the additional dependencies and performs 13 | # the additional setup, and require it from the spec files that actually need 14 | # it. 15 | # 16 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 17 | 18 | require 'simplecov' 19 | require 'coveralls' 20 | 21 | SimpleCov.start do 22 | load_profile 'test_frameworks' 23 | 24 | add_group 'Spgateway Lib', 'lib/spgateway' 25 | add_group 'Controllers', 'app/controllers' 26 | add_group 'Helpers', 'app/helper' 27 | add_group 'Jobs', 'app/jobs' 28 | 29 | add_filter 'lib/spgateway/rails/engine.rb' 30 | add_filter 'lib/spgateway/version.rb' 31 | add_filter 'lib/generators/spgateway' 32 | add_filter 'app/jobs' 33 | 34 | track_files '{app,lib}/**/*.rb' 35 | end 36 | 37 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( 38 | [ 39 | SimpleCov::Formatter::HTMLFormatter, 40 | Coveralls::SimpleCov::Formatter 41 | ] 42 | ) 43 | 44 | require 'support/wait_for' 45 | 46 | RSpec.configure do |config| 47 | # rspec-expectations config goes here. You can use an alternate 48 | # assertion/expectation library such as wrong or the stdlib/minitest 49 | # assertions if you prefer. 50 | config.expect_with :rspec do |expectations| 51 | # This option will default to `true` in RSpec 4. It makes the `description` 52 | # and `failure_message` of custom matchers include text for helper methods 53 | # defined using `chain`, e.g.: 54 | # be_bigger_than(2).and_smaller_than(4).description 55 | # # => "be bigger than 2 and smaller than 4" 56 | # ...rather than: 57 | # # => "be bigger than 2" 58 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 59 | end 60 | 61 | # rspec-mocks config goes here. You can use an alternate test double 62 | # library (such as bogus or mocha) by changing the `mock_with` option here. 63 | config.mock_with :rspec do |mocks| 64 | # Prevents you from mocking or stubbing a method that does not exist on 65 | # a real object. This is generally recommended, and will default to 66 | # `true` in RSpec 4. 67 | mocks.verify_partial_doubles = true 68 | end 69 | 70 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 71 | # have no way to turn it off -- the option exists only for backwards 72 | # compatibility in RSpec 3). It causes shared context metadata to be 73 | # inherited by the metadata hash of host groups and examples, rather than 74 | # triggering implicit auto-inclusion in groups with matching metadata. 75 | config.shared_context_metadata_behavior = :apply_to_host_groups 76 | 77 | # The settings below are suggested to provide a good initial experience 78 | # with RSpec, but feel free to customize to your heart's content. 79 | =begin 80 | # This allows you to limit a spec run to individual examples or groups 81 | # you care about by tagging them with `:focus` metadata. When nothing 82 | # is tagged with `:focus`, all examples get run. RSpec also provides 83 | # aliases for `it`, `describe`, and `context` that include `:focus` 84 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 85 | config.filter_run_when_matching :focus 86 | 87 | # Allows RSpec to persist some state between runs in order to support 88 | # the `--only-failures` and `--next-failure` CLI options. We recommend 89 | # you configure your source control system to ignore this file. 90 | config.example_status_persistence_file_path = "spec/examples.txt" 91 | 92 | # Limits the available syntax to the non-monkey patched syntax that is 93 | # recommended. For more details, see: 94 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 95 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 96 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 97 | config.disable_monkey_patching! 98 | 99 | # Many RSpec users commonly either run the entire suite or an individual 100 | # file, and it's useful to allow more verbose output when running an 101 | # individual spec file. 102 | if config.files_to_run.one? 103 | # Use the documentation formatter for detailed output, 104 | # unless a formatter has already been configured 105 | # (e.g. via a command-line flag). 106 | config.default_formatter = "doc" 107 | end 108 | 109 | # Print the 10 slowest examples and example groups at the 110 | # end of the spec run, to help surface which specs are running 111 | # particularly slow. 112 | config.profile_examples = 10 113 | 114 | # Run specs in random order to surface order dependencies. If you find an 115 | # order dependency and want to debug it, you can fix the order by providing 116 | # the seed, which is printed after each run. 117 | # --seed 1234 118 | config.order = :random 119 | 120 | # Seed global randomization in this process using the `--seed` CLI option. 121 | # Setting this allows you to use `--seed` to deterministically reproduce 122 | # test failures related to randomization by passing the same `--seed` value 123 | # as the one that triggered the failure. 124 | Kernel.srand config.seed 125 | =end 126 | end 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spgateway Rails Plugin [![Build Status](https://travis-ci.org/5xRuby/spgateway-rails.svg?branch=master)](https://travis-ci.org/5xRuby/spgateway-rails) 2 | 3 | This plugin provides convenient integration with [Spgateway](https://www.spgateway.com) - an online payment service in Taiwan. 4 | 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'spgateway-rails', github: '5xRuby/spgateway-rails' 12 | ``` 13 | 14 | And then execute: 15 | 16 | ```bash 17 | $ bundle 18 | ``` 19 | 20 | Finally, run the install generator: 21 | 22 | ```bash 23 | $ rails generate spgateway:install 24 | ``` 25 | 26 | then set your `merchant_id`, `hash_key` and `hash_iv` in `config/initializers/spgateway.rb`. 27 | 28 | 29 | ## Basic Usage 30 | 31 | ### The simplest integration: only support immediate credit card pay off 32 | 33 | 1. Place the pay button in a view, such as: 34 | 35 | ```erb 36 | <%= spgateway_pay_button 'Go pay', payment_methods: [:credit_card], order_number: @order.serial, item_description: @order.description, amount: @order.amount, payer_email: current_user&.email, class: 'btn btn-success' %> 37 | ``` 38 | 39 | > Note that we restrict the supported payment methods to only `credit_card` here. 40 | 41 | 2. Configure how to process payment results in `config/initializers/spgateway.rb`, for example: 42 | 43 | ```rb 44 | config.mpg_callback do |mpg_response| 45 | if mpg_response.status == 'SUCCESS' 46 | Order.find_by(serial: mpg_response.result.merchant_order_no) 47 | .update_attributes!(paid: true) 48 | flash[:success] = mpg_response.message 49 | else 50 | flash[:error] = mpg_response.message 51 | end 52 | 53 | redirect_to orders_path 54 | end 55 | ``` 56 | 57 | ### Supporting non-real-time payment methods: using `notify_callback` 58 | 59 | With some payment methods, such as ATM 轉帳 (VACC), 超商代碼繳費 (CVS) or 超商條碼繳費 (BARCODE), users will not complete their transaction on the web browser but maybe in front of an ATM or in a convenient store or so. Spgateway will notify our application later if such transactions has been done, and we will need an additional setup to deal with these notifications. 60 | 61 | > Note that you will not be able to test this intergration with an local application server (i.e. `http://localhost:3000`) directly, because (in normal cases) Spgateway cannot connect to your local computer. Consider using services like [ngrok](https://ngrok.com/) to get a public URL tunneled to your local application server, and use that public URL in the browser to get things work. 62 | 63 | 1. You'll need to setup `mpg_callback` and `notify_callback` like this: 64 | 65 | ```rb 66 | # Callback after the user has been redirect back from Spgateway MPG gateway. 67 | config.mpg_callback do |spgateway_response| 68 | # Only shows the results to the user here, while notify_callback will do the 69 | # actual work. 70 | 71 | if spgateway_response.status == 'SUCCESS' 72 | flash[:success] = spgateway_response.message 73 | else 74 | flash[:error] = spgateway_response.message 75 | end 76 | 77 | redirect_to orders_path 78 | end 79 | 80 | # Callback triggered by Spgateway after an order has been paid. 81 | config.notify_callback do |spgateway_response| 82 | 83 | if spgateway_response.status == 'SUCCESS' 84 | # Find the order and mark it as paid. 85 | Order.find_by(serial: spgateway_response.result.merchant_order_no) 86 | .update_attributes!(paid: true) 87 | else 88 | # Or log the error. 89 | Rails.logger.info "Spgateway Payment Not Succeed: #{spgateway_response.status}: #{spgateway_response.message} (#{spgateway_response.result.to_json})" 90 | end 91 | end 92 | ``` 93 | 94 | The `notify_callback` will be called when Spgateway tries to notify us about payment status updates, nomatter which payment method does the user select. So in the `mpg_callback` block, we should only write code for user-facing logic, to prevent dulipaced work and unexpected results. 95 | 96 | 2. Now you can add non-real-time payment methods to your pay button: 97 | 98 | ```erb 99 | <%= spgateway_pay_button 'Go pay', payment_methods: [:credit_card, :vacc, :cvs, :barcode], order_number: @order.serial, item_description: @order.description, amount: @order.amount, payer_email: current_user&.email, class: 'btn btn-success' %> 100 | ``` 101 | 102 | ### Get the customer's ATM transfer account or payment code and show them on your website with `payment_code_callback` 103 | 104 | By default, Spgateway will show the payment instruction of ATM 轉帳 (VACC), 超商代碼繳費 (CVS) or 超商條碼繳費 (BARCODE) to users on their site directly, this way you can not get the payment info and users will not be redirected back to your site. 105 | 106 | You can add the `payment_code_callback` config to let users be redirected back to your site, so then you can have the payment info and show it to your users by yourself. Do something like this: 107 | 108 | ```rb 109 | config.payment_code_callback do |spgateway_response| 110 | if spgateway_response.status == 'SUCCESS' && 111 | spgateway_response.result.payment_type == 'VACC' 112 | 113 | bank_code = spgateway_response.result.bank_code 114 | account_number = spgateway_response.result.code_no 115 | expired_at = 116 | DateTime.parse("#{spgateway_response.result.expire_date} #{spgateway_response.result.expire_time} UTC+8") 117 | Order.find_by(serial: spgateway_response.result.merchant_order_no) 118 | .update_attributes!(bank_code: bank_code, account_number: account_number, expired_at: expired_at) 119 | flash[:info] = 120 | "Please transfer the money to bank code #{bank_code}, account number #{account_number} before #{I18n.l(expired_at)}" 121 | else 122 | Rails.logger.error "Spgateway Payment Code Receive Not Succeed: #{spgateway_response.status}: #{spgateway_response.message} (#{spgateway_response.result.to_json})" 123 | flash[:error] = "Our apologies, but an unexpected error occured, please try again" 124 | end 125 | 126 | redirect_to orders_path 127 | end 128 | ``` 129 | 130 | 131 | ## TODO 132 | 133 | - Support ClientBackURL. 134 | - Build API wrapper for QueryTradeInfo. 135 | - Add option to double check the payment results after callback. 136 | - Build API wrapper for CreditCard/Cancel. 137 | - Wtite docs. 138 | - Test, test everything! 139 | 140 | 141 | ## Contributing 142 | 143 | Just open an issue or send a PR :) 144 | 145 | 146 | ## License 147 | 148 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 149 | --------------------------------------------------------------------------------