Skip to content

Commit

Permalink
Added reversals
Browse files Browse the repository at this point in the history
Added frontend bindings & respective views
  • Loading branch information
Kwok He Chu committed Jul 17, 2023
1 parent 442a27a commit b2a86c2
Show file tree
Hide file tree
Showing 19 changed files with 599 additions and 112 deletions.
88 changes: 71 additions & 17 deletions authorisation-adjustment-example/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
using adyen_dotnet_authorisation_adjustment_example.Options;
using adyen_dotnet_authorisation_adjustment_example.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace adyen_dotnet_authorisation_adjustment_example.Controllers
{
Expand All @@ -19,8 +20,8 @@ public class AdminController : Controller
private readonly IModificationsService _modificationsService;
private readonly ILogger<AdminController> _logger;
private readonly string _merchantAccount;
public AdminController(IHotelPaymentRepository repository, IModificationsService modificationsService, IOptions<AdyenOptions> options, ILogger<AdminController> logger)

public AdminController(IHotelPaymentRepository repository, IModificationsService modificationsService, IOptions<AdyenOptions> options, ILogger<AdminController> logger)
{
_repository = repository;
_merchantAccount = options.Value.ADYEN_MERCHANT_ACCOUNT;
Expand All @@ -38,14 +39,63 @@ public IActionResult Index()
{
hotelPayments.Add(kvp.Value);
}

hotelPayments.Add(new HotelPaymentModel()
{
Amount = 1234,
Currency = "EUR",
DateTime = DateTime.UtcNow,
PaymentMethodBrand = "scheme",
PaymentMethodType = "mc",
PspReference = "psp-1",
Reference = "ref-1",
ResultCode = "Authorised"
});

hotelPayments.Add(new HotelPaymentModel()
{
Amount = 5678,
Currency = "EUR",
DateTime = DateTime.UtcNow,
PaymentMethodBrand = "scheme",
PaymentMethodType = "visa",
PspReference = "psp-2",
Reference = "ref-2",
ResultCode = "Authorised"
});
ViewBag.HotelPayments = hotelPayments;
return View();
}

[HttpGet("admin/result/{status}/{pspReference}")]
public IActionResult Result(string pspReference, string status, [FromQuery(Name = "reason")] string refusalReason)
{
string msg;
string img;
switch (status)
{
case "received":
msg = $"Request received for {pspReference}.";
img = "success";
break;
default:
msg = $"Error! Refusal reason: {refusalReason}";
img = "failed";
break;
}
ViewBag.Status = status;
ViewBag.Msg = msg;
ViewBag.Img = img;
return View();
}


[HttpPost("admin/update-payment-amount")]
public async Task<ActionResult<PaymentAmountUpdateResource>> UpdatePaymentAmount(UpdatePaymentAmountRequest request, CancellationToken cancellationToken = default)
public async Task<ActionResult<PaymentAmountUpdateResource>> UpdatePaymentAmount([FromBody] UpdatePaymentAmountRequest request, CancellationToken cancellationToken = default)
{
if (!_repository.HotelPayments.TryGetValue(request.PspReference, out var hotelPayment))
var hotelPayment = _repository.GetByPspReference(request.PspReference);

if (hotelPayment == null)
{
return NotFound();
}
Expand All @@ -70,10 +120,12 @@ public async Task<ActionResult<PaymentAmountUpdateResource>> UpdatePaymentAmount
}
}

[HttpPost("admin/create-capture")]
public async Task<ActionResult<PaymentCaptureResource>> CreateCapture(CreateCaptureRequest request, CancellationToken cancellationToken = default)
[HttpPost("admin/capture-payment")]
public async Task<ActionResult<PaymentCaptureResource>> CapturePayment([FromBody] CreateCapturePaymentRequest request, CancellationToken cancellationToken = default)
{
if (!_repository.HotelPayments.TryGetValue(request.PspReference, out var hotelPayment))
var hotelPayment = _repository.GetByPspReference(request.PspReference);

if (hotelPayment == null)
{
return NotFound();
}
Expand All @@ -83,37 +135,39 @@ public async Task<ActionResult<PaymentCaptureResource>> CreateCapture(CreateCapt
var createPaymentCaptureRequest = new CreatePaymentCaptureRequest()
{
MerchantAccount = _merchantAccount, // Required.
Amount = new Amount() { Value = request.Amount, Currency = hotelPayment.Currency }, // Required.
Amount = new Amount() { Value = hotelPayment.Amount, Currency = hotelPayment.Currency }, // Required.
Reference = hotelPayment.Reference
};

var response = await _modificationsService.CaptureAuthorisedPaymentAsync(request.PspReference, createPaymentCaptureRequest, cancellationToken: cancellationToken);
return Ok(response); // Note that the response will have a DIFFERENT PSPReference compared to the initial preauth
return Ok(response); // Note that the response will have a different PSPReference compared to the initial preauthorisation.
}
catch (HttpClientException e)
{
_logger.LogError(e.ToString());
return BadRequest();
}
}
[HttpPost("admin/cancel-authorised-payment")]
public async Task<ActionResult<PaymentCancelResource>> CancelAuthorisedPaymentRequest(CancelAuthorisedPaymentRequest request, CancellationToken cancellationToken = default)

[HttpPost("admin/reversal-payment")]
public async Task<ActionResult<PaymentReversalResource>> ReversalPayment([FromBody] CreateReversalPaymentRequest request, CancellationToken cancellationToken = default)
{
if (!_repository.HotelPayments.TryGetValue(request.PspReference, out var hotelPayment))
var hotelPayment = _repository.GetByPspReference(request.PspReference);

if (hotelPayment == null)
{
return NotFound();
}

try
{
var createPaymentCancelRequest = new CreatePaymentCancelRequest()
var createPaymentReversalRequest = new CreatePaymentReversalRequest()
{
MerchantAccount = _merchantAccount, // Required.
Reference = hotelPayment.Reference
};
var response = await _modificationsService.CancelAuthorisedPaymentByPspReferenceAsync(request.PspReference, createPaymentCancelRequest, cancellationToken: cancellationToken);

var response = await _modificationsService.RefundOrCancelPaymentAsync(request.PspReference, createPaymentReversalRequest, cancellationToken: cancellationToken);
return Ok(response);
}
catch (HttpClientException e)
Expand Down
4 changes: 2 additions & 2 deletions authorisation-adjustment-example/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public async Task<ActionResult<PaymentResponse>> PreAuthorisation(PaymentRequest
MerchantAccount = _merchantAccount, // Required.
Reference = orderRef.ToString(), // Required.
Channel = PaymentRequest.ChannelEnum.Web,
Amount = new Adyen.Model.Checkout.Amount("EUR", 24999), // Value is 249.99€ in minor units.
Amount = new Amount("EUR", 24999), // Value is 249.99€ in minor units.

// Required for 3DS2 redirect flow.
ReturnUrl = $"{_urlService.GetHostUrl()}/api/handleShopperRedirect?orderRef={orderRef}",
Expand Down Expand Up @@ -165,7 +165,7 @@ public async Task<ActionResult<PaymentResponse>> PreAuthorisation(PaymentRequest
PaymentMethodType = response.PaymentMethod?.Type
};

_repository.Upsert(hotelPayment);
_repository.Insert(hotelPayment);
return Ok(response);
}
catch (Adyen.HttpClient.HttpClientException e)
Expand Down
163 changes: 157 additions & 6 deletions authorisation-adjustment-example/Controllers/WebhookController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,36 @@ public async Task<ActionResult<string>> Webhooks(NotificationRequest notificatio
return BadRequest("[not accepted invalid hmac key]");
}

// Process authorisation notification asynchronously.
// Process AUTHORISATION notification asynchronously.
// Read more about authorisation here: https://docs.adyen.com/get-started-with-adyen/payment-glossary#authorisation
await ProcessAuthorisationNotificationAsync(container.NotificationItem);

// Process capture notification asynchronously.
// Process AUTHORISATION_ADJUSTMENT notification asynchronously.
// Documentation: https://docs.adyen.com/online-payments/adjust-authorisation#adjust-authorisation
await ProcessAuthorisationAdjustmentNotificationAsync(container.NotificationItem);

// Process capture notification asynchronously.
// Process CAPTURE notification asynchronously.
// Documentation: https://docs.adyen.com/online-payments/capture
await ProcessCaptureNotificationAsync(container.NotificationItem);

// Process CAPTURE_FAILED notification asynchronously.
// Testing this scenario: https://docs.adyen.com/online-payments/classic-integrations/modify-payments/capture#testing-failed-captures
// Failure reasons: https://docs.adyen.com/online-payments/capture/failure-reasons
await ProcessCaptureFailedNotificationAsync(container.NotificationItem);

// Process CANCEL_OR_REFUND notification asychronously (also known as: `reversals`).
// Documentation: https://docs.adyen.com/online-payments/reversal
await ProcessCancelOrRefundNotificationAsync(container.NotificationItem);

// Process REFUND_FAILED notification asychronously.
// Documentation: https://docs.adyen.com/online-payments/refund#refund-failed
// Testing this scenario: https://docs.adyen.com/online-payments/refund#testing-failed-refunds
await ProcessRefundFailedNotificationAsync(container.NotificationItem);

// Process REFUNDED_REVERSED notification asynchronously.
// Documentation: https://docs.adyen.com/online-payments/refund#refunded-reversed
await ProcessRefundedReversedNotificationAsync(container.NotificationItem);

return Ok("[accepted]");
}
catch (Exception e)
Expand All @@ -71,7 +92,6 @@ public async Task<ActionResult<string>> Webhooks(NotificationRequest notificatio

private Task ProcessAuthorisationNotificationAsync(NotificationRequestItem notification)
{
// Regardless of a success or not, you would probably want to update your backend/database or (preferably) send the event to a queue.
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
Expand Down Expand Up @@ -103,7 +123,6 @@ private Task ProcessAuthorisationNotificationAsync(NotificationRequestItem notif

private Task ProcessAuthorisationAdjustmentNotificationAsync(NotificationRequestItem notification)
{
// Regardless of a success or not, you would probably want to update your backend/database or (preferably) send the event to a queue.
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
Expand Down Expand Up @@ -135,7 +154,6 @@ private Task ProcessAuthorisationAdjustmentNotificationAsync(NotificationRequest

private Task ProcessCaptureNotificationAsync(NotificationRequestItem notification)
{
// Regardless of a success or not, you would probably want to update your backend/database or (preferably) send the event to a queue.
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
Expand Down Expand Up @@ -164,5 +182,138 @@ private Task ProcessCaptureNotificationAsync(NotificationRequestItem notificatio

return Task.CompletedTask;
}

private Task ProcessCaptureFailedNotificationAsync(NotificationRequestItem notification)
{
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
_logger.LogInformation($"Webhook unsuccessful: {notification.Reason} \n" +
$"EventCode: {notification.EventCode} \n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

if (notification.EventCode != "CAPTURE_FAILED")
{
return Task.CompletedTask;
}

if (!_repository.HotelPayments.TryGetValue(notification.PspReference, out Models.HotelPaymentModel model))
{
return Task.CompletedTask;
}

// Perform your business logic here, you would probably want to process the success:true event to update your backend. We log it for now.
_logger.LogInformation($"Received successful capture webhook::\n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

private Task ProcessCancelOrRefundNotificationAsync(NotificationRequestItem notification)
{
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
_logger.LogInformation($"Webhook unsuccessful: {notification.Reason} \n" +
$"EventCode: {notification.EventCode} \n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

if (notification.EventCode != "CANCEL_OR_REFUND")
{
return Task.CompletedTask;
}

if (!_repository.HotelPayments.TryGetValue(notification.PspReference, out Models.HotelPaymentModel model))
{
return Task.CompletedTask;
}

// Perform your business logic here, you would probably want to process the success:true event to update your backend. We log it for now.
_logger.LogInformation($"Received successful capture webhook::\n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

private Task ProcessRefundFailedNotificationAsync(NotificationRequestItem notification)
{
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
_logger.LogInformation($"Webhook unsuccessful: {notification.Reason} \n" +
$"EventCode: {notification.EventCode} \n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

if (notification.EventCode != "REFUND_FAILED")
{
return Task.CompletedTask;
}

if (!_repository.HotelPayments.TryGetValue(notification.PspReference, out Models.HotelPaymentModel model))
{
return Task.CompletedTask;
}

// Perform your business logic here, you would probably want to process the success:true event to update your backend. We log it for now.
_logger.LogInformation($"Received successful capture webhook::\n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}


private Task ProcessRefundedReversedNotificationAsync(NotificationRequestItem notification)
{
if (!notification.Success)
{
// Perform your business logic here, you would probably want to process the success:false event to update your backend. We log it for now.
_logger.LogInformation($"Webhook unsuccessful: {notification.Reason} \n" +
$"EventCode: {notification.EventCode} \n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}

if (notification.EventCode != "REFUNDED_REVERSED")
{
return Task.CompletedTask;
}

if (!_repository.HotelPayments.TryGetValue(notification.PspReference, out Models.HotelPaymentModel model))
{
return Task.CompletedTask;
}

// Perform your business logic here, you would probably want to process the success:true event to update your backend. We log it for now.
_logger.LogInformation($"Received successful capture webhook::\n" +
$"Merchant Reference ::{notification.MerchantReference} \n" +
$"Original Reference ::{notification.OriginalReference} \n" +
$"PSP Reference ::{notification.PspReference} \n");

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;

namespace adyen_dotnet_authorisation_adjustment_example.Models
{
Expand Down

This file was deleted.

Loading

0 comments on commit b2a86c2

Please sign in to comment.