From a4213e3398aee39fa66257d9e2d2d08e0637e4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alican=20Akku=C5=9F?= Date: Wed, 1 Nov 2023 17:40:21 +0300 Subject: [PATCH] adds bnpl payment endoints (#139) --- Craftgate/Adapter/PaymentAdapter.cs | 44 +++++- Craftgate/Model/ApmType.cs | 1 + Craftgate/Model/BnplCartItemType.cs | 51 ++++++ Craftgate/Request/BnplPaymentOfferRequest.cs | 15 ++ Craftgate/Request/Dto/BnplPaymentCartItem.cs | 14 ++ Craftgate/Request/InitBnplPaymentRequest.cs | 11 ++ Craftgate/Response/ApmPaymentResponse.cs | 14 ++ .../Response/BnplPaymentOfferResponse.cs | 11 ++ Craftgate/Response/Dto/BnplBankOffer.cs | 15 ++ Craftgate/Response/Dto/BnplBankOfferTerm.cs | 13 ++ Craftgate/Response/InitBnplPaymentResponse.cs | 16 ++ Samples/PaymentSample.cs | 148 ++++++++++++++---- 12 files changed, 323 insertions(+), 30 deletions(-) create mode 100644 Craftgate/Model/BnplCartItemType.cs create mode 100644 Craftgate/Request/BnplPaymentOfferRequest.cs create mode 100644 Craftgate/Request/Dto/BnplPaymentCartItem.cs create mode 100644 Craftgate/Request/InitBnplPaymentRequest.cs create mode 100644 Craftgate/Response/ApmPaymentResponse.cs create mode 100644 Craftgate/Response/BnplPaymentOfferResponse.cs create mode 100644 Craftgate/Response/Dto/BnplBankOffer.cs create mode 100644 Craftgate/Response/Dto/BnplBankOfferTerm.cs create mode 100644 Craftgate/Response/InitBnplPaymentResponse.cs diff --git a/Craftgate/Adapter/PaymentAdapter.cs b/Craftgate/Adapter/PaymentAdapter.cs index 739bb52..51e1251 100644 --- a/Craftgate/Adapter/PaymentAdapter.cs +++ b/Craftgate/Adapter/PaymentAdapter.cs @@ -515,7 +515,49 @@ public Task UpdatePaymentTransactionAsync( return AsyncRestClient.Put(RequestOptions.BaseUrl + path, CreateHeaders(updatePaymentTransactionRequest, path, RequestOptions), updatePaymentTransactionRequest); } - + + public BnplPaymentOfferResponse RetrieveBnplOffers(BnplPaymentOfferRequest request) + { + var path = "/payment/v1/bnpl-payments/offers"; + return RestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(request, path, RequestOptions), request); + } + + public Task RetrieveBnplOffersAsync(BnplPaymentOfferRequest request) + { + var path = "/payment/v1/bnpl-payments/offers"; + return AsyncRestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(request, path, RequestOptions), request); + } + + public InitBnplPaymentResponse InitBnplPayment(InitBnplPaymentRequest request) + { + var path = "/payment/v1/bnpl-payments/init"; + return RestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(request, path, RequestOptions), request); + } + + public Task InitBnplPaymentAsync(InitBnplPaymentRequest request) + { + var path = "/payment/v1/bnpl-payments/init"; + return AsyncRestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(request, path, RequestOptions), request); + } + + public void ApproveBnplPayment(long PaymentId) + { + var path = "/payment/v1/bnpl-payments/" + PaymentId + "/approve"; + RestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(null, path, RequestOptions), null); + } + + public Task ApproveBnplPaymentAsync(long PaymentId) + { + var path = "/payment/v1/bnpl-payments/" + PaymentId + "/approve"; + return AsyncRestClient.Post(RequestOptions.BaseUrl + path, + CreateHeaders(null, path, RequestOptions), null); + } + public bool Is3DSecureCallbackVerified(string threeDSecureCallbackKey, Dictionary parameters) { string hash = parameters["hash"]; diff --git a/Craftgate/Model/ApmType.cs b/Craftgate/Model/ApmType.cs index 518e73d..8b57293 100644 --- a/Craftgate/Model/ApmType.cs +++ b/Craftgate/Model/ApmType.cs @@ -18,6 +18,7 @@ public enum ApmType [EnumMember(Value = "STRIPE")] STRIPE, [EnumMember(Value = "KASPI")] KASPI, [EnumMember(Value = "TOMPAY")] TOMPAY, + [EnumMember(Value = "MASLAK")] MASLAK, [EnumMember(Value = "FUND_TRANSFER")] FUND_TRANSFER, [EnumMember(Value = "CASH_ON_DELIVERY")] CASH_ON_DELIVERY } diff --git a/Craftgate/Model/BnplCartItemType.cs b/Craftgate/Model/BnplCartItemType.cs new file mode 100644 index 0000000..f34a3ee --- /dev/null +++ b/Craftgate/Model/BnplCartItemType.cs @@ -0,0 +1,51 @@ +using System.Runtime.Serialization; + +namespace Craftgate.Model +{ + public enum BnplCartItemType + { + [EnumMember(Value = "MOBILE_PHONE_OVER_5000_TRY")] + MOBILE_PHONE_OVER_5000_TRY, + + [EnumMember(Value = "MOBILE_PHONE_BELOW_5000_TRY")] + MOBILE_PHONE_BELOW_5000_TRY, + [EnumMember(Value = "TABLET")] TABLET, + [EnumMember(Value = "COMPUTER")] COMPUTER, + + [EnumMember(Value = "CONSTRUCTION_MARKET")] + CONSTRUCTION_MARKET, + [EnumMember(Value = "GOLD")] GOLD, + + [EnumMember(Value = "DIGITAL_PRODUCTS")] + DIGITAL_PRODUCTS, + [EnumMember(Value = "SUPERMARKET")] SUPERMARKET, + [EnumMember(Value = "WHITE_GOODS")] WHITE_GOODS, + + [EnumMember(Value = "WEARABLE_TECHNOLOGY")] + WEARABLE_TECHNOLOGY, + + [EnumMember(Value = "SMALL_HOME_APPLIANCES")] + SMALL_HOME_APPLIANCES, + [EnumMember(Value = "TV")] TV, + [EnumMember(Value = "GAMES_CONSOLES")] GAMES_CONSOLES, + + [EnumMember(Value = "AIR_CONDITIONER_AND_HEATER")] + AIR_CONDITIONER_AND_HEATER, + [EnumMember(Value = "ELECTRONICS")] ELECTRONICS, + [EnumMember(Value = "ACCESSORIES")] ACCESSORIES, + + [EnumMember(Value = "MOM_AND_BABY_AND_KIDS")] + MOM_AND_BABY_AND_KIDS, + [EnumMember(Value = "SHOES")] SHOES, + [EnumMember(Value = "CLOTHING")] CLOTHING, + + [EnumMember(Value = "COSMETICS_AND_PERSONAL_CARE")] + COSMETICS_AND_PERSONAL_CARE, + [EnumMember(Value = "FURNITURE")] FURNITURE, + [EnumMember(Value = "HOME_LIVING")] HOME_LIVING, + + [EnumMember(Value = "AUTOMOBILE_MOTORCYCLE")] + AUTOMOBILE_MOTORCYCLE, + [EnumMember(Value = "OTHER")] OTHER + } +} \ No newline at end of file diff --git a/Craftgate/Request/BnplPaymentOfferRequest.cs b/Craftgate/Request/BnplPaymentOfferRequest.cs new file mode 100644 index 0000000..c1994ff --- /dev/null +++ b/Craftgate/Request/BnplPaymentOfferRequest.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Craftgate.Model; +using Craftgate.Request.Dto; + +namespace Craftgate.Request +{ + public class BnplPaymentOfferRequest + { + public ApmType ApmType { get; set; } + public long? MerchantApmId { get; set; } + public decimal Price { get; set; } + public Currency Currency { get; set; } + public IList Items; + } +} \ No newline at end of file diff --git a/Craftgate/Request/Dto/BnplPaymentCartItem.cs b/Craftgate/Request/Dto/BnplPaymentCartItem.cs new file mode 100644 index 0000000..a62f50c --- /dev/null +++ b/Craftgate/Request/Dto/BnplPaymentCartItem.cs @@ -0,0 +1,14 @@ +using Craftgate.Model; + +namespace Craftgate.Request.Dto +{ + public class BnplPaymentCartItem + { + public string Id { get; set; } + public string Name { get; set; } + public string BrandName { get; set; } + public BnplCartItemType Type { get; set; } + public decimal UnitPrice { get; set; } + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/Craftgate/Request/InitBnplPaymentRequest.cs b/Craftgate/Request/InitBnplPaymentRequest.cs new file mode 100644 index 0000000..db1efeb --- /dev/null +++ b/Craftgate/Request/InitBnplPaymentRequest.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Craftgate.Request.Dto; + +namespace Craftgate.Request +{ + public class InitBnplPaymentRequest : InitApmPaymentRequest + { + public string BankCode { get; set; } + public IList CartItems; + } +} \ No newline at end of file diff --git a/Craftgate/Response/ApmPaymentResponse.cs b/Craftgate/Response/ApmPaymentResponse.cs new file mode 100644 index 0000000..8862de0 --- /dev/null +++ b/Craftgate/Response/ApmPaymentResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Craftgate.Model; +using Craftgate.Response.Common; +using Craftgate.Response.Dto; + +namespace Craftgate.Response +{ + public class ApmPaymentResponse : BasePaymentResponse + { + public ApmType ApmType { get; set; } + public string TransactionId { get; set; } + public string RedirectUrl { get; set; } + } +} \ No newline at end of file diff --git a/Craftgate/Response/BnplPaymentOfferResponse.cs b/Craftgate/Response/BnplPaymentOfferResponse.cs new file mode 100644 index 0000000..edfd482 --- /dev/null +++ b/Craftgate/Response/BnplPaymentOfferResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Craftgate.Response.Dto +{ + public class BnplPaymentOfferResponse + { + public string OfferId { get; set; } + public decimal Price { get; set; } + public IList BankOffers { get; set; } + } +} \ No newline at end of file diff --git a/Craftgate/Response/Dto/BnplBankOffer.cs b/Craftgate/Response/Dto/BnplBankOffer.cs new file mode 100644 index 0000000..0025fc9 --- /dev/null +++ b/Craftgate/Response/Dto/BnplBankOffer.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Craftgate.Response.Dto +{ + public class BnplBankOffer + { + public string BankCode { get; set; } + public string BankName { get; set; } + public string BankIconUrl { get; set; } + public string BankTableBannerMessage { get; set; } + public string BankSmallBannerMessage { get; set; } + public bool? IsSupportNonCustomer { get; set; } + public IList BankOfferTerms { get; set; } + } +} \ No newline at end of file diff --git a/Craftgate/Response/Dto/BnplBankOfferTerm.cs b/Craftgate/Response/Dto/BnplBankOfferTerm.cs new file mode 100644 index 0000000..972346c --- /dev/null +++ b/Craftgate/Response/Dto/BnplBankOfferTerm.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Craftgate.Response.Dto +{ + public class BnplBankOfferTerm + { + public int? Term { get; set; } + public decimal Amount { get; set; } + public decimal TotalAmount { get; set; } + public decimal InterestRate { get; set; } + public decimal AnnualInterestRate { get; set; } + } +} \ No newline at end of file diff --git a/Craftgate/Response/InitBnplPaymentResponse.cs b/Craftgate/Response/InitBnplPaymentResponse.cs new file mode 100644 index 0000000..cd443f8 --- /dev/null +++ b/Craftgate/Response/InitBnplPaymentResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Text; +using Craftgate.Model; +using Craftgate.Response.Dto; + +namespace Craftgate.Response +{ + public class InitBnplPaymentResponse + { + public long PaymentId { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public ApmAdditionalAction AdditionalAction { get; set; } + public string RedirectUrl { get; set; } + public PaymentError PaymentError { get; set; } + } +} \ No newline at end of file diff --git a/Samples/PaymentSample.cs b/Samples/PaymentSample.cs index 4e7c2ec..58b7a71 100644 --- a/Samples/PaymentSample.cs +++ b/Samples/PaymentSample.cs @@ -11,7 +11,8 @@ namespace Samples public class PaymentSample { private readonly CraftgateClient _craftgateClient = - new CraftgateClient("api-key", "secret-key", "https://sandbox-api.craftgate.io"); + new CraftgateClient("sandbox-YEhueLgomBjqsnvBlWVVuFsVhlvJlMHE", "sandbox-tBdcdKVGmGupzfaWcULcwDLMoglZZvTz", + "https://sandbox-api.craftgate.io"); [Test] public void Create_Payment() @@ -327,8 +328,8 @@ public void Create_Payment_Using_External_Payment_Provider_Stored_Card() { "paymentProvider", new Dictionary { - { "cardUserKey", "test-cardUserKey" }, - { "cardToken", "tuz8imxv30" } + {"cardUserKey", "test-cardUserKey"}, + {"cardToken", "tuz8imxv30"} } } } @@ -969,7 +970,7 @@ public void Init_Sodexo_Apm_Payment() ApmUserIdentity = "5555555555", AdditionalParams = new Dictionary { - { "sodexoCode", "843195" } + {"sodexoCode", "843195"} }, Items = new List { @@ -1036,7 +1037,7 @@ public void Init_Edenred_Apm_Payment() Assert.AreEqual(response.PaymentStatus, PaymentStatus.WAITING); Assert.AreEqual(response.AdditionalAction, ApmAdditionalAction.OTP_REQUIRED); } - + [Test] public void Init_Kaspi_Apm_Payment() { @@ -1075,14 +1076,14 @@ public void Init_Kaspi_Apm_Payment() Assert.AreEqual(response.PaymentStatus, PaymentStatus.WAITING); Assert.AreEqual(response.AdditionalAction, ApmAdditionalAction.REDIRECT_TO_URL); } - + [Test] public void Init_Tompay_Apm_Payment() { var additionalParams = new Dictionary(); additionalParams.Add("channel", "channel"); additionalParams.Add("phone", "phone"); - + var request = new InitApmPaymentRequest { ApmType = ApmType.TOMPAY, @@ -1127,7 +1128,7 @@ public void Complete_Edenred_Apm_Payment() PaymentId = 1, AdditionalParams = new Dictionary { - { "otpCode", "784294" } + {"otpCode", "784294"} }, }; @@ -1287,7 +1288,7 @@ public void Init_Ykb_World_Pay_Pos_Apm_Payment() }, AdditionalParams = new Dictionary { - { "sourceCode", "WEB2QR" } + {"sourceCode", "WEB2QR"} }, }; @@ -1570,7 +1571,7 @@ public void Approve_Payment_Transactions() { var request = new ApprovePaymentTransactionsRequest { - PaymentTransactionIds = new HashSet { 1, 2 }, + PaymentTransactionIds = new HashSet {1, 2}, IsTransactional = true }; @@ -1584,7 +1585,7 @@ public void Disapprove_Payment_Transactions() { var request = new DisapprovePaymentTransactionsRequest { - PaymentTransactionIds = new HashSet { 1, 2 }, + PaymentTransactionIds = new HashSet {1, 2}, IsTransactional = true }; @@ -1674,7 +1675,7 @@ public void Post_Auth_Payment() Assert.AreEqual(request.PaidPrice, response.PaidPrice); Assert.AreEqual("POST_AUTH", response.PaymentPhase); } - + [Test] public void Create_MultiCurrency_Payment() { @@ -1762,12 +1763,12 @@ public void Verify_3DS_Callback() string merchantThreeDsCallbackKey = "merchantThreeDsCallbackKeySndbox"; Dictionary parameters = new Dictionary { - { "hash", "1d3fa1e51fe7c350185c5a7f8c3ff513a991367b08c16a56f4ab9abeb738a1e1" }, - { "paymentId", "5" }, - { "conversationData", "conversation-data" }, - { "conversationId", "conversation-id" }, - { "status", "SUCCESS" }, - { "completeStatus", "WAITING" } + {"hash", "1d3fa1e51fe7c350185c5a7f8c3ff513a991367b08c16a56f4ab9abeb738a1e1"}, + {"paymentId", "5"}, + {"conversationData", "conversation-data"}, + {"conversationId", "conversation-id"}, + {"status", "SUCCESS"}, + {"completeStatus", "WAITING"} }; var isVerified = _craftgateClient.Payment() @@ -1781,11 +1782,11 @@ public void Verify_3DS_Callback_Even_Params_Has_Nullable_Value() string merchantThreeDsCallbackKey = "merchantThreeDsCallbackKeySndbox"; Dictionary parameters = new Dictionary { - { "hash", "a097f0231031a88f2d687b510afca2505ccd2977d6421be4c3784666703f6f25" }, - { "paymentId", "5" }, - { "conversationId", "conversation-id" }, - { "status", "SUCCESS" }, - { "completeStatus", "WAITING" } + {"hash", "a097f0231031a88f2d687b510afca2505ccd2977d6421be4c3784666703f6f25"}, + {"paymentId", "5"}, + {"conversationId", "conversation-id"}, + {"status", "SUCCESS"}, + {"completeStatus", "WAITING"} }; var isVerified = _craftgateClient.Payment() @@ -1799,17 +1800,106 @@ public void Not_Verify_3DS_Callback() string merchantThreeDsCallbackKey = "merchantThreeDsCallbackKeySndbox"; Dictionary parameters = new Dictionary { - { "hash", "39427942bcaasjaduqabzhdancaASasdhbcxjancakjscace82" }, - { "paymentId", "5" }, - { "conversationData", "conversation-data" }, - { "conversationId", "conversation-id" }, - { "status", "SUCCESS" }, - { "completeStatus", "WAITING" } + {"hash", "39427942bcaasjaduqabzhdancaASasdhbcxjancakjscace82"}, + {"paymentId", "5"}, + {"conversationData", "conversation-data"}, + {"conversationId", "conversation-id"}, + {"status", "SUCCESS"}, + {"completeStatus", "WAITING"} }; var isVerified = _craftgateClient.Payment() .Is3DSecureCallbackVerified(merchantThreeDsCallbackKey, parameters); Assert.False(isVerified); } + + [Test] + public void Retrieve_Bnpl_Offers() + { + var request = new BnplPaymentOfferRequest() + { + Price = new decimal(10000.0), + Currency = Currency.TRY, + ApmType = ApmType.MASLAK, + Items = new List + { + new BnplPaymentCartItem() + { + Id = "1234", + Name = "Item 1", + BrandName = "Item 1", + UnitPrice = new decimal(7000.0), + Quantity = 1, + Type = BnplCartItemType.TV + }, + new BnplPaymentCartItem() + { + Id = "1234", + Name = "Item 2", + BrandName = "Item 2", + UnitPrice = new decimal(3000.0), + Quantity = 1, + Type = BnplCartItemType.TV + } + } + }; + + var response = _craftgateClient.Payment().RetrieveBnplOffers(request); + Assert.NotNull(response.OfferId); + Assert.IsNotEmpty(request.Items); + } + + [Test] + public void Init_Bnpl_Payment() + { + var request = new InitBnplPaymentRequest + { + ApmType = ApmType.MASLAK, + Price = new decimal(10000.0), + PaidPrice = new decimal(10000.0), + Currency = Currency.TRY, + PaymentGroup = PaymentGroup.LISTING_OR_SUBSCRIPTION, + ConversationId = "456d1297-908e-4bd6-a13b-4be31a6e47d5", + ApmOrderId = Guid.NewGuid().ToString(), + CallbackUrl = "https://www.your-website.com/craftgate-apm-callback", + BankCode = "103", // fibabank + Items = new List + { + new PaymentItem + { + Name = "Item 1", + ExternalId = Guid.NewGuid().ToString(), + Price = new decimal(10000) + } + }, + CartItems = new List + { + new BnplPaymentCartItem() + { + Id = "1234", + Name = "Item 1", + BrandName = "Item 1", + UnitPrice = new decimal(10000.0), + Quantity = 1, + Type = BnplCartItemType.TV + } + } + }; + + var response = _craftgateClient.Payment().InitBnplPayment(request); + Assert.NotNull(response); + Assert.NotNull(response.PaymentId); + Assert.NotNull(response.RedirectUrl); + Assert.AreEqual(response.PaymentStatus, PaymentStatus.WAITING); + Assert.AreEqual(response.AdditionalAction, ApmAdditionalAction.REDIRECT_TO_URL); + } + + [Test] + public void Approve_Bnpl_Payment() + { + var PaymentId = 1; + + _craftgateClient.Payment().ApproveBnplPayment(PaymentId); + } } } \ No newline at end of file