Comment valider les webhooks

Les developpeurs peuvent facilement valider les webhooks reçus de la plateforme Bictorys en suivant les étapes suivantes :

Verifier le header X-Secret-Key

Chaque notification envoyée inclut le header X-Secret-Key qui contient la valeur de la clé secrete du webhook que vous avez renseignée sur votre dashboard. Vous devez verifier que la clé secrete envoyée correspond à la clé secrete enregistrée sur votre compte Bictorys avant de continuer le traitement.

Vérification des Données de la Transaction

Il est nécessaire de s'assurer que des éléments clés tels que le montant, la devise (currency), le statut de la transaction, et la référence de paiement le cas échéant sont bien présents et corrects dans la payload (données transmises).

Validation de la Commande

La commande ne doit être validée que lorsque votre système reçoit un appel de webhook (une notification automatique) confirmant le statut de la transaction. Cela garantit que le paiement a été traité avec succès avant de finaliser la commande.

/**
	 * Example of Process Webhook function.
	 */
	public function process_webhooks() {
		if ( ( strtoupper( $_SERVER['REQUEST_METHOD'] ) !== 'POST' ) ) {
			exit;
		}

		// Read and sanitize the input
		$input = file_get_contents('php://input');
		$sanitized_input = sanitize_text_field($input);
		$event = json_decode($sanitized_input, true);

		// Get all headers
		$headers = getallheaders();

		// Check for the Secret Key header
		$secret_key = isset($headers['X-Secret-Key']) ? $headers['X-Secret-Key'] : '';

		// Verify the secret key
		if ($secret_key !== $this->webhook_secret) {
			return;
		}

		// Validate necessary fields in the event data
		if (!isset($event['status'], $event['paymentReference'])) {
			exit;
		}

		// Convert status to lowercase for comparison
		$status = strtolower($event['status']);

		if ($status !== 'succeeded' && $status !== 'authorized') {
			return;
		}

		// Ensure the event object is in the correct format
		if (!is_array($event) || !isset($event['paymentReference'])) {
			exit;
		}

		// Extract order details from payment reference
		$order_details = explode('_', sanitize_text_field($event['paymentReference']));

		sleep( 10 );

		// Validate order details format
		if (count($order_details) < 1 || !is_numeric($order_details[0])) {
			exit;
		}

		$order_id = (int) $order_details[0];
		$order = wc_get_order($order_id);

		if ( ! $order ) {
			return;
		}

		// Verify merchant reference
		/*
		if ($event['merchantReference'] !== $order->get_meta('merchantReference')) {
			exit;
		}
		*/

		// Respond with a 200 HTTP status code
		http_response_code(200);

		// Check order status and exit if already processed
		$order_status = strtolower($order->get_status());
		if (in_array($order_status, ['processing', 'completed', 'on-hold'], true)) {
			exit;
		}

		// Get currency details
		$order_currency = $order->get_currency();
		$currency_symbol = get_woocommerce_currency_symbol($order_currency);
		$order_total = $order->get_total();
		$amount_paid = floatval($event['amount']);
		$bictorys_ref = sanitize_text_field($event['id']);
		$payment_currency = strtoupper(sanitize_text_field($event['currency']));
		$gateway_symbol = get_woocommerce_currency_symbol($payment_currency);

		// check if the amount paid is equal to the order amount.
		if ( $amount_paid < $order_total ) {

			$order->update_status( 'on-hold', '' );

			$order->add_meta_data( '_transaction_id', $bictorys_ref, true );
			/*
			 * translators:
			 * %1$s: Line break
			 * %2$s: Line break
			 * %3$s: Line break
			 */
			$notice      = sprintf( __( 'Thank you for shopping with us.%1$sYour payment transaction was successful, but the amount paid is not the same as the total order amount.%2$sYour order is currently on hold.%3$sKindly contact us for more information regarding your order and payment status.', 'bictorys-payment-gateway-for-woocommerce' ), '<br />', '<br />', '<br />' );
			$notice_type = 'notice';

			// Add Customer Order Note.
			$order->add_order_note( $notice, 1 );

      in_order_note = sprintf( __( '<strong>Look into this order</strong>%1$sThis order is currently on hold.%2$sReason: Amount paid is less than the total order amount.%3$sAmount Paid was <strong>%4$s (%5$s)</strong> while the total order amount is <strong>%6$s (%7$s)</strong>%8$s<strong>Bictorys Transaction Reference:</strong> %9$s', 'bictorys-payment-gateway-for-woocommerce' ), '<br />', '<br />', '<br />', $currency_symbol, $amount_paid, $currency_symbol, $order_total, '<br />', $bictorys_ref );
			$order->add_order_note( $admin_order_note );

			function_exists( 'wc_reduce_stock_levels' ) ? wc_reduce_stock_levels( $order_id ) : $order->reduce_order_stock();

			wc_add_notice( $notice, $notice_type );

			WC()->cart->empty_cart();

		} else {

			if ( $payment_currency !== $order_currency ) {

				$order->update_status( 'on-hold', '' );

				$order->update_meta_data( '_transaction_id', $bictorys_ref );
				/*
				 * translators:
				 * %1$s: Line break
				 * %2$s: Line break
				 * %3$s: Line break
				 */
				$notice      = sprintf( __( 'Thank you for shopping with us.%1$sYour payment was successful, but the payment currency is different from the order currency.%2$sYour order is currently on-hold.%3$sKindly contact us for more information regarding your order and payment status.', 'bictorys-payment-gateway-for-woocommerce' ), '<br />', '<br />', '<br />' );
				$notice_type = 'notice';

				// Add Customer Order Note.
				$order->add_order_note( $notice, 1 );

				$admin_order_note = sprintf( __( '<strong>Look into this order</strong>%1$sThis order is currently on hold.%2$sReason: Order currency is different from the payment currency.%3$sOrder Currency is <strong>%4$s (%5$s)</strong> while the payment currency is <strong>%6$s (%7$s)</strong>%8$s<strong>Bictorys Transaction Reference:</strong> %9$s', 'bictorys-payment-gateway-for-woocommerce' ), '<br />', '<br />', '<br />', $order_currency, $currency_symbol, $payment_currency, $gateway_symbol, '<br />', $bictorys_ref );
				$order->add_order_note( $admin_order_note );

				function_exists( 'wc_reduce_stock_levels' ) ? wc_reduce_stock_levels( $order_id ) : $order->reduce_order_stock();

				wc_add_notice( $notice, $notice_type );

			} else {

				$order->payment_complete( $bictorys_ref );

				/*
				 * translators: %s: Transaction Reference.
				 */
				$order->add_order_note( sprintf( __( 'Payment via Bictorys successful (Transaction Reference: %s)', 'bictorys-payment-gateway-for-woocommerce' ), $bictorys_ref ) );


				WC()->cart->empty_cart();

				if ( $this->is_autocomplete_order_enabled( $order ) ) {
					$order->update_status( 'completed' );
				}
			}
		}

		$order->save();

		exit;
	}

📘

Note

L'objet de la payload du webhook est définie dans le sdk client disponible à la demande.


/**
 * Exemple de payload contenu dans le body du webhook.
 */
	{
    "id": "33e1c83b-7cb0-437b-bc50-a7a58e5660ad",
    "merchantId": "d2d2053b-638d-4133-957e-3caf63e6b79c",
    "type": "payment",
    "amount": 100,
    "currency": "XOF",
    "paymentReference": "33e1c83b-7cb0-437b-bc50-a7a58e5660ad",
    "customerId": "33e1c83b-7cb0-437b-bc50-a7a58e5660ad",
    "customerObject": {
      "id": "fbd2053b-638d-4133-957e-3caf63e6b79c",
      "name": "John Dao",
      "phone": 22505234567890,
      "email": "[email protected]",
      "address": "23, avenue de la liberté",
      "city": "Dakar",
      "postalCode": 0,
      "country": "SN",
      "locale": "fr-FR",
      "createdAt": "2022-06-20T17:17:11Z",
      "updatedAt": "2022-06-20T17:17:11Z"
    },
    "pspName": "orange_money",
    "paymentMeans": "+221 *** ** 09",
    "paymentChannel": "Terminal",
    "merchantFees": 25,
    "customerFees": 0,
    "transactionFeeAmountHT": 25,
    "transactionFeeAmountTax": 0,
    "merchantReference": "33e1c83b-7cb0-437b-bc50-a7a58e5660ad",
    "orderType": "invoice",
    "orderId": "14e1c83b-7cb0-437b-bc50-a7a58e5660ad",
    "status": "succeeded",
    "deviceId": "SN-4562345",
    "originIp": "192.0.0.1",
    "timestamp": "2022-06-20T17:17:11Z"
}
/**
 * Possible valeur du statut du paiement
 */
    PaymentStatus:
      description: status of the transaction
      type: string
      enum:
        - succeeded
        - failed
        - cancelled
        - pending
        - processing
        - reversed
        - authorized