mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
save reservation|subscription to db after payment
This commit is contained in:
parent
ad6926ec1a
commit
ddd1ac52d6
@ -28,32 +28,36 @@ class API::PayzenController < API::PaymentsController
|
||||
@result
|
||||
end
|
||||
|
||||
def check_hash
|
||||
@result = PayZen::Helper.check_hash(params[:algorithm], params[:hash_key], params[:hash], params[:data])
|
||||
end
|
||||
|
||||
def confirm_payment
|
||||
render(json: { error: 'Bad gateway or online payment is disabled' }, status: :bad_gateway) and return unless PayZen::Helper.enabled?
|
||||
|
||||
client = PayZen::Order.new
|
||||
order = client.get(params[:order_id])
|
||||
order = client.get(params[:order_id], operation_type: 'DEBIT')
|
||||
|
||||
amount = card_amount
|
||||
|
||||
if order[:transactions].first.status == 'PAID'
|
||||
if order['answer']['transactions'].first['status'] == 'PAID'
|
||||
if params[:cart_items][:reservation]
|
||||
res = on_reservation_success(intent, amount[:details])
|
||||
res = on_reservation_success(params[:order_id], amount[:details])
|
||||
elsif params[:cart_items][:subscription]
|
||||
res = on_subscription_success(intent, amount[:details])
|
||||
res = on_subscription_success(params[:order_id], amount[:details])
|
||||
end
|
||||
end
|
||||
|
||||
render generate_payment_response(intent, res)
|
||||
render res
|
||||
rescue StandardError => e
|
||||
render json: e, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def on_reservation_success(intent, details)
|
||||
def on_reservation_success(order_id, details)
|
||||
@reservation = Reservation.new(reservation_params)
|
||||
payment_method = params[:cart_items][:reservation][:payment_method] || 'stripe'
|
||||
payment_method = params[:cart_items][:reservation][:payment_method] || 'payzen'
|
||||
user_id = if current_user.admin? || current_user.manager?
|
||||
params[:cart_items][:reservation][:user_id]
|
||||
else
|
||||
@ -62,17 +66,9 @@ class API::PayzenController < API::PaymentsController
|
||||
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)
|
||||
.pay_and_save(@reservation,
|
||||
payment_details: details,
|
||||
intent_id: intent.id,
|
||||
intent_id: order_id, # TODO: change to gateway_id
|
||||
schedule: params[:cart_items][:reservation][:payment_schedule],
|
||||
payment_method: payment_method)
|
||||
if intent.class == Stripe::PaymentIntent
|
||||
Stripe::PaymentIntent.update(
|
||||
intent.id,
|
||||
{ description: "Invoice reference: #{@reservation.invoice.reference}" },
|
||||
{ api_key: Setting.get('stripe_secret_key') }
|
||||
)
|
||||
end
|
||||
|
||||
if is_reserve
|
||||
SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible
|
||||
|
||||
@ -82,7 +78,7 @@ class API::PayzenController < API::PaymentsController
|
||||
end
|
||||
end
|
||||
|
||||
def on_subscription_success(intent, details)
|
||||
def on_subscription_success(order_id, details)
|
||||
@subscription = Subscription.new(subscription_params)
|
||||
user_id = if current_user.admin? || current_user.manager?
|
||||
params[:cart_items][:subscription][:user_id]
|
||||
@ -92,16 +88,9 @@ class API::PayzenController < API::PaymentsController
|
||||
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)
|
||||
.pay_and_save(@subscription,
|
||||
payment_details: details,
|
||||
intent_id: intent.id,
|
||||
intent_id: order_id, # TODO: change to gateway_id
|
||||
schedule: params[:cart_items][:subscription][:payment_schedule],
|
||||
payment_method: 'stripe')
|
||||
if intent.class == Stripe::PaymentIntent
|
||||
Stripe::PaymentIntent.update(
|
||||
intent.id,
|
||||
{ description: "Invoice reference: #{@subscription.invoices.first.reference}" },
|
||||
{ api_key: Setting.get('stripe_secret_key') }
|
||||
)
|
||||
end
|
||||
payment_method: 'payzen')
|
||||
|
||||
if is_subscribe
|
||||
{ template: 'api/subscriptions/show', status: :created, location: @subscription }
|
||||
|
@ -2,7 +2,7 @@ import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { CartItems } from '../models/payment';
|
||||
import { User } from '../models/user';
|
||||
import { ConfirmPaymentResponse, CreatePaymentResponse, SdkTestResponse } from '../models/payzen';
|
||||
import { CheckHashResponse, ConfirmPaymentResponse, CreatePaymentResponse, SdkTestResponse } from '../models/payzen';
|
||||
|
||||
export default class PayzenAPI {
|
||||
|
||||
@ -16,6 +16,11 @@ export default class PayzenAPI {
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async checkHash(algorithm: string, hashKey: string, hash: string, data: string): Promise<CheckHashResponse> {
|
||||
const res: AxiosResponse = await apiClient.post('/api/payzen/check_hash', { algorithm, hash_key: hashKey, hash, data });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async confirm(orderId: string, cartItems: CartItems): Promise<ConfirmPaymentResponse> {
|
||||
const res: AxiosResponse = await apiClient.post('/api/payzen/confirm_payment', { cart_items: cartItems, order_id: orderId });
|
||||
return res?.data;
|
||||
|
@ -30,14 +30,13 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
const { t } = useTranslation('shared');
|
||||
const PayZenKR = useRef<KryptonClient>(null);
|
||||
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
||||
const [hmacKey, setHmacKey] = useState<string>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const api = new SettingAPI();
|
||||
api.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey, SettingName.PayZenHmacKey]).then(settings => {
|
||||
setHmacKey(settings.get(SettingName.PayZenHmacKey));
|
||||
api.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey]).then(settings => {
|
||||
PayzenAPI.chargeCreatePayment(cartItems, customer).then(formToken => {
|
||||
KRGlue.loadLibrary(settings.get(SettingName.PayZenEndpoint), settings.get(SettingName.PayZenPublicKey)) /* Load the remote library */
|
||||
// Load the remote library
|
||||
KRGlue.loadLibrary(settings.get(SettingName.PayZenEndpoint), settings.get(SettingName.PayZenPublicKey))
|
||||
.then(({ KR }) =>
|
||||
KR.setFormConfig({
|
||||
formToken: formToken.formToken,
|
||||
@ -54,27 +53,31 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
|
||||
/**
|
||||
* Callback triggered on PayZen successful payments
|
||||
* @see https://docs.lyra.com/fr/rest/V4.0/javascript/features/reference.html#kronsubmit
|
||||
*/
|
||||
const onPaid = (event: ProcessPaymentAnswer): boolean => {
|
||||
// TODO check hash
|
||||
PayzenAPI.checkHash(event.hashAlgorithm, event.hashKey, event.hash, event.rawClientAnswer).then(async (hash) => {
|
||||
if (hash.validity) {
|
||||
const transaction = event.clientAnswer.transactions[0];
|
||||
|
||||
const transaction = event.clientAnswer.transactions[0];
|
||||
|
||||
if (event.clientAnswer.orderStatus === 'PAID') {
|
||||
PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cartItems).then(() => {
|
||||
PayZenKR.current.removeForms().then(() => {
|
||||
onSuccess(event.clientAnswer);
|
||||
});
|
||||
})
|
||||
} else {
|
||||
const error = `${transaction?.errorMessage}. ${transaction?.detailedErrorMessage || ''}`;
|
||||
onError(error || event.clientAnswer.orderStatus);
|
||||
}
|
||||
if (event.clientAnswer.orderStatus === 'PAID') {
|
||||
PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cartItems).then(() => {
|
||||
PayZenKR.current.removeForms().then(() => {
|
||||
onSuccess(event.clientAnswer);
|
||||
});
|
||||
})
|
||||
} else {
|
||||
const error = `${transaction?.errorMessage}. ${transaction?.detailedErrorMessage || ''}`;
|
||||
onError(error || event.clientAnswer.orderStatus);
|
||||
}
|
||||
}
|
||||
})
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the PayZen form was entirely loaded and displayed
|
||||
* @see https://docs.lyra.com/fr/rest/V4.0/javascript/features/reference.html#%C3%89v%C3%A9nements
|
||||
*/
|
||||
const handleFormReady = () => {
|
||||
setLoadingClass('hidden');
|
||||
@ -82,6 +85,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
|
||||
/**
|
||||
* Callback triggered when the PayZen form has started to show up but is not entirely loaded
|
||||
* @see https://docs.lyra.com/fr/rest/V4.0/javascript/features/reference.html#%C3%89v%C3%A9nements
|
||||
*/
|
||||
const handleFormCreated = () => {
|
||||
setLoadingClass('loader-overlay');
|
||||
@ -89,6 +93,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
|
||||
/**
|
||||
* Callback triggered when the PayZen payment was refused
|
||||
* @see https://docs.lyra.com/fr/rest/V4.0/javascript/features/reference.html#kronerror
|
||||
*/
|
||||
const handleError = (answer: KryptonError) => {
|
||||
const message = `${answer.errorMessage}. ${answer.detailedErrorMessage ? answer.detailedErrorMessage : ''}`;
|
||||
@ -115,40 +120,6 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const checkHash = (answer: ProcessPaymentAnswer, key: string = hmacKey): boolean => {
|
||||
/*
|
||||
TODO: convert the following to JS
|
||||
|
||||
## Check Kr-Answser object signature
|
||||
def check_hash(answer, key = nil)
|
||||
supported_hash_algorithm = ['sha256_hmac']
|
||||
|
||||
# check if the hash algorithm is supported
|
||||
unless supported_hash_algorithm.include? answer[:hashAlgorithm]
|
||||
raise PayzenError("hash algorithm not supported: #{answer[:hashAlgorithm]}. Update your SDK")
|
||||
end
|
||||
|
||||
# if key is not defined, we use kr-hash-key parameter to choose it
|
||||
if key.nil?
|
||||
if answer[:hashKey] == 'sha256_hmac'
|
||||
key = Setting.get('payzen_hmac')
|
||||
elsif answer[:hashKey] == 'password'
|
||||
key = Setting.get('payzen_password')
|
||||
else
|
||||
raise PayzenError('invalid hash-key parameter')
|
||||
end
|
||||
end
|
||||
|
||||
hash = OpenSSL::HMAC.hexdigest('SHA256', key, answer[:rawClientAnswer])
|
||||
|
||||
# return true if calculated hash and sent hash are the same
|
||||
hash == answer[:hash]
|
||||
end
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
const Loader: FunctionComponent = () => {
|
||||
return (
|
||||
<div className={`fa-3x ${loadingClass}`}>
|
||||
|
@ -11,6 +11,10 @@ export interface ConfirmPaymentResponse {
|
||||
todo?: any
|
||||
}
|
||||
|
||||
export interface CheckHashResponse {
|
||||
validity: boolean
|
||||
}
|
||||
|
||||
export interface OrderDetails {
|
||||
mode?: 'TEST' | 'PRODUCTION',
|
||||
orderCurrency?: string,
|
||||
|
3
app/views/api/payzen/check_hash.json.jbuilder
Normal file
3
app/views/api/payzen/check_hash.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.validity !!@result # rubocop:disable Style/DoubleNegation
|
@ -183,6 +183,7 @@ Rails.application.routes.draw do
|
||||
post 'payzen/sdk_test' => 'payzen#sdk_test'
|
||||
post 'payzen/create_payment' => 'payzen#create_payment'
|
||||
post 'payzen/confirm_payment' => 'payzen#confirm_payment'
|
||||
post 'payzen/check_hash' => 'payzen#check_hash'
|
||||
|
||||
# FabAnalytics
|
||||
get 'analytics/data' => 'analytics#data'
|
||||
|
@ -23,7 +23,8 @@ class PayZen::Helper
|
||||
require 'sha3'
|
||||
|
||||
content = { cart_items: cart_items, customer: customer }.to_json + DateTime.current.to_s
|
||||
SHA3::Digest.hexdigest(:sha256, content)[0...12]
|
||||
# It's safe to truncate a hash. See https://crypto.stackexchange.com/questions/74646/sha3-255-one-bit-less
|
||||
SHA3::Digest.hexdigest(:sha224, content)[0...24]
|
||||
end
|
||||
|
||||
## Generate a hash map compatible with PayZen 'V4/Customer/Customer'
|
||||
@ -50,5 +51,29 @@ class PayZen::Helper
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
## Check the PayZen signature for integrity
|
||||
def check_hash(algorithm, hash_key, hash_proof, data, key = nil)
|
||||
supported_hash_algorithm = ['sha256_hmac']
|
||||
|
||||
# check if the hash algorithm is supported
|
||||
raise PayzenError("hash algorithm not supported: #{algorithm}. Update your SDK") unless supported_hash_algorithm.include? algorithm
|
||||
|
||||
# if key is not defined, we use kr-hash-key parameter to choose it
|
||||
if key.nil?
|
||||
if hash_key == 'sha256_hmac'
|
||||
key = Setting.get('payzen_hmac')
|
||||
elsif hash_key == 'password'
|
||||
key = Setting.get('payzen_password')
|
||||
else
|
||||
raise PayzenError('invalid hash-key parameter')
|
||||
end
|
||||
end
|
||||
|
||||
hash = OpenSSL::HMAC.hexdigest('SHA256', key, data)
|
||||
|
||||
# return true if calculated hash and sent hash are the same
|
||||
hash == hash_proof
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -12,7 +12,7 @@ class PayZen::Order < PayZen::Client
|
||||
# @see https://payzen.io/en-EN/rest/V4.0/api/playground/Order/Get/
|
||||
##
|
||||
def get(order_id, operation_type: nil)
|
||||
post('/Transaction/Get/', orderId: order_id, operationType: operation_type)
|
||||
post('/Order/Get/', orderId: order_id, operationType: operation_type)
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user