├── MoyasarTest ├── xunit.runner.json ├── Fixtures │ ├── ObjectNotFound.json │ ├── Source │ │ ├── ApplePay.json │ │ ├── StcPay.json │ │ └── CreditCard.json │ ├── Payment.json │ ├── Invoice │ │ ├── Canceled.json │ │ ├── Updated.json │ │ ├── Initiated.json │ │ ├── Paid.json │ │ └── List.json │ ├── ApplePay │ │ ├── Paid.json │ │ └── Failed.json │ ├── StcPay │ │ ├── Paid.json │ │ ├── Failed.json │ │ └── Initiated.json │ ├── CreditCard │ │ ├── Paid.json │ │ ├── Initiated.json │ │ └── Refunded.json │ └── PaymentList.json ├── Helpers │ ├── ServiceMockHelper.cs │ ├── FixtureHelper.cs │ └── HttpMockHelper.cs ├── InvoiceInfoTest.cs ├── PaymentInfoTest.cs ├── MoyasarTest.csproj ├── HelpersTest.cs ├── BaseTest.cs ├── InvoiceTest.cs └── PaymentTest.cs ├── Moyasar ├── package-icon.png ├── Abstraction │ ├── IPaymentMethod.cs │ ├── IPaymentSource.cs │ └── Resource.cs ├── Exceptions │ ├── FieldError.cs │ ├── TooManyRequestsException.cs │ ├── NetworkException.cs │ ├── ValidationException.cs │ └── ApiException.cs ├── Models │ ├── PaginationResultMeta.cs │ ├── ApplePaySource.cs │ ├── StcPayMethod.cs │ ├── ApplePayMethod.cs │ ├── CreditCard.cs │ ├── PaginationResult.cs │ ├── StcPaySource.cs │ ├── SearchQuery.cs │ ├── PaymentInfo.cs │ ├── InvoiceInfo.cs │ └── CreditCardSource.cs ├── Providers │ ├── MetadataSerializer.cs │ └── PaymentMethodConverter.cs ├── Moyasar.csproj ├── Helpers │ └── CreditCardHelper.cs ├── MoyasarService.cs └── Services │ ├── Payment.cs │ └── Invoice.cs ├── .travis.yml ├── MoyasarDotNet.sln.DotSettings ├── LICENSE ├── MoyasarDotNet.sln ├── .gitattributes ├── README.md └── .gitignore /MoyasarTest/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "maxParallelThreads": 1 3 | } -------------------------------------------------------------------------------- /Moyasar/package-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyasar/moyasar-dotnet/HEAD/Moyasar/package-icon.png -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/ObjectNotFound.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "api_error", 3 | "message": "Object not found", 4 | "errors": null 5 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: MoyasarDotNet.sln 3 | mono: none 4 | dotnet: 3.1.14 5 | install: 6 | - dotnet restore 7 | script: 8 | - dotnet test -f "netcoreapp3.1" -------------------------------------------------------------------------------- /Moyasar/Abstraction/IPaymentMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Moyasar.Abstraction 4 | { 5 | public interface IPaymentMethod 6 | { 7 | string Type { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Moyasar/Exceptions/FieldError.cs: -------------------------------------------------------------------------------- 1 | namespace Moyasar.Exceptions 2 | { 3 | public class FieldError 4 | { 5 | public string Field { get; set; } 6 | public string Error { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Moyasar/Abstraction/IPaymentSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Moyasar.Abstraction 4 | { 5 | public interface IPaymentSource 6 | { 7 | string Type { get; } 8 | 9 | void Validate(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Source/ApplePay.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "applepay", 3 | "company": "visa", 4 | "name": null, 5 | "number": "XXXX-XXXX-XXXX-1111", 6 | "message": "APPROVED", 7 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 8 | "reference_number": "125478454231", 9 | } 10 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Source/StcPay.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stcpay", 3 | "mobile": "0555555555", 4 | "reference_number": "xxxxxxxxxxxxx", 5 | "branch": "1", 6 | "cashier": "1", 7 | "transaction_url": "https://apimig.moyasar.com/v1/stc_pays/35360576-dec6-44bf-a342-0e36ef72de25/proceed?otp_token=KJHKH87989g78huh", 8 | "message": "Paid" 9 | } 10 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Source/CreditCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "creditcard", 3 | "company": "visa", 4 | "name": "Long John", 5 | "number": "XXXX-XXXX-XXXX-1111", 6 | "message": null, 7 | "transaction_url": "https://api.moyasar.com/v1/transaction_auths/a9a6c4c9-b065-48c6-952a-184266ec4b4a/form?token=auth_n7FoPBZo2cYriecyZvzmXrXTP13E7og9HC3jGFDYqzF", 8 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 9 | "reference_number": "125478454231", 10 | } 11 | -------------------------------------------------------------------------------- /Moyasar/Exceptions/TooManyRequestsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Moyasar.Exceptions 6 | { 7 | /// 8 | /// Represents 429 Too Many Requests HTTP Response 9 | /// 10 | public class TooManyRequestsException : ApiException 11 | { 12 | public TooManyRequestsException(string message) : base(message) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Moyasar/Exceptions/NetworkException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Moyasar.Exceptions 4 | { 5 | public class NetworkException : Exception 6 | { 7 | public NetworkException() 8 | { 9 | } 10 | 11 | public NetworkException(string message) : base(message) 12 | { 13 | } 14 | 15 | public NetworkException(string message, Exception innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MoyasarDotNet.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /MoyasarTest/Helpers/ServiceMockHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using Newtonsoft.Json; 5 | using BasicDict = System.Collections.Generic.Dictionary; 6 | 7 | namespace MoyasarTest.Helpers 8 | { 9 | public class ServiceMockHelper 10 | { 11 | public static void MockJsonResponse(string filename) 12 | { 13 | HttpMockHelper.MockHttpResponse(HttpStatusCode.OK, FixtureHelper.GetJsonObjectFixture(filename)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Payment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a4a144ba-adc3-43bd-98e8-c80f2925fdc4", 3 | "status": "initiated", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": "{9}", 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": {}, 19 | "metadata": {} 20 | } 21 | -------------------------------------------------------------------------------- /Moyasar/Models/PaginationResultMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Moyasar.Models 5 | { 6 | public class PaginationResultMeta 7 | { 8 | [JsonProperty("current_page")] 9 | public int CurrentPage { get; set; } 10 | 11 | [JsonProperty("next_page")] 12 | public int? NextPage { get; set; } 13 | 14 | [JsonProperty("prev_page")] 15 | public int? PreviousPage { get; set; } 16 | 17 | [JsonProperty("total_pages")] 18 | public int TotalPages { get; set; } 19 | 20 | [JsonProperty("total_count")] 21 | public int TotalCount { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /Moyasar/Exceptions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Moyasar.Exceptions 5 | { 6 | /// 7 | /// Thrown when supplied information are incorrect 8 | /// 9 | public class ValidationException : Exception 10 | { 11 | public List FieldErrors { get; set; } 12 | 13 | public ValidationException() 14 | { 15 | } 16 | 17 | public ValidationException(string message) : base(message) 18 | { 19 | } 20 | 21 | public ValidationException(string message, Exception innerException) : base(message, innerException) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Invoice/Canceled.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f91065f7-d188-4ec8-8fc5-af97841ec14e", 3 | "status": "canceled", 4 | "amount": 7000, 5 | "currency": "SAR", 6 | "description": "A 70 SAR invoice just because", 7 | "expired_at": "2016-04-07T06:45:18.866Z", 8 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 9 | "amount_format": "70.00 SAR", 10 | "url": "https://dashboard.moyasar.com/invoices/37a54bed-7d54-444c-a151-c287106da514?lang=en", 11 | "callback_url": "http://www.example.com/invoice_callback", 12 | "created_at": "2016-04-06T06:45:18.866Z", 13 | "updated_at": "2016-04-06T06:45:18.866Z", 14 | "payments": [], 15 | "metadata": {} 16 | } 17 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Invoice/Updated.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f91065f7-d188-4ec8-8fc5-af97841ec14e", 3 | "status": "initiated", 4 | "amount": 8000, 5 | "currency": "USD", 6 | "description": "An 80 USD invoice just because", 7 | "expired_at": "2016-04-07T06:45:18.866Z", 8 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 9 | "amount_format": "70.00 SAR", 10 | "url": "https://dashboard.moyasar.com/invoices/37a54bed-7d54-444c-a151-c287106da514?lang=en", 11 | "callback_url": "http://www.example.com/invoice_callback", 12 | "created_at": "2016-04-06T06:45:18.866Z", 13 | "updated_at": "2016-04-06T06:45:18.866Z", 14 | "payments": [], 15 | "metadata": {} 16 | } 17 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Invoice/Initiated.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f91065f7-d188-4ec8-8fc5-af97841ec14e", 3 | "status": "initiated", 4 | "amount": 7000, 5 | "currency": "SAR", 6 | "description": "A 70 SAR invoice just because", 7 | "expired_at": "2016-04-07T06:45:18.866Z", 8 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 9 | "amount_format": "70.00 SAR", 10 | "url": "https://dashboard.moyasar.com/invoices/37a54bed-7d54-444c-a151-c287106da514?lang=en", 11 | "callback_url": "http://www.example.com/invoice_callback", 12 | "created_at": "2016-04-06T06:45:18.866Z", 13 | "updated_at": "2016-04-06T06:45:18.866Z", 14 | "payments": [], 15 | "metadata": {} 16 | } 17 | -------------------------------------------------------------------------------- /Moyasar/Models/ApplePaySource.cs: -------------------------------------------------------------------------------- 1 | using Moyasar.Abstraction; 2 | using Moyasar.Exceptions; 3 | using Newtonsoft.Json; 4 | 5 | namespace Moyasar.Models 6 | { 7 | public class ApplePaySource : IPaymentSource 8 | { 9 | [JsonProperty("type")] 10 | public string Type { get; } = "applepay"; 11 | 12 | [JsonProperty("token")] 13 | public string Token { get; set; } 14 | 15 | public void SetTokenFromObject(object token) 16 | { 17 | Token = JsonConvert.SerializeObject(token); 18 | } 19 | 20 | public void Validate() 21 | { 22 | try 23 | { 24 | JsonConvert.DeserializeObject(Token); 25 | } 26 | catch 27 | { 28 | throw new ValidationException("Apple Pay token is not a valid JSON string"); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/ApplePay/Paid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a4a144ba-adc3-43bd-98e8-c80f2925fdc4", 3 | "status": "paid", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": null, 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "applepay", 20 | "company": "visa", 21 | "name": null, 22 | "number": "XXXX-XXXX-XXXX-1111", 23 | "message": "APPROVED", 24 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 25 | "reference_number": "125478454231" 26 | }, 27 | "metadata": { 28 | "order_id": "a2620c7d-658e-4c27-aa78-bc6532549cec", 29 | "tax": "150" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Moyasar/Abstraction/Resource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Moyasar.Abstraction 4 | { 5 | /// 6 | /// Represents an abstract resource that other Moyasar's resources are built on 7 | /// 8 | /// Resource type that inherents this class 9 | public abstract class Resource 10 | { 11 | protected static string Name => typeof(TResource).Name.ToLower(); 12 | protected static string PluralName => $"{Name}s"; 13 | protected static string ResourceUrl => $"{MoyasarService.CurrentVersionUrl}/{PluralName}"; 14 | 15 | protected static string GetCreateUrl() => ResourceUrl; 16 | protected static string GetFetchUrl(string id) => $"{ResourceUrl}/{id}"; 17 | protected static string GetUpdateUrl(string id) => $"{ResourceUrl}/{id}"; 18 | protected static string GetListUrl() => ResourceUrl; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/ApplePay/Failed.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a02910d0-d373-41e5-a9b8-84168e7f6bbf", 3 | "status": "failed", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": null, 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "applepay", 20 | "company": "visa", 21 | "name": null, 22 | "number": "XXXX-XXXX-XXXX-1111", 23 | "message": "Expired token", 24 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 25 | "reference_number": "125478454231" 26 | }, 27 | "metadata": { 28 | "order_id": "5ccfc0a4-b43b-437b-ab44-0ffdcf5755d8", 29 | "tax": "150" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MoyasarTest/InvoiceInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Models; 4 | using Newtonsoft.Json; 5 | using Xunit; 6 | 7 | namespace MoyasarTest 8 | { 9 | public class InvoiceInfoTest 10 | { 11 | [Fact] 12 | public void InvoiceInfoShouldSerializeMetadata() 13 | { 14 | var info = new InvoiceInfo 15 | { 16 | Amount = 8000, 17 | Currency = "SAR", 18 | Description = "Test", 19 | CallbackUrl = "https://aa.aa/cc", 20 | ExpiredAt = DateTime.Now, 21 | Metadata = new Dictionary 22 | { 23 | {"order_id", "123"} 24 | } 25 | }; 26 | 27 | var json = JsonConvert.SerializeObject(info); 28 | 29 | Assert.Contains(@"""metadata"":{""order_id"":""123""}", json); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MoyasarTest/Helpers/FixtureHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Moyasar; 5 | using Moyasar.Models; 6 | using Moyasar.Services; 7 | using Newtonsoft.Json; 8 | 9 | namespace MoyasarTest.Helpers 10 | { 11 | public static class FixtureHelper 12 | { 13 | public static string GetJsonObjectFixture(string filename, Dictionary overrides = null) 14 | { 15 | var rawJson = File.ReadAllText(filename); 16 | var dict = JsonConvert.DeserializeObject>(rawJson); 17 | 18 | if (overrides == null) 19 | { 20 | return JsonConvert.SerializeObject(dict); 21 | } 22 | 23 | foreach (var pair in overrides) 24 | { 25 | dict[pair.Key] = pair.Value; 26 | } 27 | 28 | return JsonConvert.SerializeObject(dict); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /MoyasarTest/PaymentInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Models; 4 | using Newtonsoft.Json; 5 | using Xunit; 6 | 7 | namespace MoyasarTest 8 | { 9 | public class PaymentInfoTest 10 | { 11 | [Fact] 12 | public void PaymentInfoShouldSerializeMetadata() 13 | { 14 | var info = new PaymentInfo 15 | { 16 | Amount = 8000, 17 | Currency = "SAR", 18 | Description = "Test", 19 | CallbackUrl = "https://aa.aa/cc", 20 | Source = new CreditCardSource(), 21 | Metadata = new Dictionary 22 | { 23 | {"order_id", "123"} 24 | } 25 | }; 26 | 27 | var json = JsonConvert.SerializeObject(info); 28 | 29 | Assert.Contains(@"""metadata"":{""order_id"":""123""}", json); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Moyasar/Models/StcPayMethod.cs: -------------------------------------------------------------------------------- 1 | using Moyasar.Abstraction; 2 | using Newtonsoft.Json; 3 | 4 | namespace Moyasar.Models 5 | { 6 | /// 7 | /// Represents an stc pay payment method. Used for new payments 8 | /// 9 | public class StcPayMethod : IPaymentMethod 10 | { 11 | [JsonProperty("type")] 12 | public string Type { get; } = "stcpay"; 13 | 14 | [JsonProperty("mobile")] 15 | public string Mobile { get; set; } 16 | 17 | [JsonProperty("reference_number")] 18 | public string ReferenceNumber { get; set; } 19 | 20 | [JsonProperty("branch")] 21 | public string Branch { get; set; } 22 | 23 | [JsonProperty("cashier")] 24 | public string Cashier { get; set; } 25 | 26 | [JsonProperty("transaction_url")] 27 | public string TransactionUrl { get; set; } 28 | 29 | [JsonProperty("message")] 30 | public string Message { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /Moyasar/Models/ApplePayMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moyasar.Abstraction; 3 | using Newtonsoft.Json; 4 | 5 | namespace Moyasar.Models 6 | { 7 | /// 8 | /// Represents an Apple Pay payment method. Used for new payments 9 | /// 10 | public class ApplePayMethod : IPaymentMethod 11 | { 12 | [JsonProperty("type")] 13 | public string Type { get; } = "applepay"; 14 | 15 | [JsonProperty("company")] 16 | public string Company { get; set; } 17 | 18 | [JsonProperty("name")] 19 | public string Name { get; set; } 20 | 21 | [JsonProperty("number")] 22 | public string Number { get; set; } 23 | 24 | [JsonProperty("message")] 25 | public string Message { get; set; } 26 | 27 | [JsonProperty("gateway_id")] 28 | public string GatewayId { get; set; } 29 | 30 | [JsonProperty("reference_number")] 31 | public string ReferenceNumber { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/StcPay/Paid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "50559d3b-e67f-4b3a-8df8-509dde19fe38", 3 | "status": "paid", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": null, 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "stcpay", 20 | "mobile": "0555555555", 21 | "reference_number": "xxxxxxxxxxxxx", 22 | "branch": "1", 23 | "cashier": "1", 24 | "transaction_url": "https://apimig.moyasar.com/v1/stc_pays/35360576-dec6-44bf-a342-0e36ef72de25/proceed?otp_token=KJHKH87989g78huh", 25 | "message": "Paid" 26 | }, 27 | "metadata": { 28 | "order_id": "e573e01a-38a7-4a59-8ced-f6fd2b0b4b2f", 29 | "tax": "150" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/StcPay/Failed.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ed310d8e-8fc6-4fc9-a82c-b431caf647e7", 3 | "status": "failed", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": null, 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "stcpay", 20 | "mobile": "0555555555", 21 | "reference_number": "xxxxxxxxxxxxx", 22 | "branch": "1", 23 | "cashier": "1", 24 | "transaction_url": "https://apimig.moyasar.com/v1/stc_pays/35360576-dec6-44bf-a342-0e36ef72de25/proceed?otp_token=KJHKH87989g78huh", 25 | "message": "Invalid OTP" 26 | }, 27 | "metadata": { 28 | "order_id": "4469d544-4ce6-4d50-a8a8-57164a87d5c5", 29 | "tax": "150" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/StcPay/Initiated.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d58ac1ae-717d-4c8a-867b-6567c64c09e6", 3 | "status": "initiated", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": null, 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "stcpay", 20 | "mobile": "0555555555", 21 | "reference_number": "xxxxxxxxxxxxx", 22 | "branch": "1", 23 | "cashier": "1", 24 | "transaction_url": "https://apimig.moyasar.com/v1/stc_pays/35360576-dec6-44bf-a342-0e36ef72de25/proceed?otp_token=KJHKH87989g78huh", 25 | "message": "Initiated" 26 | }, 27 | "metadata": { 28 | "order_id": "f1482e3e-2af2-4225-a465-d1a1bba8877c", 29 | "tax": "150" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MoyasarTest/MoyasarTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Moyasar/Models/CreditCard.cs: -------------------------------------------------------------------------------- 1 | using Moyasar.Abstraction; 2 | using Newtonsoft.Json; 3 | 4 | namespace Moyasar.Models 5 | { 6 | /// 7 | /// Represents credit card payment method for a payment 8 | /// 9 | public class CreditCard : IPaymentMethod 10 | { 11 | [JsonProperty("type")] 12 | public string Type { get; } 13 | 14 | [JsonProperty("company")] 15 | public string Company { get; set; } 16 | 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | [JsonProperty("number")] 21 | public string Number { get; set; } 22 | 23 | [JsonProperty("message")] 24 | public string Message { get; set; } 25 | 26 | [JsonProperty("transaction_url")] 27 | public string TransactionUrl { get; set; } 28 | 29 | [JsonProperty("gateway_id")] 30 | public string GatewayId { get; set; } 31 | 32 | [JsonProperty("reference_number")] 33 | public string ReferenceNumber { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Moyasar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/CreditCard/Paid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b6c01c90-a091-45a4-9358-71668ecbf7ea", 3 | "status": "paid", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": "https://mystore.com/order/redirect-back", 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "creditcard", 20 | "company": "visa", 21 | "name": "Long John", 22 | "number": "XXXX-XXXX-XXXX-1111", 23 | "message": "APPROVED", 24 | "transaction_url": "https://api.moyasar.com/v1/transaction_auths/a9a6c4c9-b065-48c6-952a-184266ec4b4a/form?token=auth_n7FoPBZo2cYriecyZvzmXrXTP13E7og9HC3jGFDYqzF", 25 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 26 | "reference_number": "125478454231" 27 | }, 28 | "metadata": { 29 | "order_id": "5c02ba44-7fd1-444c-b82b-d3993b87d4b0", 30 | "tax": "50" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/CreditCard/Initiated.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cb7e89a5-dded-4c60-b74d-b676e1ec6289", 3 | "status": "initiated", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 0, 8 | "refunded_at": null, 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": "https://mystore.com/order/redirect-back", 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-02T10:14:14.414Z", 18 | "source": { 19 | "type": "creditcard", 20 | "company": "visa", 21 | "name": "Long John", 22 | "number": "XXXX-XXXX-XXXX-1111", 23 | "message": null, 24 | "transaction_url": "https://api.moyasar.com/v1/transaction_auths/a9a6c4c9-b065-48c6-952a-184266ec4b4a/form?token=auth_n7FoPBZo2cYriecyZvzmXrXTP13E7og9HC3jGFDYqzF", 25 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 26 | "reference_number": "125478454231" 27 | }, 28 | "metadata": { 29 | "order_id": "fcd546c9-9b1d-446f-b48f-8667daae51b7", 30 | "tax": "50" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Moyasar/Models/PaginationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Abstraction; 4 | using Newtonsoft.Json; 5 | 6 | namespace Moyasar.Models 7 | { 8 | /// 9 | /// Contains meta-data that help with resources pagination 10 | /// 11 | /// Resource Type 12 | public class PaginationResult where TModel : Resource 13 | { 14 | public int CurrentPage => Meta.CurrentPage; 15 | public int? NextPage => Meta.NextPage; 16 | public int? PreviousPage => Meta.PreviousPage; 17 | public int TotalPages => Meta.TotalPages; 18 | public int TotalCount => Meta.TotalCount; 19 | 20 | [JsonProperty("meta")] 21 | public PaginationResultMeta Meta { get; private set; } 22 | 23 | [JsonIgnore] 24 | public List Items { get; private set; } 25 | 26 | [JsonProperty("payments")] 27 | private List Payments { set => Items = value; } 28 | 29 | [JsonProperty("invoices")] 30 | private List Invoices { set => Items = value; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MoyasarTest/HelpersTest.cs: -------------------------------------------------------------------------------- 1 | using Moyasar.Helpers; 2 | using Xunit; 3 | 4 | namespace MoyasarTest 5 | { 6 | public class HelpersTest 7 | { 8 | [Fact] 9 | public void TestVisaValidation() 10 | { 11 | var testCards = new[] 12 | { 13 | "4111111111111111", 14 | "4012888888881881", 15 | "4222222222222" 16 | }; 17 | 18 | foreach (var testCard in testCards) 19 | { 20 | Assert.True(CreditCardHelper.IsVisa(testCard), 21 | $"{testCard} is not a valid Visa"); 22 | } 23 | } 24 | 25 | [Fact] 26 | public void TestMasterCardValidation() 27 | { 28 | var testCards = new[] 29 | { 30 | "5555555555554444", 31 | "5105105105105100" 32 | }; 33 | 34 | foreach (var testCard in testCards) 35 | { 36 | Assert.True(CreditCardHelper.IsMasterCard(testCard), 37 | $"{testCard} is not a valid MasterCard"); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/CreditCard/Refunded.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b6c01c90-a091-45a4-9358-71668ecbf7ea", 3 | "status": "refunded", 4 | "amount": 1000, 5 | "fee": 0, 6 | "currency": "SAR", 7 | "refunded": 1000, 8 | "refunded_at": "2019-01-03T10:14:14.414Z", 9 | "description": "Test Payment", 10 | "amount_format": "10.00 SAR", 11 | "fee_format": "0.00 SAR", 12 | "refunded_format": "0.00 SAR", 13 | "invoice_id": null, 14 | "ip": null, 15 | "callback_url": "https://mystore.com/order/redirect-back", 16 | "created_at": "2019-01-02T10:14:14.414Z", 17 | "updated_at": "2019-01-03T10:14:14.414Z", 18 | "source": { 19 | "type": "creditcard", 20 | "company": "visa", 21 | "name": "Long John", 22 | "number": "XXXX-XXXX-XXXX-1111", 23 | "message": "APPROVED", 24 | "transaction_url": "https://api.moyasar.com/v1/transaction_auths/a9a6c4c9-b065-48c6-952a-184266ec4b4a/form?token=auth_n7FoPBZo2cYriecyZvzmXrXTP13E7og9HC3jGFDYqzF", 25 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 26 | "reference_number": "125478454231" 27 | }, 28 | "metadata": { 29 | "order_id": "5c02ba44-7fd1-444c-b82b-d3993b87d4b0", 30 | "tax": "50" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MoyasarDotNet.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moyasar", "Moyasar\Moyasar.csproj", "{1852F9AD-ECDD-4E72-A17A-428FABDFF3E8}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoyasarTest", "MoyasarTest\MoyasarTest.csproj", "{DF80A79F-A069-4EFC-86D9-B349D0D0D82A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1852F9AD-ECDD-4E72-A17A-428FABDFF3E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1852F9AD-ECDD-4E72-A17A-428FABDFF3E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1852F9AD-ECDD-4E72-A17A-428FABDFF3E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1852F9AD-ECDD-4E72-A17A-428FABDFF3E8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {DF80A79F-A069-4EFC-86D9-B349D0D0D82A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {DF80A79F-A069-4EFC-86D9-B349D0D0D82A}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {DF80A79F-A069-4EFC-86D9-B349D0D0D82A}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {DF80A79F-A069-4EFC-86D9-B349D0D0D82A}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /Moyasar/Providers/MetadataSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Abstraction; 4 | using Newtonsoft.Json; 5 | 6 | namespace Moyasar.Providers 7 | { 8 | /// 9 | /// Converter used by Newtonsoft's json library to deserialize a json payment 10 | /// type object into an IPaymentMethod 11 | /// 12 | public class MetadataSerializer : JsonConverter 13 | { 14 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 15 | { 16 | if (value is Dictionary dict) 17 | { 18 | writer.WriteNull(); 19 | 20 | foreach (var pair in dict) 21 | { 22 | writer.WritePropertyName($"metadata[{pair.Key}]"); 23 | writer.WriteValue(pair.Value); 24 | } 25 | } 26 | else 27 | { 28 | serializer.Serialize(writer, value); 29 | } 30 | } 31 | 32 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 33 | { 34 | return serializer.Deserialize(reader, objectType); 35 | } 36 | 37 | public override bool CanConvert(Type objectType) 38 | { 39 | return typeof(IPaymentMethod) == objectType; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Moyasar/Moyasar.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net40;net45;net451;net452;net46;net461;netstandard2.0;netstandard2.1 5 | 2.3.0 6 | Moyasar API's .NET Wrapper 7 | Ali Alhoshaiyan, Moyasar Dev Team 8 | Copyright © 2021 Moyasar. All rights reserved 9 | https://moyasar.com/ 10 | Moyasar;API;Payment;CreditCard;Sadad;Mada 11 | package-icon.png 12 | https://github.com/moyasar/moyasar-dotnet.git 13 | git 14 | true 15 | LICENSE 16 | 8 17 | 2.3.0 18 | 2.3.0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Moyasar/Exceptions/ApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Moyasar.Exceptions 6 | { 7 | /// 8 | /// Represents a server side error (400 - 599) 9 | /// 10 | public class ApiException : Exception 11 | { 12 | public int HttpStatusCode { get; set; } 13 | public string ResponsePayload { get; set; } 14 | 15 | public string Type { get; set; } 16 | public string Errors { get; set; } 17 | public Dictionary> ErrorsDictionary { get; set; } 18 | 19 | public ApiException(string message) : base(message) 20 | { 21 | } 22 | 23 | public override string ToString() 24 | { 25 | var builder = new StringBuilder(); 26 | builder.Append($"Status Code: {HttpStatusCode.ToString()}\n"); 27 | builder.Append($"Error Type: {Type}\n"); 28 | builder.Append($"Message: {Message}\n"); 29 | 30 | if (ErrorsDictionary != null) 31 | { 32 | foreach (var error in ErrorsDictionary) 33 | { 34 | builder.Append($"Error [{error.Key}]:\n"); 35 | foreach (var s in error.Value) 36 | { 37 | builder.Append($"\t- {s}\n"); 38 | } 39 | } 40 | } 41 | 42 | builder.Append($"Payload:\n{ResponsePayload}"); 43 | 44 | return builder.ToString(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Moyasar/Providers/PaymentMethodConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moyasar.Abstraction; 3 | using Moyasar.Models; 4 | using Newtonsoft.Json; 5 | 6 | namespace Moyasar.Providers 7 | { 8 | /// 9 | /// Converter used by Newtonsoft's json library to deserialize a json payment 10 | /// type object into an IPaymentMethod 11 | /// 12 | public class PaymentMethodConverter : JsonConverter 13 | { 14 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 15 | { 16 | serializer.Serialize(writer, value); 17 | } 18 | 19 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 20 | { 21 | dynamic dict = serializer.Deserialize(reader); 22 | string json = JsonConvert.SerializeObject(dict); 23 | 24 | string type; 25 | try 26 | { 27 | type = dict.type; 28 | } 29 | catch 30 | { 31 | return null; 32 | } 33 | 34 | return type.ToLower() switch 35 | { 36 | "creditcard" => JsonConvert.DeserializeObject(json), 37 | "applepay" => JsonConvert.DeserializeObject(json), 38 | "stcpay" => JsonConvert.DeserializeObject(json), 39 | _ => null 40 | }; 41 | } 42 | 43 | public override bool CanConvert(Type objectType) 44 | { 45 | return typeof(IPaymentMethod) == objectType; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Invoice/Paid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f91065f7-d188-4ec8-8fc5-af97841ec14e", 3 | "status": "paid", 4 | "amount": 7000, 5 | "currency": "SAR", 6 | "description": "A 70 SAR invoice just because", 7 | "expired_at": "2021-04-07T06:45:18.866Z", 8 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 9 | "amount_format": "70.00 SAR", 10 | "url": "https://dashboard.moyasar.com/invoices/37a54bed-7d54-444c-a151-c287106da514?lang=en", 11 | "callback_url": "http://www.example.com/invoice_callback", 12 | "created_at": "2019-01-01T10:14:14.414Z", 13 | "updated_at": "2019-01-01T10:14:14.414Z", 14 | "payments": [ 15 | { 16 | "id": "a4a144ba-adc3-43bd-98e8-c80f2925fdc4", 17 | "status": "paid", 18 | "amount": 7000, 19 | "fee": 0, 20 | "currency": "SAR", 21 | "refunded": 0, 22 | "refunded_at": null, 23 | "description": "A 70 SAR invoice just because", 24 | "amount_format": "70.00 SAR", 25 | "fee_format": "0.00 SAR", 26 | "refunded_format": "0.00 SAR", 27 | "invoice_id": null, 28 | "ip": null, 29 | "callback_url": null, 30 | "created_at": "2019-01-02T10:14:14.414Z", 31 | "updated_at": "2019-01-02T10:14:14.414Z", 32 | "source": { 33 | "type": "applepay", 34 | "company": "visa", 35 | "name": null, 36 | "number": "XXXX-XXXX-XXXX-1111", 37 | "message": "APPROVED", 38 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 39 | "reference_number": "125478454231", 40 | } 41 | } 42 | ], 43 | "metadata": { 44 | "order_id": "de92988a-34bd-43a5-963f-b757cf02de7b" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/Invoice/List.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "current_page": 2, 4 | "next_page": 3, 5 | "prev_page": 1, 6 | "total_pages": 3, 7 | "total_count": 6 8 | }, 9 | "invoices": [ 10 | { 11 | "id": "f91065f7-d188-4ec8-8fc5-af97841ec14e", 12 | "status": "initiated", 13 | "amount": 7000, 14 | "currency": "SAR", 15 | "description": "A 70 SAR invoice just because", 16 | "expired_at": "2016-04-07T06:45:18.866Z", 17 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 18 | "amount_format": "70.00 SAR", 19 | "url": "https://dashboard.moyasar.com/invoices/37a54bed-7d54-444c-a151-c287106da514?lang=en", 20 | "callback_url": "http://www.example.com/invoice_callback", 21 | "created_at": "2016-04-06T06:45:18.866Z", 22 | "updated_at": "2016-04-06T06:45:18.866Z", 23 | "payments": [], 24 | "metadata": { 25 | "order_id": "9e5c7df4-b796-4c83-9a61-e304c9c8fa51" 26 | } 27 | }, 28 | { 29 | "id": "d23ac387-aee5-42db-a396-499e7f37de57", 30 | "status": "expired", 31 | "amount": 8000, 32 | "currency": "SAR", 33 | "description": "A 80 SAR invoice just because", 34 | "expired_at": "2016-04-08T06:45:18.866Z", 35 | "logo_url": "https://s3-eu-west-1.amazonaws.com/moyasar.api.assets.prod/entities/logos/default.png", 36 | "amount_format": "80.00 SAR", 37 | "url": "https://dashboard.moyasar.com/invoices/d23ac387-aee5-42db-a396-499e7f37de57?lang=en", 38 | "callback_url": "http://www.example.com/invoice_callback", 39 | "created_at": "2016-04-05T06:45:18.866Z", 40 | "updated_at": "2016-04-05T06:45:18.866Z", 41 | "payments": [], 42 | "metadata": {} 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /Moyasar/Models/StcPaySource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using Moyasar.Abstraction; 6 | using Moyasar.Exceptions; 7 | using Newtonsoft.Json; 8 | 9 | namespace Moyasar.Models 10 | { 11 | public class StcPaySource : IPaymentSource 12 | { 13 | [JsonProperty("type")] 14 | public string Type { get; } = "stcpay"; 15 | 16 | [JsonProperty("mobile")] 17 | public string Mobile { get; set; } 18 | 19 | [JsonProperty("branch", NullValueHandling = NullValueHandling.Ignore)] 20 | public string Branch { get; set; } 21 | 22 | [JsonProperty("cashier", NullValueHandling = NullValueHandling.Ignore)] 23 | public string Cashier { get; set; } 24 | 25 | public void Validate() 26 | { 27 | var errors = new List(); 28 | 29 | if (String.IsNullOrEmpty(Mobile)) 30 | { 31 | errors.Add(new FieldError 32 | { 33 | Field = nameof(Mobile), 34 | Error = "Mobile number is required." 35 | }); 36 | } 37 | 38 | if (!Regex.IsMatch(Mobile, @"^05[503649187][0-9]{7}$")) 39 | { 40 | errors.Add(new FieldError 41 | { 42 | Field = nameof(Mobile), 43 | Error = "Number is not a valid Saudi mobile." 44 | }); 45 | } 46 | 47 | if (errors.Any()) 48 | { 49 | throw new ValidationException("stc pay information are incorrect") 50 | { 51 | FieldErrors = errors 52 | }; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Moyasar/Models/SearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Moyasar.Providers; 4 | using Newtonsoft.Json; 5 | using BasicStringDict = System.Collections.Generic.Dictionary; 6 | 7 | namespace Moyasar.Models 8 | { 9 | /// 10 | /// Contains search parametes used when searching a resource 11 | /// 12 | public class SearchQuery 13 | { 14 | [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] 15 | public string Id { get; set; } 16 | 17 | [JsonProperty("source", NullValueHandling = NullValueHandling.Ignore)] 18 | public string Source { get; set; } 19 | 20 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] 21 | public string Status { get; set; } 22 | 23 | [JsonProperty("page", NullValueHandling = NullValueHandling.Ignore)] 24 | public int? Page { get; set; } 25 | 26 | [JsonProperty("created[gt]", NullValueHandling = NullValueHandling.Ignore)] 27 | public DateTime? CreatedAfter { get; set; } 28 | 29 | [JsonProperty("created[lt]", NullValueHandling = NullValueHandling.Ignore)] 30 | public DateTime? CreatedBefore { get; set; } 31 | 32 | [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] 33 | [JsonConverter(typeof(MetadataSerializer))] 34 | public BasicStringDict Metadata { get; set; } 35 | 36 | public SearchQuery Clone() => new SearchQuery 37 | { 38 | Id = Id, 39 | Source = Source, 40 | Status = Status, 41 | Page = Page, 42 | CreatedAfter = CreatedAfter, 43 | CreatedBefore = CreatedBefore, 44 | Metadata = Metadata.ToDictionary(p => p.Key, p => p.Value) 45 | }; 46 | } 47 | } -------------------------------------------------------------------------------- /MoyasarTest/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using Moyasar.Exceptions; 6 | using Moyasar.Models; 7 | using MoyasarTest.Helpers; 8 | using Newtonsoft.Json; 9 | using Xunit; 10 | 11 | namespace MoyasarTest 12 | { 13 | public class BaseTest 14 | { 15 | [Fact] 16 | public async void TestSendRequestMustThrowApiExceptionOnErrorCode() 17 | { 18 | HttpMockHelper.MockHttpResponse(HttpStatusCode.NotFound, File.ReadAllText("Fixtures/ObjectNotFound.json")); 19 | 20 | await Assert.ThrowsAsync(async () => 21 | { 22 | await Task.Run(() => 23 | { 24 | Moyasar.MoyasarService.SendRequest( 25 | "GET", 26 | "http://someurl/", 27 | null 28 | ); 29 | }); 30 | }); 31 | } 32 | 33 | [Fact] 34 | public async void TestSendRequestMustThrowTransportExceptionWhenCantConnect() 35 | { 36 | HttpMockHelper.MockTransportError(); 37 | 38 | await Assert.ThrowsAsync(async () => 39 | { 40 | await Task.Run(() => 41 | { 42 | Moyasar.MoyasarService.SendRequest( 43 | "GET", 44 | "http://someurl/", 45 | null 46 | ); 47 | }); 48 | }); 49 | } 50 | 51 | [Fact] 52 | public void TestUrlParamsBuilder() 53 | { 54 | var url = "https://api.moyasar.com/v1/payments"; 55 | 56 | var urlParams = new Dictionary 57 | { 58 | { "id", "81c0fc10-9424-476d-b2c3-67e7aae1088a" }, 59 | { "created[gt]", "13/12/2017" }, 60 | { "metadata", null } 61 | }; 62 | 63 | Assert.Equal( 64 | "https://api.moyasar.com/v1/payments?id=81c0fc10-9424-476d-b2c3-67e7aae1088a&created[gt]=13/12/2017", 65 | Moyasar.MoyasarService.AppendUrlParameters(url, urlParams)); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /MoyasarTest/Helpers/HttpMockHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Text; 5 | using Moq; 6 | 7 | namespace MoyasarTest.Helpers 8 | { 9 | public static class HttpMockHelper 10 | { 11 | public static void MockHttpResponse(HttpStatusCode statusCode, string response) 12 | { 13 | var httpWebResponse = new Mock(MockBehavior.Loose); 14 | httpWebResponse.Setup(r => r.StatusCode).Returns(statusCode); 15 | 16 | if (response == null) response = ""; 17 | 18 | httpWebResponse.Setup(r => r.GetResponseStream()).Returns( 19 | new MemoryStream(Encoding.UTF8.GetBytes(response)) 20 | ); 21 | 22 | var httpWebRequest = new Mock(MockBehavior.Loose); 23 | 24 | if ((int)statusCode >= 400 && (int)statusCode < 600) 25 | { 26 | httpWebRequest.Setup(req => req.GetResponse()).Callback(() => throw new WebException 27 | ( 28 | "Protocol Error", 29 | null, 30 | WebExceptionStatus.ProtocolError, 31 | httpWebResponse.Object 32 | )); 33 | } 34 | else 35 | { 36 | httpWebRequest.Setup(req => req.GetResponse()).Returns(httpWebResponse.Object); 37 | } 38 | 39 | httpWebRequest.Setup(req => req.GetRequestStream()).Returns(new MemoryStream()); 40 | 41 | Moyasar.MoyasarService.HttpWebRequestFactory = url => 42 | { 43 | httpWebRequest.Setup(req => req.RequestUri).Returns(new Uri(url)); 44 | return httpWebRequest.Object; 45 | }; 46 | } 47 | 48 | public static void MockTransportError() 49 | { 50 | var httpWebRequest = new Mock(MockBehavior.Loose); 51 | httpWebRequest.Setup(req => req.GetResponse()).Throws(); 52 | 53 | Moyasar.MoyasarService.HttpWebRequestFactory = url => 54 | { 55 | httpWebRequest.Setup(req => req.RequestUri).Returns(new Uri(url)); 56 | return httpWebRequest.Object; 57 | }; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Moyasar/Helpers/CreditCardHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Moyasar.Helpers 4 | { 5 | /// 6 | /// Contains a number of methods that helps with credit card validation 7 | /// 8 | public static class CreditCardHelper 9 | { 10 | private static readonly Regex VisaNumberPattern; 11 | private static readonly Regex MasterCardNumberPattern; 12 | 13 | static CreditCardHelper() 14 | { 15 | VisaNumberPattern = new Regex(@"^4[0-9]{12}(?:[0-9]{3})?$"); 16 | MasterCardNumberPattern = 17 | new Regex(@"^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$"); 18 | } 19 | 20 | /// 21 | /// Check whether a given number is a valid Visa number 22 | /// 23 | /// Number to check 24 | /// True when the number given is a valid Visa number 25 | public static bool IsVisa(string number) 26 | { 27 | return VisaNumberPattern.IsMatch(number); 28 | } 29 | 30 | /// 31 | /// Check whether a given number is a valid MasterCard number 32 | /// 33 | /// Number to check 34 | /// True when the number given is a valid MasterCard number 35 | public static bool IsMasterCard(string number) 36 | { 37 | return MasterCardNumberPattern.IsMatch(number); 38 | } 39 | 40 | /// 41 | /// Get credit card type for a given number 42 | /// 43 | /// Credit card number 44 | /// Credit card type 45 | public static CreditCardType? GetCreditCardType(string number) 46 | { 47 | if (number == null) 48 | { 49 | return null; 50 | } 51 | 52 | if (IsVisa(number)) 53 | { 54 | return CreditCardType.Visa; 55 | } 56 | 57 | if (IsMasterCard(number)) 58 | { 59 | return CreditCardType.MasterCard; 60 | } 61 | 62 | return null; 63 | } 64 | } 65 | 66 | /// 67 | /// List of known credit card types 68 | /// 69 | public enum CreditCardType 70 | { 71 | Visa = 0, 72 | MasterCard = 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Moyasar/Models/PaymentInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moyasar.Abstraction; 5 | using Moyasar.Exceptions; 6 | using Newtonsoft.Json; 7 | 8 | namespace Moyasar.Models 9 | { 10 | /// 11 | /// Model that contains information needed to create a new payment 12 | /// 13 | public class PaymentInfo 14 | { 15 | [JsonProperty("amount")] 16 | public int Amount { get; set; } 17 | 18 | [JsonProperty("currency", NullValueHandling = NullValueHandling.Ignore)] 19 | public string Currency { get; set; } = "SAR"; 20 | 21 | [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] 22 | public string Description { get; set; } 23 | 24 | [JsonProperty("source")] 25 | public IPaymentSource Source { get; set; } 26 | 27 | [JsonProperty("callback_url")] 28 | public string CallbackUrl { get; set; } 29 | 30 | [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] 31 | public Dictionary Metadata { get; set; } 32 | 33 | public void Validate() 34 | { 35 | var errors = new List(); 36 | 37 | if (Amount < 1) errors.Add(new FieldError() 38 | { 39 | Field = nameof(Amount), 40 | Error = "Amount must be a positive integer greater than 0" 41 | }); 42 | 43 | if (Source == null) 44 | { 45 | errors.Add(new FieldError() 46 | { 47 | Field = nameof(Source), 48 | Error = "A source of payment must be provided" 49 | }); 50 | } 51 | else 52 | { 53 | Source.Validate(); 54 | } 55 | 56 | if (Source is CreditCardSource) 57 | { 58 | try 59 | { 60 | new Uri(CallbackUrl); 61 | } 62 | catch 63 | { 64 | errors.Add(new FieldError() 65 | { 66 | Field = nameof(CallbackUrl), 67 | Error = $"The value ({CallbackUrl}) is not a valid url" 68 | }); 69 | } 70 | } 71 | 72 | if (errors.Any()) 73 | { 74 | throw new ValidationException() 75 | { 76 | FieldErrors = errors 77 | }; 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Moyasar/Models/InvoiceInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Exceptions; 4 | using Newtonsoft.Json; 5 | 6 | namespace Moyasar.Models 7 | { 8 | /// 9 | /// Model that contains information needed to create a new invoice 10 | /// 11 | public class InvoiceInfo 12 | { 13 | [JsonProperty("amount")] 14 | public int Amount { get; set; } 15 | 16 | [JsonProperty("currency")] 17 | public string Currency { get; set; } = "SAR"; 18 | 19 | [JsonProperty("description")] 20 | public string Description { get; set; } 21 | 22 | [JsonProperty("callback_url", NullValueHandling = NullValueHandling.Ignore)] 23 | public string CallbackUrl { get; set; } 24 | 25 | [JsonProperty("expired_at", NullValueHandling = NullValueHandling.Ignore)] 26 | public DateTime? ExpiredAt { get; set; } 27 | 28 | [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] 29 | public Dictionary Metadata { get; set; } 30 | 31 | public void Validate() 32 | { 33 | var errors = new List(); 34 | 35 | if (Amount < 1) errors.Add(new FieldError() 36 | { 37 | Field = nameof(Amount), 38 | Error = "Amount must be a positive integer greater than 0" 39 | }); 40 | 41 | if (string.IsNullOrEmpty(Currency)) errors.Add(new FieldError() 42 | { 43 | Field = nameof(Currency), 44 | Error = "Field is required" 45 | }); 46 | 47 | if (string.IsNullOrEmpty(Description)) errors.Add(new FieldError() 48 | { 49 | Field = nameof(Description), 50 | Error = "Field is required" 51 | }); 52 | 53 | if (CallbackUrl != null) 54 | { 55 | try 56 | { 57 | new Uri(CallbackUrl); 58 | } 59 | catch 60 | { 61 | errors.Add(new FieldError() 62 | { 63 | Field = nameof(CallbackUrl), 64 | Error = "CallbackUrl must be a valid URI" 65 | }); 66 | } 67 | } 68 | 69 | if (ExpiredAt != null && ExpiredAt.Value <= DateTime.Now) 70 | { 71 | errors.Add(new FieldError() 72 | { 73 | Field = nameof(ExpiredAt), 74 | Error = "ExpiredAt must be a future date and time" 75 | }); 76 | } 77 | 78 | if (errors.Count > 0) 79 | { 80 | throw new ValidationException 81 | { 82 | FieldErrors = errors 83 | }; 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Moyasar/Models/CreditCardSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using Moyasar.Abstraction; 6 | using Moyasar.Exceptions; 7 | using Moyasar.Helpers; 8 | using Newtonsoft.Json; 9 | 10 | namespace Moyasar.Models 11 | { 12 | /// 13 | /// Model that contains credit card information needed to create a new payment 14 | /// 15 | [DataContract] 16 | public class CreditCardSource : IPaymentSource 17 | { 18 | [JsonProperty("type")] 19 | public string Type { get; } = "creditcard"; 20 | 21 | [JsonProperty("name")] 22 | public string Name { get; set; } 23 | 24 | [JsonProperty("number")] 25 | public string Number { get; set; } 26 | 27 | [JsonProperty("cvc")] 28 | public int Cvc { get; set; } 29 | 30 | [JsonProperty("month")] 31 | public int Month { get; set; } 32 | 33 | [JsonProperty("year")] 34 | public int Year { get; set; } 35 | 36 | // By default TRUE according to documentation 37 | [JsonProperty("3ds", NullValueHandling = NullValueHandling.Ignore)] 38 | public bool _3Ds { get; set; } = true; 39 | 40 | [JsonProperty("manual", NullValueHandling = NullValueHandling.Ignore)] 41 | public bool Manual { get; set; } = false; 42 | 43 | public void Validate() 44 | { 45 | var errors = new List(); 46 | 47 | if (String.IsNullOrEmpty(Name)) errors.Add(new FieldError() 48 | { 49 | Field = nameof(Name), 50 | Error = "Credit card holder name is required" 51 | }); 52 | 53 | if(CreditCardHelper.GetCreditCardType(Number) == null) errors.Add(new FieldError() 54 | { 55 | Field = nameof(Number), 56 | Error = $"The number {Number} is not a valid credit card number" 57 | }); 58 | 59 | if(!(Cvc >= 1 && Cvc <= 999)) errors.Add(new FieldError() 60 | { 61 | Field = nameof(Month), 62 | Error = "Cvc must be a three digit number" 63 | }); 64 | 65 | if(!(Month >= 1 && Month <= 12)) errors.Add(new FieldError() 66 | { 67 | Field = nameof(Month), 68 | Error = "Month must be an integer between 1 and 12" 69 | }); 70 | 71 | if(Year < 1) errors.Add(new FieldError() 72 | { 73 | Field = nameof(Month), 74 | Error = "Year must be a positive integer greater than or equals to 1" 75 | }); 76 | 77 | if (errors.Any()) 78 | { 79 | throw new ValidationException("Credit card information is incorrect") 80 | { 81 | FieldErrors = errors 82 | }; 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project has been archived and is no longer maintained. 2 | 3 | # Moyasar.Net 4 | 5 | [![Build Status](https://travis-ci.org/aliio/moyasar-dotnet.svg?branch=master)](https://travis-ci.org/aliio/moyasar-dotnet) 6 | 7 | Moyasar's .NET Client Library 8 | 9 | 10 | # Target Frameworks 11 | 12 | This library targets following frameworks: 13 | 14 | 1. .Net Standard 2.0 15 | 2. .Net Framework 4.0 16 | 17 | 18 | # Installation 19 | 20 | If you are using `dotnet` command line tools you can add the library to 21 | your project using the following command 22 | 23 | ```bash 24 | dotnet add package moyasar 25 | ``` 26 | 27 | Or if you are using Nuget Package Manager 28 | 29 | ```powershell 30 | PM> Install-Package moyasar 31 | ``` 32 | 33 | 34 | # Manual Installation 35 | 36 | `Please note that this installation method is not recommended` 37 | 38 | To Install the library manually please download the last release from 39 | the releases section and reference it in your project. 40 | 41 | 42 | # Usage 43 | 44 | ### Setup 45 | 46 | Make sure to set the API key before proceeding 47 | 48 | ```csharp 49 | MoyasarService.ApiKey = "YouKeyHere"; 50 | ``` 51 | 52 | ### Payment 53 | 54 | Make sure you always try to catch the following exceptions: 55 | 56 | `ValidationException` 57 | 58 | `NetworkException` 59 | 60 | `ApiException` 61 | 62 | To fetch a payment from Moyasar, use the following: 63 | 64 | ```csharp 65 | Payment.Fetch("Payment-Id"); 66 | ``` 67 | 68 | To refund a payment, one must have a Payment instance `somePayment` then 69 | invoke the following: 70 | 71 | ```csharp 72 | somePayment.Refund(); 73 | ``` 74 | 75 | To update your payment, change `Description` property on that payment, 76 | then invoke `Update`: 77 | 78 | ```csharp 79 | somePayment.Description = "Colombia, Narino Sandona, Medium Roast (Special)"; 80 | somePayment.Update(); 81 | ``` 82 | 83 | To list or search for payments at Moyasar, do the following: 84 | 85 | ```csharp 86 | var result = Payment.List(); 87 | ``` 88 | 89 | or 90 | 91 | ```csharp 92 | var result = Payment.List(new SearchQuery() 93 | { 94 | Id = "SomeId", 95 | Source = "creditcard OR sadad", 96 | Status = "some status", 97 | Page = 2, 98 | CreatedAfter = DateTime.Now.AddDays(-5), 99 | CreatedBefore = DateTime.Now 100 | }); 101 | ``` 102 | 103 | All `SearchQuery` parameters are optional, use what is needed 104 | 105 | ### Invoice 106 | 107 | Use `Invoice` class with the same methods as `Payment` class, except 108 | for the following: 109 | 110 | To create an invoice for example: 111 | 112 | ```csharp 113 | var invoice = Invoice.Create(new InvoiceInfo() 114 | { 115 | Amount = 7000, 116 | Currency = "SAR", 117 | Description = "A 70 SAR invoice just because", 118 | ExpiredAt = DateTime.Now.AddDays(3), 119 | CallbackUrl = "http://www.example.com/invoice_callback" 120 | }); 121 | ``` 122 | 123 | To Cancel an Invoice: 124 | 125 | ```csharp 126 | someInvoice.Cancel(); 127 | ``` 128 | 129 | For more details, please refer to the official documentation: https://moyasar.com/docs/api/ 130 | 131 | # Testing 132 | 133 | To run the tests use the following command 134 | 135 | ```bash 136 | dotnet test 137 | ``` 138 | -------------------------------------------------------------------------------- /MoyasarTest/Fixtures/PaymentList.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "current_page": 2, 4 | "next_page": 3, 5 | "prev_page": 1, 6 | "total_pages": 3, 7 | "total_count": 9 8 | }, 9 | "payments": [ 10 | { 11 | "id": "b6c01c90-a091-45a4-9358-71668ecbf7ea", 12 | "status": "paid", 13 | "amount": 1000, 14 | "fee": 0, 15 | "currency": "SAR", 16 | "refunded": 0, 17 | "refunded_at": null, 18 | "description": "Test Payment", 19 | "amount_format": "10.00 SAR", 20 | "fee_format": "0.00 SAR", 21 | "refunded_format": "0.00 SAR", 22 | "invoice_id": null, 23 | "ip": null, 24 | "callback_url": "https://mystore.com/order/redirect-back", 25 | "created_at": "2019-01-02T10:14:14.414Z", 26 | "updated_at": "2019-01-02T10:14:14.414Z", 27 | "source": { 28 | "type": "creditcard", 29 | "company": "visa", 30 | "name": "Long John", 31 | "number": "XXXX-XXXX-XXXX-1111", 32 | "message": "APPROVED", 33 | "transaction_url": "https://api.moyasar.com/v1/transaction_auths/a9a6c4c9-b065-48c6-952a-184266ec4b4a/form?token=auth_n7FoPBZo2cYriecyZvzmXrXTP13E7og9HC3jGFDYqzF", 34 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 35 | "reference_number": "125478454231", 36 | }, 37 | "metadata": { 38 | "order_id": "5c02ba44-7fd1-444c-b82b-d3993b87d4b0", 39 | "tax": "50" 40 | } 41 | }, 42 | { 43 | "id": "a4a144ba-adc3-43bd-98e8-c80f2925fdc4", 44 | "status": "paid", 45 | "amount": 1000, 46 | "fee": 0, 47 | "currency": "SAR", 48 | "refunded": 0, 49 | "refunded_at": null, 50 | "description": "Test Payment", 51 | "amount_format": "10.00 SAR", 52 | "fee_format": "0.00 SAR", 53 | "refunded_format": "0.00 SAR", 54 | "invoice_id": null, 55 | "ip": null, 56 | "callback_url": null, 57 | "created_at": "2019-01-02T10:14:14.414Z", 58 | "updated_at": "2019-01-02T10:14:14.414Z", 59 | "source": { 60 | "type": "applepay", 61 | "company": "visa", 62 | "name": null, 63 | "number": "XXXX-XXXX-XXXX-1111", 64 | "message": "APPROVED", 65 | "gateway_id": "moyasar_ap_je1iUidxhrh74257S891wvW", 66 | "reference_number": "125478454231" 67 | }, 68 | "metadata": { 69 | "order_id": "a2620c7d-658e-4c27-aa78-bc6532549cec", 70 | "tax": "150" 71 | } 72 | }, 73 | { 74 | "id": "50559d3b-e67f-4b3a-8df8-509dde19fe38", 75 | "status": "paid", 76 | "amount": 1000, 77 | "fee": 0, 78 | "currency": "SAR", 79 | "refunded": 0, 80 | "refunded_at": null, 81 | "description": "Test Payment", 82 | "amount_format": "10.00 SAR", 83 | "fee_format": "0.00 SAR", 84 | "refunded_format": "0.00 SAR", 85 | "invoice_id": null, 86 | "ip": null, 87 | "callback_url": null, 88 | "created_at": "2019-01-02T10:14:14.414Z", 89 | "updated_at": "2019-01-02T10:14:14.414Z", 90 | "source": { 91 | "type": "stcpay", 92 | "mobile": "0555555555", 93 | "reference_number": "xxxxxxxxxxxxx", 94 | "branch": "1", 95 | "cashier": "1", 96 | "transaction_url": "https://apimig.moyasar.com/v1/stc_pays/35360576-dec6-44bf-a342-0e36ef72de25/proceed?otp_token=KJHKH87989g78huh", 97 | "message": "Paid" 98 | }, 99 | "metadata": { 100 | "order_id": "e573e01a-38a7-4a59-8ced-f6fd2b0b4b2f", 101 | "tax": "150" 102 | } 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /MoyasarTest/InvoiceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Moyasar.Exceptions; 5 | using Moyasar.Models; 6 | using Moyasar.Services; 7 | using MoyasarTest.Helpers; 8 | using Xunit; 9 | 10 | namespace MoyasarTest 11 | { 12 | public class InvoiceTest 13 | { 14 | [Fact] 15 | public async void TestInvoiceInfoValidation() 16 | { 17 | var info = GetValidInvoiceInfo(); 18 | info.Validate(); 19 | 20 | info.Amount = 0; 21 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 22 | 23 | info = GetValidInvoiceInfo(); 24 | info.Currency = ""; 25 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 26 | 27 | info = GetValidInvoiceInfo(); 28 | info.Description = ""; 29 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 30 | 31 | info = GetValidInvoiceInfo(); 32 | info.ExpiredAt = DateTime.Now.AddDays(-1); 33 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 34 | 35 | info = GetValidInvoiceInfo(); 36 | info.CallbackUrl = "not a valid url"; 37 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 38 | } 39 | 40 | [Fact] 41 | public void TestCreateInvoice() 42 | { 43 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Initiated.json"); 44 | 45 | var invoice = Invoice.Create(GetValidInvoiceInfo()); 46 | Assert.IsType(invoice); 47 | 48 | Assert.Equal(7000, invoice.Amount); 49 | Assert.Equal("SAR", invoice.Currency); 50 | Assert.Equal("A 70 SAR invoice just because", invoice.Description); 51 | Assert.Equal(DateTime.Parse("2016-04-07T06:45:18.866Z").ToUniversalTime(), invoice.ExpiredAt); 52 | Assert.Equal("http://www.example.com/invoice_callback", invoice.CallbackUrl); 53 | } 54 | 55 | [Fact] 56 | public void TestFetchInvoice() 57 | { 58 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Paid.json"); 59 | 60 | var invoice = Invoice.Fetch("f91065f7-d188-4ec8-8fc5-af97841ec14e"); 61 | 62 | Assert.Equal("f91065f7-d188-4ec8-8fc5-af97841ec14e", invoice.Id); 63 | Assert.Equal(7000, invoice.Amount); 64 | Assert.Equal("SAR", invoice.Currency); 65 | Assert.Equal("A 70 SAR invoice just because", invoice.Description); 66 | Assert.Equal(DateTime.Parse("2021-04-07T06:45:18.866Z").ToUniversalTime(), invoice.ExpiredAt); 67 | Assert.Equal("http://www.example.com/invoice_callback", invoice.CallbackUrl); 68 | Assert.Equal("de92988a-34bd-43a5-963f-b757cf02de7b", invoice.Metadata["order_id"]); 69 | 70 | Assert.Single(invoice.Payments); 71 | 72 | Assert.Equal("a4a144ba-adc3-43bd-98e8-c80f2925fdc4", invoice.Payments[0].Id); 73 | Assert.Equal(7000, invoice.Payments[0].Amount); 74 | Assert.Equal("SAR", invoice.Payments[0].Currency); 75 | Assert.Equal("A 70 SAR invoice just because", invoice.Payments[0].Description); 76 | } 77 | 78 | [Fact] 79 | public void TestUpdateInvoice() 80 | { 81 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Initiated.json"); 82 | var invoice = Invoice.Fetch("f91065f7-d188-4ec8-8fc5-af97841ec14e"); 83 | 84 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Updated.json"); 85 | invoice.Update(); 86 | 87 | Assert.Equal(8000, invoice.Amount); 88 | Assert.Equal("USD", invoice.Currency); 89 | Assert.Equal("An 80 USD invoice just because", invoice.Description); 90 | } 91 | 92 | [Fact] 93 | public void TestCancelInvoice() 94 | { 95 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Initiated.json"); 96 | var invoice = Invoice.Fetch("some-random-id"); 97 | 98 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/Canceled.json"); 99 | invoice.Cancel(); 100 | Assert.Equal(7000, invoice.Amount); 101 | Assert.Equal("canceled", invoice.Status); 102 | } 103 | 104 | [Fact] 105 | public void TestInvoiceListing() 106 | { 107 | ServiceMockHelper.MockJsonResponse("Fixtures/Invoice/List.json"); 108 | var pagination = Invoice.List(); 109 | 110 | Assert.Equal(2, pagination.Items.Count); 111 | 112 | Assert.Equal(7000, pagination.Items[0].Amount); 113 | Assert.Equal("SAR", pagination.Items[0].Currency); 114 | Assert.Equal(DateTime.Parse("2016-04-07T06:45:18.866Z").ToUniversalTime(), pagination.Items[0].ExpiredAt); 115 | Assert.Equal("9e5c7df4-b796-4c83-9a61-e304c9c8fa51", pagination.Items[0].Metadata["order_id"]); 116 | 117 | Assert.Equal(2, pagination.CurrentPage); 118 | Assert.Equal(3, pagination.NextPage); 119 | Assert.Equal(1, pagination.PreviousPage); 120 | Assert.Equal(3, pagination.TotalPages); 121 | } 122 | 123 | internal static InvoiceInfo GetValidInvoiceInfo() 124 | { 125 | return new InvoiceInfo 126 | { 127 | Amount = 7000, 128 | Currency = "SAR", 129 | Description = "A 70 SAR invoice just because", 130 | ExpiredAt = DateTime.Now.AddDays(3), 131 | CallbackUrl = "http://www.example.com/invoice_callback" 132 | }; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /Moyasar/MoyasarService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | using Moyasar.Exceptions; 7 | using Moyasar.Models; 8 | using Newtonsoft.Json; 9 | using BasicStringDict = System.Collections.Generic.Dictionary; 10 | 11 | namespace Moyasar 12 | { 13 | public static class MoyasarService 14 | { 15 | public const string ApiBaseUrl = "https://api.moyasar.com"; 16 | public const string ApiVersion = "v1"; 17 | public static string CurrentVersionUrl => $"{ApiBaseUrl}/{ApiVersion}"; 18 | 19 | /// 20 | /// Moyasar's API key that is used to authenticate all outbound requests 21 | /// 22 | public static string ApiKey { get; set; } 23 | public static Func HttpWebRequestFactory { get; set; } 24 | 25 | static MoyasarService() 26 | { 27 | HttpWebRequestFactory = CreateHttpWebRequest; 28 | } 29 | 30 | public static HttpWebRequest CreateHttpWebRequest(string url) 31 | { 32 | // Use Create instead of CreateHttp for compatibility with .Net Framework 4.0 33 | return WebRequest.Create(url) as HttpWebRequest; 34 | } 35 | 36 | /// 37 | /// Creates and send an HTTP request to the specified URL 38 | /// 39 | /// A valid HTTP method 40 | /// Target URL 41 | /// Optional request data 42 | /// Response string 43 | /// Thrown when an exception occurs at server 44 | /// Thrown when server is unreachable 45 | public static string SendRequest(string httpMethod, string url, object parameters) 46 | { 47 | httpMethod = httpMethod.ToUpper(); 48 | 49 | if (httpMethod == "GET" && parameters != null) 50 | { 51 | var dict = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(parameters)); 52 | url = AppendUrlParameters(url, dict); 53 | } 54 | 55 | ConfigureTls(); 56 | var webRequest = HttpWebRequestFactory(url); 57 | webRequest.Method = httpMethod; 58 | webRequest.Credentials = new NetworkCredential(ApiKey, ""); 59 | 60 | if(httpMethod != "GET" && parameters != null) 61 | { 62 | webRequest.ContentType = "application/json"; 63 | using (var sw = new StreamWriter(webRequest.GetRequestStream())) 64 | { 65 | sw.Write(JsonConvert.SerializeObject(parameters)); 66 | sw.Flush(); 67 | } 68 | } 69 | 70 | try 71 | { 72 | string result = null; 73 | HttpWebResponse response = webRequest.GetResponse() as HttpWebResponse; 74 | using (var sr = new StreamReader(response.GetResponseStream())) 75 | { 76 | result = sr.ReadToEnd(); 77 | } 78 | 79 | return result; 80 | } 81 | catch (WebException ex) 82 | { 83 | if (ex.Status == WebExceptionStatus.ProtocolError) 84 | { 85 | string result = null; 86 | var response = ex.Response as HttpWebResponse; 87 | using (var sr = new StreamReader(response.GetResponseStream())) 88 | { 89 | result = sr.ReadToEnd(); 90 | } 91 | 92 | if ((int) response.StatusCode == 429) 93 | { 94 | throw new TooManyRequestsException("Too Many Requests") 95 | { 96 | HttpStatusCode = (int)response.StatusCode, 97 | ResponsePayload = result 98 | }; 99 | } 100 | 101 | if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 600) 102 | { 103 | dynamic resObj = JsonConvert.DeserializeObject(result); 104 | 105 | var msg = ""; 106 | try { msg = resObj.message; } catch {} 107 | var exception = new ApiException(msg) 108 | { 109 | HttpStatusCode = (int)response.StatusCode, 110 | ResponsePayload = result, 111 | }; 112 | 113 | try { exception.Type = resObj.type.ToString(); } catch {} 114 | try { exception.Errors = resObj.errors.ToString(); } catch {} 115 | 116 | try 117 | { 118 | exception.ErrorsDictionary = 119 | JsonConvert.DeserializeObject>>(resObj.errors.ToString()); 120 | } catch {} 121 | 122 | throw exception; 123 | } 124 | } 125 | 126 | throw new NetworkException("Could not connect to Moyasar service", ex); 127 | } 128 | } 129 | 130 | private static void ConfigureTls() 131 | { 132 | ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; // TLS 1.2 133 | } 134 | 135 | public static string AppendUrlParameters(string url, BasicStringDict parameters) 136 | { 137 | if (parameters == null) 138 | { 139 | return url; 140 | } 141 | 142 | var p = new StringBuilder(); 143 | foreach (var parameter in parameters) 144 | { 145 | if (string.IsNullOrEmpty(parameter.Value)) 146 | { 147 | continue; 148 | } 149 | 150 | p.Append($"&{parameter.Key.ToLower()}={parameter.Value}"); 151 | } 152 | 153 | if (p.Length > 0) 154 | { 155 | return $"{url}?{p.ToString().Substring(1)}"; 156 | } 157 | 158 | return url; 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### ASPNETCore ### 2 | ## Ignore Visual Studio temporary files, build results, and 3 | ## files generated by popular Visual Studio add-ons. 4 | 5 | # User-specific files 6 | *.suo 7 | *.user 8 | *.userosscache 9 | *.sln.docstates 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | [Ll]og/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # Visual Studio code coverage results 115 | *.coverage 116 | *.coveragexml 117 | 118 | # NCrunch 119 | _NCrunch_* 120 | .*crunch*.local.xml 121 | nCrunchTemp_* 122 | 123 | # MightyMoose 124 | *.mm.* 125 | AutoTest.Net/ 126 | 127 | # Web workbench (sass) 128 | .sass-cache/ 129 | 130 | # Installshield output folder 131 | [Ee]xpress/ 132 | 133 | # DocProject is a documentation generator add-in 134 | DocProject/buildhelp/ 135 | DocProject/Help/*.HxT 136 | DocProject/Help/*.HxC 137 | DocProject/Help/*.hhc 138 | DocProject/Help/*.hhk 139 | DocProject/Help/*.hhp 140 | DocProject/Help/Html2 141 | DocProject/Help/html 142 | 143 | # Click-Once directory 144 | publish/ 145 | 146 | # Publish Web Output 147 | *.[Pp]ublish.xml 148 | *.azurePubxml 149 | # TODO: Comment the next line if you want to checkin your web deploy settings 150 | # but database connection strings (with potential passwords) will be unencrypted 151 | *.pubxml 152 | *.publishproj 153 | 154 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 155 | # checkin your Azure Web App publish settings, but sensitive information contained 156 | # in these scripts will be unencrypted 157 | PublishScripts/ 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | # NuGet v3's project.json files produces more ignoreable files 168 | *.nuget.props 169 | *.nuget.targets 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Windows Store app package directories and files 180 | AppPackages/ 181 | BundleArtifacts/ 182 | Package.StoreAssociation.xml 183 | _pkginfo.txt 184 | 185 | # Visual Studio cache files 186 | # files ending in .cache can be ignored 187 | *.[Cc]ache 188 | # but keep track of directories ending in .cache 189 | !*.[Cc]ache/ 190 | 191 | # Others 192 | ClientBin/ 193 | ~$* 194 | *~ 195 | *.dbmdl 196 | *.dbproj.schemaview 197 | *.jfm 198 | *.pfx 199 | *.publishsettings 200 | node_modules/ 201 | orleans.codegen.cs 202 | 203 | # Since there are multiple workflows, uncomment next line to ignore bower_components 204 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 205 | #bower_components/ 206 | 207 | # RIA/Silverlight projects 208 | Generated_Code/ 209 | 210 | # Backup & report files from converting an old project file 211 | # to a newer Visual Studio version. Backup files are not needed, 212 | # because we have git ;-) 213 | _UpgradeReport_Files/ 214 | Backup*/ 215 | UpgradeLog*.XML 216 | UpgradeLog*.htm 217 | 218 | # SQL Server files 219 | *.mdf 220 | *.ldf 221 | 222 | # Business Intelligence projects 223 | *.rdl.data 224 | *.bim.layout 225 | *.bim_*.settings 226 | 227 | # Microsoft Fakes 228 | FakesAssemblies/ 229 | 230 | # GhostDoc plugin setting file 231 | *.GhostDoc.xml 232 | 233 | # Node.js Tools for Visual Studio 234 | .ntvs_analysis.dat 235 | 236 | # Visual Studio 6 build log 237 | *.plg 238 | 239 | # Visual Studio 6 workspace options file 240 | *.opt 241 | 242 | # Visual Studio LightSwitch build output 243 | **/*.HTMLClient/GeneratedArtifacts 244 | **/*.DesktopClient/GeneratedArtifacts 245 | **/*.DesktopClient/ModelManifest.xml 246 | **/*.Server/GeneratedArtifacts 247 | **/*.Server/ModelManifest.xml 248 | _Pvt_Extensions 249 | 250 | # Paket dependency manager 251 | .paket/paket.exe 252 | paket-files/ 253 | 254 | # FAKE - F# Make 255 | .fake/ 256 | 257 | # JetBrains Rider 258 | .idea/ 259 | *.sln.iml 260 | 261 | # CodeRush 262 | .cr/ 263 | 264 | # Python Tools for Visual Studio (PTVS) 265 | __pycache__/ 266 | *.pyc 267 | 268 | # Cake - Uncomment if you are using it 269 | # tools/ 270 | 271 | ### macOS ### 272 | # General 273 | .DS_Store 274 | .AppleDouble 275 | .LSOverride 276 | 277 | # Icon must end with two \r 278 | Icon 279 | 280 | # Thumbnails 281 | ._* 282 | 283 | # Files that might appear in the root of a volume 284 | .DocumentRevisions-V100 285 | .fseventsd 286 | .Spotlight-V100 287 | .TemporaryItems 288 | .Trashes 289 | .VolumeIcon.icns 290 | .com.apple.timemachine.donotpresent 291 | 292 | # Directories potentially created on remote AFP share 293 | .AppleDB 294 | .AppleDesktop 295 | Network Trash Folder 296 | Temporary Items 297 | .apdisk 298 | 299 | ### Rider ### 300 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 301 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 302 | 303 | # User-specific stuff 304 | .idea/**/workspace.xml 305 | .idea/**/tasks.xml 306 | .idea/**/usage.statistics.xml 307 | .idea/**/dictionaries 308 | .idea/**/shelf 309 | 310 | # Generated files 311 | .idea/**/contentModel.xml 312 | 313 | # Sensitive or high-churn files 314 | .idea/**/dataSources/ 315 | .idea/**/dataSources.ids 316 | .idea/**/dataSources.local.xml 317 | .idea/**/sqlDataSources.xml 318 | .idea/**/dynamic.xml 319 | .idea/**/uiDesigner.xml 320 | .idea/**/dbnavigator.xml 321 | 322 | # Gradle 323 | .idea/**/gradle.xml 324 | .idea/**/libraries 325 | 326 | # Gradle and Maven with auto-import 327 | # When using Gradle or Maven with auto-import, you should exclude module files, 328 | # since they will be recreated, and may cause churn. Uncomment if using 329 | # auto-import. 330 | # .idea/modules.xml 331 | # .idea/*.iml 332 | # .idea/modules 333 | 334 | # CMake 335 | cmake-build-*/ 336 | 337 | # Mongo Explorer plugin 338 | .idea/**/mongoSettings.xml 339 | 340 | # File-based project format 341 | *.iws 342 | 343 | # IntelliJ 344 | out/ 345 | 346 | # mpeltonen/sbt-idea plugin 347 | .idea_modules/ 348 | 349 | # JIRA plugin 350 | atlassian-ide-plugin.xml 351 | 352 | # Cursive Clojure plugin 353 | .idea/replstate.xml 354 | 355 | # Crashlytics plugin (for Android Studio and IntelliJ) 356 | com_crashlytics_export_strings.xml 357 | crashlytics.properties 358 | crashlytics-build.properties 359 | fabric.properties 360 | 361 | # Editor-based Rest Client 362 | .idea/httpRequests 363 | 364 | # Android studio 3.1+ serialized cache file 365 | .idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /Moyasar/Services/Payment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moyasar.Abstraction; 4 | using Moyasar.Exceptions; 5 | using Moyasar.Models; 6 | using Moyasar.Providers; 7 | using Newtonsoft.Json; 8 | 9 | namespace Moyasar.Services 10 | { 11 | public class Payment : Resource 12 | { 13 | [JsonProperty("id")] 14 | public string Id { get; private set; } 15 | 16 | [JsonProperty("status")] 17 | public string Status { get; private set; } 18 | 19 | [JsonProperty("amount")] 20 | public int Amount { get; private set; } 21 | 22 | [JsonProperty("fee")] 23 | public int Fee { get; private set; } 24 | 25 | [JsonProperty("refunded")] 26 | public int RefundedAmount { get; private set; } 27 | 28 | [JsonProperty("refunded_at")] 29 | public DateTime? RefundedAt { get; private set; } 30 | 31 | [JsonProperty("captured")] 32 | public int CapturedAmount { get; private set; } 33 | 34 | [JsonProperty("captured_at")] 35 | public DateTime? CapturedAt { get; private set; } 36 | 37 | [JsonProperty("voided_at")] 38 | public DateTime? VoidedAt { get; private set; } 39 | 40 | [JsonProperty("amount_format")] 41 | public string FormattedAmount { get; private set; } 42 | 43 | [JsonProperty("fee_format")] 44 | public string FormattedFee { get; private set; } 45 | 46 | [JsonProperty("refunded_format")] 47 | public string FormattedRefundedAmount { get; private set; } 48 | 49 | [JsonProperty("captured_format")] 50 | public string FormattedCapturedAmount { get; private set; } 51 | 52 | [JsonProperty("currency")] 53 | public string Currency { get; private set; } 54 | 55 | [JsonProperty("invoice_id")] 56 | public string InvoiceId { get; private set; } 57 | 58 | [JsonProperty("ip")] 59 | public string Ip { get; private set; } 60 | 61 | [JsonProperty("callback_url")] 62 | public string CallbackUrl { get; private set; } 63 | 64 | [JsonProperty("created_at")] 65 | public DateTime? CreatedAt { get; private set; } 66 | 67 | [JsonProperty("updated_at")] 68 | public DateTime? UpdatedAt { get; private set; } 69 | 70 | [JsonProperty("metadata")] 71 | public Dictionary Metadata { get; private set; } 72 | 73 | [JsonProperty("source")] 74 | [JsonConverter(typeof(PaymentMethodConverter))] 75 | public IPaymentMethod Source { get; private set; } 76 | 77 | [JsonProperty("description")] 78 | public string Description { get; set; } 79 | 80 | internal Payment() { } 81 | 82 | protected static string GetRefundUrl(string id) => $"{ResourceUrl}/{id}/refund"; 83 | protected static string GetCaptureUrl(string id) => $"{ResourceUrl}/{id}/capture"; 84 | protected static string GetVoidUrl(string id) => $"{ResourceUrl}/{id}/void"; 85 | 86 | /// 87 | /// Updates the following fields 88 | /// 89 | /// Description 90 | /// 91 | /// 92 | /// Thrown when an exception occurs at server 93 | /// Thrown when server is unreachable 94 | public void Update() 95 | { 96 | MoyasarService.SendRequest("PUT", GetUpdateUrl(Id), new Dictionary() 97 | { 98 | { "description", Description } 99 | }); 100 | } 101 | 102 | /// 103 | /// Refunds the current payment 104 | /// 105 | /// Optional amount to refund, should be equal to or less than current amount 106 | /// An updated Payment instance 107 | /// Thrown when an exception occurs at server 108 | /// Thrown when server is unreachable 109 | public Payment Refund(int? amount = null) 110 | { 111 | if (amount > this.Amount) 112 | { 113 | throw new ValidationException("Amount to be refunded must be equal to or less than current amount"); 114 | } 115 | 116 | var reqParams = amount != null ? new Dictionary 117 | { 118 | { "amount", amount.Value } 119 | } : null; 120 | 121 | return DeserializePayment 122 | ( 123 | MoyasarService.SendRequest("POST", GetRefundUrl(Id), reqParams), 124 | this 125 | ); 126 | } 127 | 128 | /// 129 | /// Captures the current authorized payment 130 | /// 131 | /// Optional amount to capture, should be equal to or less than current amount 132 | /// An updated Payment instance 133 | /// Thrown when an exception occurs at server 134 | /// Thrown when server is unreachable 135 | public Payment Capture(int? amount = null) 136 | { 137 | if (amount > this.Amount) 138 | { 139 | throw new ValidationException("Amount to be refunded must be equal to or less than current amount"); 140 | } 141 | 142 | if (Status != "authorized") 143 | { 144 | throw new ValidationException("Payment is not in authorized status."); 145 | } 146 | 147 | var reqParams = amount != null ? new Dictionary 148 | { 149 | { "amount", amount.Value } 150 | } : null; 151 | 152 | return DeserializePayment 153 | ( 154 | MoyasarService.SendRequest("POST", GetCaptureUrl(Id), reqParams), 155 | this 156 | ); 157 | } 158 | 159 | /// 160 | /// Voids the current authorized payment 161 | /// 162 | /// An updated Payment instance 163 | /// Thrown when an exception occurs at server 164 | /// Thrown when server is unreachable 165 | public Payment Void() 166 | { 167 | if (Status != "authorized") 168 | { 169 | throw new ValidationException("Payment is not in authorized status."); 170 | } 171 | 172 | return DeserializePayment 173 | ( 174 | MoyasarService.SendRequest("POST", GetVoidUrl(Id), null), 175 | this 176 | ); 177 | } 178 | 179 | /// 180 | /// Get an payment from Moyasar by Id 181 | /// 182 | /// Payment Id 183 | /// Payment instance representing an payment created at Moyasar 184 | /// Thrown when an exception occurs at server 185 | /// Thrown when server is unreachable 186 | public static Payment Fetch(string id) 187 | { 188 | return DeserializePayment(MoyasarService.SendRequest( 189 | "GET", 190 | GetFetchUrl(id), 191 | null 192 | )); 193 | } 194 | 195 | /// 196 | /// Retrieve provisioned payments at Moyasar 197 | /// 198 | /// Used to filter results 199 | /// A list of payments 200 | /// Thrown when an exception occurs at server 201 | /// Thrown when server is unreachable 202 | public static PaginationResult List(SearchQuery query = null) 203 | { 204 | var responseJson = MoyasarService.SendRequest( 205 | "GET", 206 | GetListUrl(), 207 | query 208 | ); 209 | 210 | return JsonConvert.DeserializeObject>(responseJson); 211 | } 212 | 213 | internal static Payment DeserializePayment(string json, Payment obj = null) 214 | { 215 | var payment = obj ?? new Payment(); 216 | JsonConvert.PopulateObject(json, payment); 217 | return payment; 218 | } 219 | } 220 | } -------------------------------------------------------------------------------- /MoyasarTest/PaymentTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Moyasar.Abstraction; 5 | using Moyasar.Exceptions; 6 | using Moyasar.Models; 7 | using Moyasar.Services; 8 | using MoyasarTest.Helpers; 9 | using Xunit; 10 | using BasicDict = System.Collections.Generic.Dictionary; 11 | 12 | namespace MoyasarTest 13 | { 14 | public class PaymentTest 15 | { 16 | [Fact(DisplayName = "Method must return with no exceptions thrown if data are valid")] 17 | public void TestValidatePaymentInfo() 18 | { 19 | GetValidPaymentInfo().Validate(); 20 | GetValidPaymentInfo(GetValidApplePaySource()).Validate(); 21 | GetValidPaymentInfo(GetValidStcPaySource()).Validate(); 22 | } 23 | 24 | [Fact] 25 | public async void TestPaymentInfoValidationRules() 26 | { 27 | var info = GetValidPaymentInfo(); 28 | info.Validate(); 29 | info.Amount = -1; 30 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 31 | 32 | info = GetValidPaymentInfo(); 33 | info.Validate(); 34 | info.Source = null; 35 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 36 | 37 | info = GetValidPaymentInfo(); 38 | info.Validate(); 39 | info.CallbackUrl = "hey"; 40 | await Assert.ThrowsAsync(async () => await Task.Run(() => info.Validate())); 41 | } 42 | 43 | [Fact] 44 | public async void TestCcSourceValidationRules() 45 | { 46 | var source = GetValidCcSource(); 47 | source.Validate(); 48 | source.Name = ""; 49 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 50 | 51 | source = GetValidCcSource(); 52 | source.Validate(); 53 | source.Number = ""; 54 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 55 | 56 | source = GetValidCcSource(); 57 | source.Validate(); 58 | source.Cvc = 0; 59 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 60 | source.Cvc = 1000; 61 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 62 | 63 | source = GetValidCcSource(); 64 | source.Validate(); 65 | source.Month = 0; 66 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 67 | source.Month = 13; 68 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 69 | 70 | source = GetValidCcSource(); 71 | source.Validate(); 72 | source.Year = -1; 73 | await Assert.ThrowsAsync(async () => await Task.Run(() => source.Validate())); 74 | } 75 | 76 | [Fact] 77 | public void TestDeserializingPayment() 78 | { 79 | ServiceMockHelper.MockJsonResponse("Fixtures/CreditCard/Paid.json"); 80 | 81 | var payment = Payment.Fetch("b6c01c90-a091-45a4-9358-71668ecbf7ea"); 82 | Assert.Equal("b6c01c90-a091-45a4-9358-71668ecbf7ea", payment.Id); 83 | Assert.Equal(1000, payment.Amount); 84 | Assert.Equal("SAR", payment.Currency); 85 | Assert.Equal("Test Payment", payment.Description); 86 | Assert.Equal("https://mystore.com/order/redirect-back", payment.CallbackUrl); 87 | Assert.Equal("5c02ba44-7fd1-444c-b82b-d3993b87d4b0", payment.Metadata["order_id"]); 88 | Assert.Equal("50", payment.Metadata["tax"]); 89 | 90 | Assert.IsType(payment.Source); 91 | var ccSource = (CreditCard) payment.Source; 92 | 93 | Assert.Equal("Long John", ccSource.Name); 94 | Assert.Equal("XXXX-XXXX-XXXX-1111", ccSource.Number); 95 | Assert.Equal("moyasar_ap_je1iUidxhrh74257S891wvW", ccSource.GatewayId); 96 | Assert.Equal("125478454231", ccSource.ReferenceNumber); 97 | } 98 | 99 | [Fact] 100 | public void TestDeserializingApplePayPayment() 101 | { 102 | ServiceMockHelper.MockJsonResponse("Fixtures/ApplePay/Paid.json"); 103 | 104 | var payment = Payment.Fetch("a4a144ba-adc3-43bd-98e8-c80f2925fdc4"); 105 | Assert.Equal(1000, payment.Amount); 106 | Assert.Equal("SAR", payment.Currency); 107 | Assert.Equal("Test Payment", payment.Description); 108 | Assert.Null(payment.CallbackUrl); 109 | 110 | Assert.IsType(payment.Source); 111 | var applePaySource = (ApplePayMethod) payment.Source; 112 | 113 | Assert.Equal("applepay", applePaySource.Type); 114 | Assert.Equal("XXXX-XXXX-XXXX-1111", applePaySource.Number); 115 | Assert.Equal("APPROVED", applePaySource.Message); 116 | Assert.Equal("moyasar_ap_je1iUidxhrh74257S891wvW", applePaySource.GatewayId); 117 | Assert.Equal("125478454231", applePaySource.ReferenceNumber); 118 | } 119 | 120 | [Fact] 121 | public void TestDeserializingStcPayPayment() 122 | { 123 | ServiceMockHelper.MockJsonResponse("Fixtures/StcPay/Paid.json"); 124 | 125 | var payment = Payment.Fetch("50559d3b-e67f-4b3a-8df8-509dde19fe38"); 126 | Assert.Equal(1000, payment.Amount); 127 | Assert.Equal("SAR", payment.Currency); 128 | Assert.Equal("Test Payment", payment.Description); 129 | Assert.Null(payment.CallbackUrl); 130 | 131 | Assert.IsType(payment.Source); 132 | var method = (StcPayMethod) payment.Source; 133 | Assert.Equal("stcpay", method.Type); 134 | Assert.Equal("0555555555", method.Mobile); 135 | Assert.Equal("Paid", method.Message); 136 | } 137 | 138 | [Fact] 139 | public void TestRefundPayment() 140 | { 141 | ServiceMockHelper.MockJsonResponse("Fixtures/CreditCard/Paid.json"); 142 | 143 | var payment = Payment.Fetch("b6c01c90-a091-45a4-9358-71668ecbf7ea"); 144 | var id = payment.Id; 145 | var amount = payment.Amount; 146 | 147 | ServiceMockHelper.MockJsonResponse("Fixtures/CreditCard/Refunded.json"); 148 | 149 | payment.Refund(); 150 | 151 | Assert.Equal(id, payment.Id); 152 | Assert.Equal("refunded", payment.Status); 153 | Assert.Equal(amount, payment.RefundedAmount); 154 | Assert.Equal(DateTime.Parse("2019-01-03T10:14:14.414Z").ToUniversalTime(), payment.RefundedAt); 155 | } 156 | 157 | [Fact] 158 | public async void RefundHigherAmountMustThrowException() 159 | { 160 | ServiceMockHelper.MockJsonResponse("Fixtures/CreditCard/Paid.json"); 161 | 162 | var payment = Payment.Fetch("b6c01c90-a091-45a4-9358-71668ecbf7ea"); 163 | var id = payment.Id; 164 | var amount = payment.Amount; 165 | 166 | ServiceMockHelper.MockJsonResponse("Fixtures/CreditCard/Refunded.json"); 167 | 168 | await Assert.ThrowsAsync 169 | ( 170 | async () => await Task.Run(() => payment.Refund(amount + 1)) 171 | ); 172 | } 173 | 174 | [Fact] 175 | public void TestPaymentListing() 176 | { 177 | ServiceMockHelper.MockJsonResponse("Fixtures/PaymentList.json"); 178 | var pagination = Payment.List(); 179 | 180 | Assert.IsType(pagination.Items[0].Source); 181 | Assert.IsType(pagination.Items[1].Source); 182 | Assert.IsType(pagination.Items[2].Source); 183 | 184 | Assert.Equal(2, pagination.CurrentPage); 185 | Assert.Equal(3, pagination.NextPage); 186 | Assert.Equal(1, pagination.PreviousPage); 187 | Assert.Equal(3, pagination.TotalPages); 188 | Assert.Equal(9, pagination.TotalCount); 189 | } 190 | 191 | internal static PaymentInfo GetValidPaymentInfo(IPaymentSource source = null) 192 | { 193 | return new PaymentInfo 194 | { 195 | Amount = 3500, 196 | Currency = "SAR", 197 | Description = "Chinese Noodles Meal", 198 | Source = source ?? GetValidCcSource(), 199 | CallbackUrl = "http://mysite.test/payment_callback", 200 | Metadata = new Dictionary 201 | { 202 | {"order_id", "1232141"}, 203 | {"store_note", "okay"} 204 | } 205 | }; 206 | } 207 | 208 | private static CreditCardSource GetValidCcSource() 209 | { 210 | return new CreditCardSource 211 | { 212 | Name = "John Doe", 213 | Number = "4111111111111111", 214 | Cvc = 141, 215 | Month = 3, 216 | Year = 2021, 217 | }; 218 | } 219 | 220 | private static ApplePaySource GetValidApplePaySource() 221 | { 222 | return new ApplePaySource 223 | { 224 | Token = @"{""stuff"":""foobar""}" 225 | }; 226 | } 227 | 228 | private static StcPaySource GetValidStcPaySource() 229 | { 230 | return new StcPaySource 231 | { 232 | Branch = "1", 233 | Cashier = "1", 234 | Mobile = "0555555555" 235 | }; 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Moyasar/Services/Invoice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Moyasar.Abstraction; 6 | using Moyasar.Exceptions; 7 | using Moyasar.Models; 8 | using Newtonsoft.Json; 9 | 10 | namespace Moyasar.Services 11 | { 12 | /// 13 | /// Represents a Moyasar Invoice 14 | /// 15 | public class Invoice : Resource 16 | { 17 | [JsonProperty("id")] 18 | public string Id { get; private set; } 19 | 20 | [JsonProperty("status")] 21 | public string Status { get; private set; } 22 | 23 | [JsonProperty("amount")] 24 | public int Amount { get; set; } 25 | 26 | [JsonProperty("currency")] 27 | public string Currency { get; set; } 28 | 29 | [JsonProperty("description")] 30 | public string Description { get; set; } 31 | 32 | [JsonProperty("expired_at")] 33 | public DateTime? ExpiredAt { get; set; } 34 | 35 | [JsonProperty("logo_url")] 36 | public string LogoUrl { get; private set; } 37 | 38 | [JsonProperty("amount_format")] 39 | public string FormattedAmount { get; private set; } 40 | 41 | [JsonProperty("url")] 42 | public string Url { get; private set; } 43 | 44 | [JsonProperty("created_at")] 45 | public DateTime? CreatedAt { get; private set; } 46 | 47 | [JsonProperty("updated_at")] 48 | public DateTime? UpdatedAt { get; private set; } 49 | 50 | [JsonProperty("payments")] 51 | public List Payments { get; private set; } 52 | 53 | [JsonProperty("callback_url")] 54 | public string CallbackUrl { get; set; } 55 | 56 | [JsonProperty("metadata")] 57 | public Dictionary Metadata { get; private set; } 58 | 59 | private static string GetCancelUrl(string id) => $"{ResourceUrl}/{id}/cancel"; 60 | private static string GetCreateBulkUrl() => $"{ResourceUrl}/bulk"; 61 | 62 | [JsonConstructor] 63 | internal Invoice() { } 64 | 65 | /// 66 | /// Updates the following fields 67 | /// 68 | /// Amount 69 | /// Currency 70 | /// Description 71 | /// 72 | /// 73 | /// Thrown when an exception occurs at server 74 | /// Thrown when server is unreachable 75 | public void Update() 76 | { 77 | Validate(true); 78 | 79 | var requestParams = new Dictionary() 80 | { 81 | { "amount", Amount }, 82 | { "currency", Currency }, 83 | { "description", Description } 84 | }; 85 | 86 | if (CallbackUrl != null) 87 | { 88 | requestParams.Add("callback_url", CallbackUrl); 89 | } 90 | 91 | if (ExpiredAt != null) 92 | { 93 | requestParams.Add("expired_at", ExpiredAt); 94 | } 95 | 96 | if (Metadata != null) 97 | { 98 | requestParams.Add("metadata", Metadata); 99 | } 100 | 101 | var response = MoyasarService.SendRequest("PUT", GetUpdateUrl(Id), requestParams); 102 | DeserializeInvoice(response, this); 103 | } 104 | 105 | /// 106 | /// Changes invoice state to canceled 107 | /// 108 | /// Thrown when an exception occurs at server 109 | /// Thrown when server is unreachable 110 | public void Cancel() 111 | { 112 | var response = MoyasarService.SendRequest("PUT", GetCancelUrl(Id), null); 113 | DeserializeInvoice(response, this); 114 | } 115 | 116 | /// 117 | /// Throws a ValidationException when one or more fields are invalid 118 | /// 119 | /// 120 | public void Validate(bool isUpdating = false) 121 | { 122 | var errors = new List(); 123 | 124 | if (Amount < 1) errors.Add(new FieldError() 125 | { 126 | Field = nameof(Amount), 127 | Error = "Amount must be a positive integer greater than 0" 128 | }); 129 | 130 | if (Currency == null) errors.Add(new FieldError() 131 | { 132 | Field = nameof(Currency), 133 | Error = "Field is required" 134 | }); 135 | 136 | if (Description == null) errors.Add(new FieldError() 137 | { 138 | Field = nameof(Description), 139 | Error = "Field is required" 140 | }); 141 | 142 | if (CallbackUrl != null) 143 | { 144 | try 145 | { 146 | new Uri(CallbackUrl); 147 | } 148 | catch 149 | { 150 | errors.Add(new FieldError() 151 | { 152 | Field = nameof(CallbackUrl), 153 | Error = "CallbackUrl must be a valid URI" 154 | }); 155 | } 156 | } 157 | 158 | if (!isUpdating && ExpiredAt != null && ExpiredAt.Value <= DateTime.Now) 159 | { 160 | errors.Add(new FieldError() 161 | { 162 | Field = nameof(ExpiredAt), 163 | Error = "ExpiredAt must be a future date and time" 164 | }); 165 | } 166 | 167 | if (errors.Count > 0) 168 | { 169 | throw new ValidationException 170 | { 171 | FieldErrors = errors 172 | }; 173 | } 174 | } 175 | 176 | /// 177 | /// Creates a new invoice at Moyasar and returns an Invoice instance for it 178 | /// 179 | /// Information needed to create a new invoice 180 | /// Invoice instance representing an invoice created at Moyasar 181 | /// Thrown when an exception occurs at server 182 | /// Thrown when server is unreachable 183 | public static Invoice Create(InvoiceInfo info) 184 | { 185 | info.Validate(); 186 | var response = MoyasarService.SendRequest("POST", GetCreateUrl(), info); 187 | 188 | return DeserializeInvoice(response); 189 | } 190 | 191 | /// 192 | /// Creates a new invoice at Moyasar and returns an Invoice instance for it 193 | /// 194 | /// Information needed to create a new invoice 195 | /// Invoice instance representing an invoice created at Moyasar 196 | /// Thrown when an exception occurs at server 197 | /// Thrown when server is unreachable 198 | public static List CreateBulk(List info) 199 | { 200 | if (info.Count == 0) 201 | { 202 | throw new ValidationException("At least one invoice is required."); 203 | } 204 | 205 | if (info.Count > 50) 206 | { 207 | throw new ValidationException("No more than 50 invoices is allowed at once."); 208 | } 209 | 210 | var data = new Dictionary> 211 | { 212 | {"invoices", info} 213 | }; 214 | 215 | var response = MoyasarService.SendRequest("POST", GetCreateBulkUrl(), data); 216 | 217 | return JsonConvert.DeserializeObject>>(response).First().Value; 218 | } 219 | 220 | /// 221 | /// Get an invoice from Moyasar by Id 222 | /// 223 | /// Invoice Id 224 | /// Invoice instance representing an invoice created at Moyasar 225 | /// Thrown when an exception occurs at server 226 | /// Thrown when server is unreachable 227 | public static Invoice Fetch(string id) 228 | { 229 | return DeserializeInvoice(MoyasarService.SendRequest("GET", GetFetchUrl(id), null)); 230 | } 231 | 232 | /// 233 | /// Retrieve provisioned invoices at Moyasar 234 | /// 235 | /// Used to filter results 236 | /// A list of invoices 237 | /// Thrown when an exception occurs at server 238 | /// Thrown when server is unreachable 239 | public static PaginationResult List(SearchQuery query = null) 240 | { 241 | var responseJson = MoyasarService.SendRequest 242 | ( 243 | "GET", 244 | GetListUrl(), 245 | query 246 | ); 247 | 248 | return JsonConvert.DeserializeObject>(responseJson); 249 | } 250 | 251 | internal static Invoice DeserializeInvoice(string json, Invoice obj = null) 252 | { 253 | var invoice = obj ?? new Invoice(); 254 | JsonConvert.PopulateObject(json, invoice); 255 | return invoice; 256 | } 257 | } 258 | } 259 | --------------------------------------------------------------------------------