diff --git a/Streetwriters.Common/Models/ListPaymentsResponse.cs b/Streetwriters.Common/Models/ListPaymentsResponse.cs new file mode 100644 index 0000000..1703a84 --- /dev/null +++ b/Streetwriters.Common/Models/ListPaymentsResponse.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json.Serialization; + +namespace Streetwriters.Common.Models +{ + public partial class ListPaymentsResponse + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("response")] + public Payment[] Payments { get; set; } + } + + public partial class Payment + { + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("subscription_id")] + public long SubscriptionId { get; set; } + + [JsonPropertyName("amount")] + public double Amount { get; set; } + + [JsonPropertyName("currency")] + public string Currency { get; set; } + + [JsonPropertyName("payout_date")] + public string PayoutDate { get; set; } + + [JsonPropertyName("is_paid")] + public short IsPaid { get; set; } + + [JsonPropertyName("is_one_off_charge")] + public bool IsOneOffCharge { get; set; } + + [JsonPropertyName("receipt_url")] + public string ReceiptUrl { get; set; } + } +} diff --git a/Streetwriters.Common/Models/ListTransactionsResponse.cs b/Streetwriters.Common/Models/ListTransactionsResponse.cs new file mode 100644 index 0000000..9379eb7 --- /dev/null +++ b/Streetwriters.Common/Models/ListTransactionsResponse.cs @@ -0,0 +1,77 @@ +namespace Streetwriters.Common.Models +{ + using System; + using System.Text.Json.Serialization; + + public partial class ListTransactionsResponse + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("response")] + public Transaction[] Transactions { get; set; } + } + + public partial class Transaction + { + [JsonPropertyName("order_id")] + public string OrderId { get; set; } + + [JsonPropertyName("checkout_id")] + public string CheckoutId { get; set; } + + [JsonPropertyName("amount")] + public string Amount { get; set; } + + [JsonPropertyName("currency")] + public string Currency { get; set; } + + [JsonPropertyName("status")] + public string Status { get; set; } + + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } + + [JsonPropertyName("passthrough")] + public object Passthrough { get; set; } + + [JsonPropertyName("product_id")] + public long ProductId { get; set; } + + [JsonPropertyName("is_subscription")] + public bool IsSubscription { get; set; } + + [JsonPropertyName("is_one_off")] + public bool IsOneOff { get; set; } + + [JsonPropertyName("subscription")] + public PaddleSubscription Subscription { get; set; } + + [JsonPropertyName("user")] + public PaddleTransactionUser User { get; set; } + + [JsonPropertyName("receipt_url")] + public string ReceiptUrl { get; set; } + } + + public partial class PaddleSubscription + { + [JsonPropertyName("subscription_id")] + public long SubscriptionId { get; set; } + + [JsonPropertyName("status")] + public string Status { get; set; } + } + + public partial class PaddleTransactionUser + { + [JsonPropertyName("user_id")] + public long UserId { get; set; } + + [JsonPropertyName("email")] + public string Email { get; set; } + + [JsonPropertyName("marketing_consent")] + public bool MarketingConsent { get; set; } + } +} diff --git a/Streetwriters.Common/Models/ListUsersResponse.cs b/Streetwriters.Common/Models/ListUsersResponse.cs new file mode 100644 index 0000000..f0b1c0a --- /dev/null +++ b/Streetwriters.Common/Models/ListUsersResponse.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json.Serialization; + +namespace Streetwriters.Common.Models +{ + public partial class ListUsersResponse + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("response")] + public PaddleUser[] Users { get; set; } + } + + public class PaddleUser + { + [JsonPropertyName("subscription_id")] + public long SubscriptionId { get; set; } + + [JsonPropertyName("plan_id")] + public long PlanId { get; set; } + + [JsonPropertyName("user_id")] + public long UserId { get; set; } + + [JsonPropertyName("user_email")] + public string UserEmail { get; set; } + + [JsonPropertyName("marketing_consent")] + public bool MarketingConsent { get; set; } + + [JsonPropertyName("update_url")] + public string UpdateUrl { get; set; } + + [JsonPropertyName("cancel_url")] + public string CancelUrl { get; set; } + + [JsonPropertyName("state")] + public string State { get; set; } + + [JsonPropertyName("signup_date")] + public string SignupDate { get; set; } + + [JsonPropertyName("quantity")] + public long Quantity { get; set; } + } +} diff --git a/Streetwriters.Common/Models/RefundPaymentResponse.cs b/Streetwriters.Common/Models/RefundPaymentResponse.cs new file mode 100644 index 0000000..d74a294 --- /dev/null +++ b/Streetwriters.Common/Models/RefundPaymentResponse.cs @@ -0,0 +1,20 @@ +using System; +using System.Text.Json.Serialization; + +namespace Streetwriters.Common.Models +{ + public partial class RefundPaymentResponse + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("response")] + public Refund Refund { get; set; } + } + + public partial class Refund + { + [JsonPropertyName("refund_request_id")] + public long RefundRequestId { get; set; } + } +} diff --git a/Streetwriters.Common/Services/PaddleService.cs b/Streetwriters.Common/Services/PaddleService.cs new file mode 100644 index 0000000..1ab9801 --- /dev/null +++ b/Streetwriters.Common/Services/PaddleService.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Streetwriters.Common.Models; + +namespace Streetwriters.Common.Services +{ + public class PaddleService(string vendorId, string vendorAuthCode) + { +#if (DEBUG || STAGING) + const string PADDLE_BASE_URI = "https://sandbox-vendors.paddle.com/api"; +#else + const string PADDLE_BASE_URI = "https://vendors.paddle.com/api"; +#endif + + HttpClient httpClient = new HttpClient(); + + public async Task ListUsersAsync( + string subscriptionId, + int results + ) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/users"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "subscription_id", subscriptionId }, + { "results_per_page", results.ToString() }, + } + ) + ); + + return await response.Content.ReadFromJsonAsync(); + } + + public async Task ListPaymentsAsync( + string subscriptionId, + long planId + ) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/payments"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "subscription_id", subscriptionId }, + { "is_paid", "1" }, + { "plan", planId.ToString() }, + { "is_one_off_charge", "0" }, + } + ) + ); + + return await response.Content.ReadFromJsonAsync(); + } + + public async Task ListTransactionsAsync( + string subscriptionId + ) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/{subscriptionId}/transactions"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + } + ) + ); + + return await response.Content.ReadFromJsonAsync(); + } + + public async Task FindUserFromOrderAsync(string orderId) + { + var url = $"{PADDLE_BASE_URI}/2.0/order/{orderId}/transactions"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + } + ) + ); + var transactions = await response.Content.ReadFromJsonAsync(); + if (transactions.Transactions.Length == 0) return null; + return transactions.Transactions[0].User; + } + + public async Task RefundPaymentAsync(string paymentId, string reason = "") + { + var url = $"{PADDLE_BASE_URI}/2.0/payment/refund"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "order_id", paymentId }, + { "reason", reason }, + } + ) + ); + + var refundResponse = await response.Content.ReadFromJsonAsync(); + return refundResponse.Success; + } + + public async Task CancelSubscriptionAsync(string subscriptionId) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/users_cancel"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "subscription_id", subscriptionId }, + } + ) + ); + + return response.IsSuccessStatusCode; + } + + public async Task PauseSubscriptionAsync(string subscriptionId) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/users/update"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "subscription_id", subscriptionId }, + { "pause", "true" }, + } + ) + ); + + return response.IsSuccessStatusCode; + } + + public async Task ResumeSubscriptionAsync(string subscriptionId) + { + var url = $"{PADDLE_BASE_URI}/2.0/subscription/users/update"; + var httpClient = new HttpClient(); + var response = await httpClient.PostAsync( + url, + new FormUrlEncodedContent( + new Dictionary + { + { "vendor_id", vendorId }, + { "vendor_auth_code", vendorAuthCode }, + { "subscription_id", subscriptionId }, + { "pause", "false" }, + } + ) + ); + + return response.IsSuccessStatusCode; + } + } +}