├── 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 | [](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