1
0
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:
Sylvain 2021-04-14 17:56:04 +02:00
parent ad6926ec1a
commit ddd1ac52d6
8 changed files with 78 additions and 80 deletions

View File

@ -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 }

View File

@ -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;

View File

@ -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,10 +53,11 @@ 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];
if (event.clientAnswer.orderStatus === 'PAID') {
@ -70,11 +70,14 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
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}`}>

View File

@ -11,6 +11,10 @@ export interface ConfirmPaymentResponse {
todo?: any
}
export interface CheckHashResponse {
validity: boolean
}
export interface OrderDetails {
mode?: 'TEST' | 'PRODUCTION',
orderCurrency?: string,

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.validity !!@result # rubocop:disable Style/DoubleNegation

View File

@ -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'

View File

@ -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

View File

@ -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