Skip to content

Notifications

Cecile Veneziani edited this page · 2 revisions
Clone this wiki locally

Adyen sends notifications to your server to inform you about any activity on their side. Adyen is able to send these notifications as SOAP requests or as simple HTTP POST requests. For now, this library only supports handling HTTP POST notifications, because handling SOAP requests would require setting up a SOAP server.

It is advised to always store every (non-duplicate) notification that you receive for accountability reasons. Because of this, the library provides an ActiveRecord based class to store them in the database.

Creating the notification model

Generate the notification model and its migration with:

$ rails g adyen:notification
$ rake db:migrate

Receiving notifications via HTTP POST

In the Adyen merchant environment, you can provide an URL to which the notifications should be posted. Make sure that you choose HTTP POST as method instead of SOAP. For security reasons, it's best to choose an https-based url and use authentication credentials as well.

Adyen sends the notification details as POST parameters to your action. For more information about what information is being sent, please refer to the Adyen integration manual. You can store all this information from the request into a record with Adyen::Notification::HttpPost.log(request). You can access all fields using snake_case notation instead of the camelCase notation Adyen uses, so eventCode becomes @notification.event_code. After it has been stored, you can perform some action based on its contents. After the notification has been handled, you can set the processed field to true.

An example controller implementation:

class AdyenController < ActionController::Base
  before_filter :authenticate

  # POST https://example.com/adyen/notify
  def notify
    @notification = AdyenNotification.log(params)
    @notification.handle!
  rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
    # Validation failed, because of the duplicate check.
    # So ignore this notification, it is already stored and handled.
  ensure
    # Always return that we have accepted the notification
    render :text => '[accepted]'
  end
  protected 

  # Enable HTTP basic authentication
  def authenticate
    authenticate_or_request_with_http_basic do |username, password|
      username == 'something_you_chose' && password == 'very$ecret'
    end
  end
end

And the corresponding AdyenNotification model : (you can retrieve the original notification in all the notifications associated to a 'AUTHORIZATION' notification)

# app/models/AdyenNotification.rb
class AdyenNotification < ActiveRecord::Base
  belongs_to :user, :class_name => "User", :foreign_key => "merchant_reference"
  belongs_to :original_notification, :class_name => "AdyenNotification", :foreign_key => "original_reference", :primary_key => "psp_reference"

  def handle!
    if success?
      case event_code
      when 'AUTHORISATION'

        # A payment authorized successfully, so handle the payment
        # ...
        # flag the notification so we know that it has been processed
        # User.find(shopper_reference.to_i).renew_premium!(1.month)
        time_of_premiumness = AMOUNTS.invert[(value * 100).to_i].months
        User.find(merchant_reference.to_i).renew_premium!(time_of_premiumness)
        update_column(:processed, true)
      when 'CANCEL_OR_REFUND'
        time_of_premiumness = AMOUNTS.invert[(original_notification.value * 100).to_i].months
        User.find(merchant_reference.to_i).cancel_premium!(time_of_premiumness)
        update_column(:processed, true)
      when 'RECURRING_CONTRACT'
        # Handle a new recurring contract
        # ...
        update_column(:processed, true)
      end
    end
  end
end

To test that, you just have to test your model with cucumber:

Feature: Notifications
  In order to be more accurate
  As an admin
  I want to be able to refund or cancel some payment

  Background:
    And I have signed in with "email@person.com/password"

  Scenario: Refund someone
    Given The application receive a valid subscription request for user "email@person.com"
    Then "email@person.com" should be a premium member
    Given The application receive a valid cancel or refund request for user "email@person.com"
    Then "email@person.com" should not be a premium member

And in your adyen_steps.rb

Given /^The application receive a valid subscription request for user "([^\"]*)"$/ do |email|
  sub_notif = Factory.create :subscription_notification, :user => User.find_by_email(email)
  sub_notif.handle!
end


Given /^The application receive a valid cancel or refund request for user "([^\"]*)"$/ do |email|
  u = User.find_by_email(email)
  corn = Factory.create :cancel_or_refund_notification, :original_notification => u.last_successful_operation, :user => u
  corn.handle!
end

Then /^"([^\"]*)" should be a premium member$/ do |email|
  User.find_by_email(email).premium_member?.should be(true)
end

Then /^"([^\"]*)" should not be a premium member$/ do |email|
  User.find_by_email(email).premium_member?.should be(false)
end

And in your Factorygirl factories:

Factory.define :user do |user|
  user.first_name "Kevin"
end

Factory.sequence :psp_reference do |n|
  "psp#{n}"
end

Factory.define :adyen_notification do |notification|
  notification.live                   false
  notification.user                   {Factory{:user}}
  notification.merchant_account_code  "mac"
  notification.event_date             Time.now
  notification.processed              false
  notification.psp_reference          {Factory.next :psp_reference}
  notification.currency               "EUR"
end

Factory.define :successful_adyen_notification, :parent => :adyen_notification do |notification|
  notification.success                true
end

Factory.define :subscription_notification, :parent => :successful_adyen_notification do |notification|
  notification.event_code             "AUTHORISATION"
  notification.payment_method         "visa"
  notification.operations             "CANCEL CAPTURE REFUND"
  notification.reason                 "40735:1111:12/2012"
  notification.value                  74.97
  notification.processed              true
end

Factory.define :cancel_or_refund_notification, :parent => :successful_adyen_notification do |notification|
  notification.event_code             "CANCEL_OR_REFUND"
  notification.original_notification  {Factory{:subscription_notification}}
end

The Adyen::Notification model uses validations to check whether the notification is a duplicate. Duplicates can safely be ignored.

Something went wrong with that request. Please try again.