upgrades: add authorize.net integration.
Add integration for accepting payments with Authorize.net. https://developer.authorize.net/hello_world.html
This commit is contained in:
@@ -4,7 +4,7 @@ class UserUpgradesController < ApplicationController
|
|||||||
respond_to :js, :html, :json, :xml
|
respond_to :js, :html, :json, :xml
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@user_upgrade = authorize UserUpgrade.create(recipient: recipient, purchaser: CurrentUser.user, status: "pending", upgrade_type: params[:upgrade_type])
|
@user_upgrade = authorize UserUpgrade.create(recipient: recipient, purchaser: CurrentUser.user, status: "pending", upgrade_type: params[:upgrade_type], payment_processor: params[:payment_processor])
|
||||||
@country = params[:country] || CurrentUser.country || "US"
|
@country = params[:country] || CurrentUser.country || "US"
|
||||||
@allow_promotion_codes = params[:promo].to_s.truthy?
|
@allow_promotion_codes = params[:promo].to_s.truthy?
|
||||||
@checkout = @user_upgrade.create_checkout!(country: @country, allow_promotion_codes: @allow_promotion_codes)
|
@checkout = @user_upgrade.create_checkout!(country: @country, allow_promotion_codes: @allow_promotion_codes)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhooksController < ApplicationController
|
class WebhooksController < ApplicationController
|
||||||
skip_forgery_protection only: :receive
|
skip_forgery_protection only: [:receive, :authorize_net]
|
||||||
|
|
||||||
rescue_with Stripe::SignatureVerificationError, status: 400
|
rescue_with Stripe::SignatureVerificationError, status: 400
|
||||||
rescue_with DiscordSlashCommand::WebhookVerificationError, status: 401
|
rescue_with DiscordSlashCommand::WebhookVerificationError, status: 401
|
||||||
|
|
||||||
@@ -17,4 +18,9 @@ class WebhooksController < ApplicationController
|
|||||||
head 400
|
head 400
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_net
|
||||||
|
PaymentTransaction::AuthorizeNet.receive_webhook(request)
|
||||||
|
head 200
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module UserUpgradesHelper
|
|
||||||
def cents_to_usd(cents)
|
|
||||||
number_to_currency(cents / 100, precision: 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
148
app/logical/authorize_net_client.rb
Normal file
148
app/logical/authorize_net_client.rb
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# An API client for Authorize.net. Used for processing payments for user upgrades.
|
||||||
|
#
|
||||||
|
# https://developer.authorize.net/api.html
|
||||||
|
# https://developer.authorize.net/api/reference/index.html
|
||||||
|
class AuthorizeNetClient
|
||||||
|
class Error < StandardError; end
|
||||||
|
|
||||||
|
attr_reader :login_id, :transaction_key, :test_mode, :http
|
||||||
|
|
||||||
|
def initialize(login_id: Danbooru.config.authorize_net_login_id, transaction_key: Danbooru.config.authorize_net_transaction_key, test_mode: Danbooru.config.authorize_net_test_mode, http: Danbooru::Http.new)
|
||||||
|
@login_id = login_id
|
||||||
|
@transaction_key = transaction_key
|
||||||
|
@test_mode = test_mode
|
||||||
|
@http = http
|
||||||
|
end
|
||||||
|
|
||||||
|
concerning :ApiMethods do
|
||||||
|
def authenticate_test
|
||||||
|
post!(
|
||||||
|
authenticateTestRequest: {
|
||||||
|
merchantAuthentication: {
|
||||||
|
name: login_id,
|
||||||
|
transactionKey: transaction_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/index.html#transaction-reporting-get-transaction-details
|
||||||
|
def get_transaction(transaction_id)
|
||||||
|
post!(
|
||||||
|
getTransactionDetailsRequest: {
|
||||||
|
merchantAuthentication: {
|
||||||
|
name: login_id,
|
||||||
|
transactionKey: transaction_key,
|
||||||
|
},
|
||||||
|
transId: transaction_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/index.html#accept-suite-get-an-accept-payment-page
|
||||||
|
def get_hosted_payment_page(reference_id:, settings: {}, **transaction_request)
|
||||||
|
post!(
|
||||||
|
getHostedPaymentPageRequest: {
|
||||||
|
merchantAuthentication: {
|
||||||
|
name: login_id,
|
||||||
|
transactionKey: transaction_key,
|
||||||
|
},
|
||||||
|
refId: reference_id,
|
||||||
|
transactionRequest: transaction_request,
|
||||||
|
"hostedPaymentSettings": {
|
||||||
|
"setting": hosted_payment_settings(settings),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def hosted_payment_settings(settings)
|
||||||
|
settings.map do |name, hash|
|
||||||
|
{
|
||||||
|
"settingName": "hostedPayment#{name.to_s.camelize}Options",
|
||||||
|
"settingValue": hash.to_json,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def post!(**request)
|
||||||
|
resp = http.post!(api_url, json: request)
|
||||||
|
|
||||||
|
body = resp.body.to_s.delete_prefix("\xEF\xBB\xBF") # delete UTF-8 BOM
|
||||||
|
json = JSON.parse(body).with_indifferent_access
|
||||||
|
|
||||||
|
if json.dig(:messages, :resultCode) != "Ok"
|
||||||
|
code = json.dig(:messages, :message, 0, :code)
|
||||||
|
text = json.dig(:messages, :message, 0, :text)
|
||||||
|
raise Error, "Authorize.net call failed (request=#{request.keys.first} code=#{code} text=#{text})"
|
||||||
|
else
|
||||||
|
json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/index.html#gettingstarted-section-section-header
|
||||||
|
def api_url
|
||||||
|
if test_mode
|
||||||
|
"https://apitest.authorize.net/xml/v1/request.api"
|
||||||
|
else
|
||||||
|
"https://api.authorize.net/xml/v1/request.api"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/accept_hosted.html#Form_POST_URLs
|
||||||
|
def payment_page_url
|
||||||
|
if test_mode
|
||||||
|
"https://test.authorize.net/payment/payment"
|
||||||
|
else
|
||||||
|
"https://accept.authorize.net/payment/payment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html
|
||||||
|
concerning :WebhookApiMethods do
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#List_My_Webhooks
|
||||||
|
def webhooks
|
||||||
|
webhook_get!("webhooks")
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Get_a_Webhook
|
||||||
|
def webhook(webhook_id)
|
||||||
|
webhook_get!("webhooks/#{webhook_id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Retrieve_Notification_History
|
||||||
|
def notifications(status: nil)
|
||||||
|
webhook_get!("notifications", params: { deliveryStatus: status }.compact)
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Retrieve_a_Specific_Notification's_History
|
||||||
|
def notification(notification_id)
|
||||||
|
webhook_get!("notifications/#{notification_id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Create_A_Webhook
|
||||||
|
def create_webhook(name:, url:, eventTypes:, status: "active")
|
||||||
|
webhook_post!("webhooks", form: { name: name, url: url, eventTypes: eventTypes, status: status })
|
||||||
|
end
|
||||||
|
|
||||||
|
def webhook_get!(path, **options)
|
||||||
|
http.basic_auth(user: login_id, pass: transaction_key).get!(webhook_url(path), **options).parse
|
||||||
|
end
|
||||||
|
|
||||||
|
def webhook_post!(path, **options)
|
||||||
|
http.basic_auth(user: login_id, pass: transaction_key).post!(webhook_url(path), **options).parse
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#API_Endpoint_Hosts
|
||||||
|
def webhook_url(path)
|
||||||
|
if test_mode
|
||||||
|
"https://apitest.authorize.net/rest/v1/#{path}"
|
||||||
|
else
|
||||||
|
"https://api.authorize.net/rest/v1/#{path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
125
app/logical/payment_transaction/authorize_net.rb
Normal file
125
app/logical/payment_transaction/authorize_net.rb
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# https://sandbox.authorize.net/
|
||||||
|
# https://developer.authorize.net/hello_world.html
|
||||||
|
# https://developer.authorize.net/api/reference/index.html
|
||||||
|
# https://developer.authorize.net/api/reference/features/accept_hosted.html
|
||||||
|
# https://developer.authorize.net/hello_world/testing_guide.html
|
||||||
|
class PaymentTransaction::AuthorizeNet < PaymentTransaction
|
||||||
|
extend Memoist
|
||||||
|
|
||||||
|
class InvalidWebhookError < StandardError; end
|
||||||
|
|
||||||
|
def create!(country: "US", allow_promotion_codes: false)
|
||||||
|
# https://developer.authorize.net/api/reference/index.html#accept-suite-get-an-accept-payment-page
|
||||||
|
response = api_client.get_hosted_payment_page(
|
||||||
|
reference_id: user_upgrade.id,
|
||||||
|
transactionType: "authCaptureTransaction",
|
||||||
|
amount: user_upgrade.price,
|
||||||
|
customer: {
|
||||||
|
id: user_upgrade.purchaser.id,
|
||||||
|
email: user_upgrade.purchaser.email_address&.address,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
button: {
|
||||||
|
text: "Pay",
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
show: false,
|
||||||
|
merchantName: Danbooru.config.canonical_app_name,
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
cardCodeRequired: true,
|
||||||
|
showCreditCard: true,
|
||||||
|
showBankAccount: false,
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
showEmail: true, requiredEmail: true, addPaymentProfile: false
|
||||||
|
},
|
||||||
|
billing_address: {
|
||||||
|
show: true,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
shipping_address: {
|
||||||
|
show: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
style: { bgColor: "blue" },
|
||||||
|
return: {
|
||||||
|
url: Routes.user_upgrade_url(user_upgrade),
|
||||||
|
cancelUrl: Routes.new_user_upgrade_url(user_id: recipient.id),
|
||||||
|
urlText: "Continue",
|
||||||
|
cancelUrlText: "Cancel",
|
||||||
|
showReceipt: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
[api_client.payment_page_url, response[:token]]
|
||||||
|
end
|
||||||
|
|
||||||
|
def refund!(reason = nil)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
concerning :WebhookMethods do
|
||||||
|
class_methods do
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Event_Types_and_Payloads
|
||||||
|
def receive_webhook(request)
|
||||||
|
verify_webhook!(request)
|
||||||
|
|
||||||
|
case request.params[:eventType]
|
||||||
|
when "net.authorize.payment.authcapture.created"
|
||||||
|
payment_completed(request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.authorize.net/api/reference/features/webhooks.html#Verifying_the_Notification
|
||||||
|
private def verify_webhook!(request)
|
||||||
|
payload = request.body.read
|
||||||
|
actual_signature = request.headers["X-Anet-Signature"].to_s
|
||||||
|
calculated_signature = "sha512=" + OpenSSL::HMAC.digest("sha512", Danbooru.config.authorize_net_signature_key, payload).unpack1("H*").upcase
|
||||||
|
raise InvalidWebhookError unless ActiveSupport::SecurityUtils::secure_compare(actual_signature, calculated_signature)
|
||||||
|
|
||||||
|
request
|
||||||
|
end
|
||||||
|
|
||||||
|
private def payment_completed(request)
|
||||||
|
# Authorize.net's shitty API sends a real request with fake values when you trigger a test webhook.
|
||||||
|
# The only way to detect a test webhook is to check for these hardcoded fake values.
|
||||||
|
if request.params.dig(:payload, :authAmount) == 12.5 && request.params.dig(:payload, :id) == "245" && request.params.dig(:payload, :authCode) == "572"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
user_upgrade_id = request.params.dig(:payload, :merchantReferenceId)
|
||||||
|
transaction_id = request.params.dig(:payload, :id)
|
||||||
|
user_upgrade = UserUpgrade.find(user_upgrade_id)
|
||||||
|
user_upgrade.update!(transaction_id: transaction_id)
|
||||||
|
user_upgrade.process_upgrade!("paid")
|
||||||
|
end
|
||||||
|
|
||||||
|
private def register_webhook
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def receipt_url
|
||||||
|
# "https://sandbox.authorize.net/ui/themes/sandbox/Transaction/TransactionReceipt.aspx?transid=#{transaction_id}" if transaction_id.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def payment_url
|
||||||
|
# "https://sandbox.authorize.net/ui/themes/sandbox/transaction/transactiondetail.aspx?transID=40092238841" if transaction_id.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def transaction
|
||||||
|
return nil if user_upgrade.transaction_id.nil?
|
||||||
|
api_client.get_transaction(user_upgrade.transaction_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_client
|
||||||
|
AuthorizeNetClient.new
|
||||||
|
end
|
||||||
|
|
||||||
|
memoize :api_client, :transaction
|
||||||
|
end
|
||||||
@@ -21,6 +21,7 @@ class UserUpgrade < ApplicationRecord
|
|||||||
|
|
||||||
enum payment_processor: {
|
enum payment_processor: {
|
||||||
stripe: 0,
|
stripe: 0,
|
||||||
|
authorize_net: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :gifted, -> { where("recipient_id != purchaser_id") }
|
scope :gifted, -> { where("recipient_id != purchaser_id") }
|
||||||
@@ -32,9 +33,9 @@ class UserUpgrade < ApplicationRecord
|
|||||||
|
|
||||||
def self.gold_price
|
def self.gold_price
|
||||||
if Danbooru.config.is_promotion?
|
if Danbooru.config.is_promotion?
|
||||||
1500
|
15.00
|
||||||
else
|
else
|
||||||
2000
|
20.00
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -46,6 +47,17 @@ class UserUpgrade < ApplicationRecord
|
|||||||
platinum_price - gold_price
|
platinum_price - gold_price
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def price
|
||||||
|
case upgrade_type
|
||||||
|
in "gold"
|
||||||
|
UserUpgrade.gold_price
|
||||||
|
in "platinum"
|
||||||
|
UserUpgrade.platinum_price
|
||||||
|
in "gold_to_platinum"
|
||||||
|
UserUpgrade.gold_to_platinum_price
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def level
|
def level
|
||||||
case upgrade_type
|
case upgrade_type
|
||||||
when "gold"
|
when "gold"
|
||||||
@@ -157,7 +169,12 @@ class UserUpgrade < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def transaction
|
def transaction
|
||||||
PaymentTransaction::Stripe.new(self)
|
case payment_processor
|
||||||
|
in "stripe"
|
||||||
|
PaymentTransaction::Stripe.new(self)
|
||||||
|
in "authorize_net"
|
||||||
|
PaymentTransaction::AuthorizeNet.new(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_receipt?
|
def has_receipt?
|
||||||
|
|||||||
@@ -1,2 +1,15 @@
|
|||||||
var stripe = Stripe("<%= j Danbooru.config.stripe_publishable_key %>");
|
<% if @user_upgrade.stripe? %>
|
||||||
stripe.redirectToCheckout({ sessionId: "<%= j @checkout.id %>" });
|
var stripe = Stripe("<%= j Danbooru.config.stripe_publishable_key %>");
|
||||||
|
stripe.redirectToCheckout({ sessionId: "<%= j @checkout.id %>" });
|
||||||
|
<% elsif @user_upgrade.authorize_net? %>
|
||||||
|
$(function() {
|
||||||
|
var url = "<%= j @checkout[0] %>";
|
||||||
|
var token = "<%= j @checkout[1] %>";
|
||||||
|
|
||||||
|
var $form = $('<form method="POST">').attr("action", url)
|
||||||
|
var $input = $('<input type="hidden" name="token">').val(token);
|
||||||
|
$form.append($input).appendTo("body").submit();
|
||||||
|
});
|
||||||
|
<% else %>
|
||||||
|
<% raise NotImplementedError, "payment method not implemented" %>
|
||||||
|
<% end %>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<% if Danbooru.config.is_promotion? %>
|
<% if Danbooru.config.is_promotion? %>
|
||||||
<s>$20</s>
|
<s>$20</s>
|
||||||
<% end %>
|
<% end %>
|
||||||
<b><%= cents_to_usd(UserUpgrade.gold_price) %></b>
|
<b><%= number_to_currency(UserUpgrade.gold_price) %></b>
|
||||||
<div class="fineprint">One time fee</div>
|
<div class="fineprint">One time fee</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<% if @user_upgrade.purchaser.is_anonymous? %>
|
<% if @user_upgrade.purchaser.is_anonymous? %>
|
||||||
<%= link_to "Get #{Danbooru.config.canonical_app_name} Gold", new_user_path(url: new_user_upgrade_path), class: "button-primary" %>
|
<%= link_to "Get #{Danbooru.config.canonical_app_name} Gold", new_user_path(url: new_user_upgrade_path), class: "button-primary" %>
|
||||||
<% elsif @user_upgrade.recipient.level <= User::Levels::MEMBER %>
|
<% elsif @user_upgrade.recipient.level <= User::Levels::MEMBER %>
|
||||||
<%= button_to "Get #{Danbooru.config.canonical_app_name} Gold", user_upgrades_path(user_id: @recipient.id, upgrade_type: "gold", country: params[:country], promo: params[:promo]), class: "button-primary", remote: true, disable_with: "Redirecting..." %>
|
<%= button_to "Get #{Danbooru.config.canonical_app_name} Gold", user_upgrades_path(user_id: @recipient.id, upgrade_type: "gold", country: params[:country], promo: params[:promo], payment_processor: "authorize_net"), class: "button-primary", remote: true, disable_with: "Redirecting..." %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= button_to "Get #{Danbooru.config.canonical_app_name} Gold", user_upgrades_path(user_id: @recipient.id), class: "button-primary", disabled: true %>
|
<%= button_to "Get #{Danbooru.config.canonical_app_name} Gold", user_upgrades_path(user_id: @recipient.id), class: "button-primary", disabled: true %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="notice notice-info notice-large" id="upgrade-account-notice">
|
<div class="notice notice-info notice-large" id="upgrade-account-notice">
|
||||||
<h2><%= link_to "Upgrade your account for only #{cents_to_usd(UserUpgrade.gold_price)}!", new_user_upgrade_path, id: "goto-upgrade-account" %></h2>
|
<h2><%= link_to "Upgrade your account for only #{number_to_currency(UserUpgrade.gold_price)}!", new_user_upgrade_path, id: "goto-upgrade-account" %></h2>
|
||||||
<div><%= link_to "No thanks", "#", id: "hide-upgrade-account-notice" %></div>
|
<div><%= link_to "No thanks", "#", id: "hide-upgrade-account-notice" %></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -402,6 +402,26 @@ module Danbooru
|
|||||||
def stripe_promotion_discount_id
|
def stripe_promotion_discount_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The login ID for Authorize.net. Used for accepting payments for user upgrades.
|
||||||
|
# Signup for a test account at https://developer.authorize.net/hello_world/sandbox.html.
|
||||||
|
def authorize_net_login_id
|
||||||
|
end
|
||||||
|
|
||||||
|
# The transaction key for Authorize.net. This is the API secret for API calls.
|
||||||
|
def authorize_net_transaction_key
|
||||||
|
end
|
||||||
|
|
||||||
|
# The signature key for Authorize.net. Used for verifying webhooks sent by Authorize.net.
|
||||||
|
# Generate at Account > Settings > Security Settings > General Security Settings > API Credentials and Keys
|
||||||
|
def authorize_net_signature_key
|
||||||
|
end
|
||||||
|
|
||||||
|
# Whether to use the test environment or the live environment for Authorize.net. The test environment
|
||||||
|
# allows testing payments without using real credit cards.
|
||||||
|
def authorize_net_test_mode
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def twitter_api_key
|
def twitter_api_key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ Rails.application.routes.draw do
|
|||||||
resources :user_name_change_requests, only: [:new, :create, :show, :index]
|
resources :user_name_change_requests, only: [:new, :create, :show, :index]
|
||||||
resources :webhooks do
|
resources :webhooks do
|
||||||
post :receive, on: :collection
|
post :receive, on: :collection
|
||||||
|
post :authorize_net, on: :collection
|
||||||
end
|
end
|
||||||
resources :wiki_pages, id: /.+?(?=\.json|\.xml|\.html)|.+/ do
|
resources :wiki_pages, id: /.+?(?=\.json|\.xml|\.html)|.+/ do
|
||||||
put :revert, on: :member
|
put :revert, on: :member
|
||||||
|
|||||||
Reference in New Issue
Block a user