Skip to content

Commit

Permalink
B2B Express checkout added
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrieldwight committed Nov 17, 2023
1 parent 6903170 commit 7226da2
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 2 deletions.
34 changes: 34 additions & 0 deletions MpesaSdk/Callbacks/B2BExpressCheckoutCallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Newtonsoft.Json;

namespace MpesaSdk.Callbacks
{
public class B2BExpressCheckoutCallback
{
[JsonProperty("resultCode")]
public string ResultCode { get; set; }

[JsonProperty("resultDesc")]
public string ResultDesc { get; set; }

[JsonProperty("amount")]
public string Amount { get; set; }

[JsonProperty("requestId")]
public string RequestId { get; set; }

[JsonProperty("resultType")]
public string ResultType { get; set; }

[JsonProperty("conversationID")]
public string ConversationId { get; set; }

[JsonProperty("transactionId")]
public string TransactionId { get; set; }

[JsonProperty("status")]
public string Status { get; set; }

[JsonProperty("paymentReference")]
public string PaymentReference { get; set; }
}
}
39 changes: 39 additions & 0 deletions MpesaSdk/Dtos/B2BExpressCheckoutRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Newtonsoft.Json;

namespace MpesaSdk.Dtos
{
public class B2BExpressCheckoutRequest
{
[JsonProperty("primaryShortCode")]
public string PrimaryShortCode { get; set; }

[JsonProperty("receiverShortCode")]
public string ReceiverShortCode { get; set; }

[JsonProperty("amount")]
public string Amount { get; set; }

[JsonProperty("paymentRef")]
public string PaymentRef { get; set; }

[JsonProperty("callbackUrl")]
public string CallbackUrl { get; set; }

[JsonProperty("partnerName")]
public string PartnerName { get; set; }

[JsonProperty("RequestRefID")]
public string RequestRefId { get; set; }

public B2BExpressCheckoutRequest(string primaryShortCode, string receiverShortCode, string amount, string paymentRef, string callbackUrl, string partnerName, string requestRefId)
{
PrimaryShortCode = primaryShortCode;
ReceiverShortCode = receiverShortCode;
Amount = amount;
PaymentRef = paymentRef;
CallbackUrl = callbackUrl;
PartnerName = partnerName;
RequestRefId = requestRefId;
}
}
}
18 changes: 18 additions & 0 deletions MpesaSdk/Interfaces/IMpesaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,5 +442,23 @@ public interface IMpesaClient
/// <param name="cancellationToken"></param>
/// <returns></returns>
BillManagerResponse BillManagerUpdateInvoice(BillManagerInvoicingRequest billManagerInvoicingRequest, string accesstoken, CancellationToken cancellationToken = default);

/// <summary>
/// B2B(UssdPush to Till) is a product for enabling merchants to initiate USSD Push to Till enabling their fellow merchants to pay from their owned till numbers to the vendor's paybill.
/// </summary>
/// <param name="b2BExpressCheckoutRequest"></param>
/// <param name="accesstoken"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<B2BExpressCheckoutResponse> B2BExpressCheckoutAsync(B2BExpressCheckoutRequest b2BExpressCheckoutRequest, string accesstoken, CancellationToken cancellationToken = default);

/// <summary>
/// B2B(UssdPush to Till) is a product for enabling merchants to initiate USSD Push to Till enabling their fellow merchants to pay from their owned till numbers to the vendor's paybill.
/// </summary>
/// <param name="b2BExpressCheckoutRequest"></param>
/// <param name="accesstoken"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
B2BExpressCheckoutResponse B2BExpressCheckout(B2BExpressCheckoutRequest b2BExpressCheckoutRequest, string accesstoken, CancellationToken cancellationToken = default);
}
}
23 changes: 22 additions & 1 deletion MpesaSdk/MpesaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,28 @@ public MpesaClient(HttpClient client)
_client = client;
}

public async Task<BillManagerResponse> BillManagerOnboardingAsync(BillManagerOnboardingRequest billManagerOnboardingRequest, string accesstoken, CancellationToken cancellationToken = default)
public async Task<B2BExpressCheckoutResponse> B2BExpressCheckoutAsync(B2BExpressCheckoutRequest b2BExpressCheckoutRequest, string accesstoken, CancellationToken cancellationToken = default)
{
var validator = new B2BExpressCheckoutValidator();
var results = await validator.ValidateAsync(b2BExpressCheckoutRequest, cancellationToken);

return !results.IsValid
? throw new MpesaAPIException(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage.ToString())))
: await MpesaPostRequestAsync<B2BExpressCheckoutResponse>(b2BExpressCheckoutRequest, accesstoken, MpesaRequestEndpoint.B2BExpressCheckout, cancellationToken);
}

public B2BExpressCheckoutResponse B2BExpressCheckout(B2BExpressCheckoutRequest b2BExpressCheckoutRequest, string accesstoken, CancellationToken cancellationToken = default)
{
var validator = new B2BExpressCheckoutValidator();
var results = validator.Validate(b2BExpressCheckoutRequest);

return !results.IsValid
? throw new MpesaAPIException(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage.ToString())))
: MpesaPostRequestAsync<B2BExpressCheckoutResponse>(b2BExpressCheckoutRequest, accesstoken, MpesaRequestEndpoint.B2BExpressCheckout, cancellationToken).GetAwaiter().GetResult();
}


public async Task<BillManagerResponse> BillManagerOnboardingAsync(BillManagerOnboardingRequest billManagerOnboardingRequest, string accesstoken, CancellationToken cancellationToken = default)
{
var validator = new BillManagerOnboardingValidator();
var results = await validator.ValidateAsync(billManagerOnboardingRequest, cancellationToken);
Expand Down
4 changes: 3 additions & 1 deletion MpesaSdk/MpesaRequestEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public static class MpesaRequestEndpoint
/// <summary>
/// Update invoice API allows you to alter invoice items by using the external reference previously used to create the invoice you want to update. Any other update on the invoice can be done by using the Cancel Invoice API which will recall the invoice, then a new invoice can be created.
/// </summary>
public static string BusinessManagerSingleInvoicingUpdate { get; set; } = "/v1/billmanager-invoice/update/single-invoicing";
public static string BusinessManagerSingleInvoicingUpdate { get; set; } = "v1/billmanager-invoice/update/single-invoicing";

public static string B2BExpressCheckout { get; set; } = "v1/ussdpush/get-msisdn";
}
}
13 changes: 13 additions & 0 deletions MpesaSdk/Response/B2BExpressCheckoutResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Newtonsoft.Json;

namespace MpesaSdk.Response
{
public class B2BExpressCheckoutResponse
{
[JsonProperty("code")]
public string Code { get; set; }

[JsonProperty("status")]
public string Status { get; set; }
}
}
69 changes: 69 additions & 0 deletions MpesaSdk/Validators/B2BExpressCheckoutValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using FluentValidation;
using MpesaSdk.Dtos;
using System;

namespace MpesaSdk.Validators
{
public class B2BExpressCheckoutValidator : AbstractValidator<B2BExpressCheckoutRequest>
{
public B2BExpressCheckoutValidator()
{
RuleFor(x => x.PrimaryShortCode)
.NotNull()
.WithMessage("{PropertyName} - The paybill or till number shortcode should not be empty.")
.Must(x => int.TryParse(x, out int value))
.WithMessage("{PropertyName} - The paybill or till number must be a numeric value.")
.Length(5, 7)
.WithMessage("{PropertyName} - The paybill or till number should be 5 to 7 account number digits.");

RuleFor(x => x.ReceiverShortCode)
.NotNull()
.WithMessage("{PropertyName} - The receiver paybill or till number shortcode should not be empty.")
.Must(x => int.TryParse(x, out int value))
.WithMessage("{PropertyName} - The receiver paybill or till number must be a numeric value.")
.Length(5, 7)
.WithMessage("{PropertyName} - The receiver paybill or till number should be 5 to 7 account number digits.");

RuleFor(x => x.Amount)
.NotNull()
.WithMessage("{PropertyName} - Amount is required.")
.NotEmpty()
.WithMessage("{PropertyName} - Amount must not be empty")
.Must(x => int.TryParse(x, out int value))
.WithMessage("{PropertyName} - The amount should be in numeric value.");

RuleFor(x => x.PaymentRef)
.NotNull()
.WithMessage("{PropertyName} - The payment reference should not be empty.")
.MaximumLength(12)
.WithMessage("{PropertyName} - The payment reference should not be more than 12 characters.");

RuleFor(x => x.CallbackUrl)
.NotNull()
.WithMessage("{PropertyName} - The callback url is required.")
.Must(x => LinkMustBeAUri(x))
.WithMessage("{PropertyName} - The callback url should be a valid secure url.");

RuleFor(x => x.PartnerName)
.NotNull()
.WithMessage("{PropertyName} - The partner name is required.")
.NotEmpty()
.WithMessage("{PropertyName} - This partner name should not be empty.");

RuleFor(x => x.RequestRefId)
.NotNull()
.WithMessage("{PropertyName} - The unique identifier is required.")
.NotEmpty()
.WithMessage("{PropertyName} - The unique identifier should not be empty.");
}

private static bool LinkMustBeAUri(string link)
{
if (!Uri.IsWellFormedUriString(link, UriKind.Absolute))
{
return false;
}
return true;
}
}
}

0 comments on commit 7226da2

Please sign in to comment.