From 53aaef26348cf14de81dcc9d507752333171c611 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Mon, 4 Sep 2017 18:44:30 -0700 Subject: [PATCH 01/31] invoices: Add the models --- Sources/Stripe/Models/Invoices/Invoice.swift | 212 +++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 Sources/Stripe/Models/Invoices/Invoice.swift diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift new file mode 100644 index 0000000..4013f22 --- /dev/null +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -0,0 +1,212 @@ +// +// Invoice.swift +// Stripe +// +// Created by Anthony Castelli on 9/4/17. +// +// + +import Foundation +import Vapor + +/** + Invoice Model + https://stripe.com/docs/api#invoice_object + */ +public final class Invoice: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amountDue: Int + public private(set) var applicationFee: Int? + public private(set) var attemptCount: Int + public private(set) var hasAttemptedCharge: Bool + public private(set) var charge: String? + public private(set) var isClosed: Bool + public private(set) var customer: String? + public private(set) var date: Date? + public private(set) var description: String? + public private(set) var discount: String? + public private(set) var endingBalance: Int? + public private(set) var isForgiven: Bool + public private(set) var isLiveMode: Bool + public private(set) var isPaid: Bool + + public private(set) var nextPaymentAttempt: Date? + public private(set) var periodStart: Date? + public private(set) var periodEnd: Date? + + public private(set) var receiptNumber: String? + public private(set) var startingBalance: Int? + + public private(set) var statementDescriptor: String? + public private(set) var subscription: String? + + public private(set) var subtotal: Int + public private(set) var total: Int + + public private(set) var tax: Int? + public private(set) var taxPercent: Int? + + public private(set) var webhooksDeliveredAt: Date? + + public private(set) var metadata: Node? + + public private(set) var lines: [InvoiceItem]? + public private(set) var currency: StripeCurrency? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amountDue = try node.get("amount_due") + self.applicationFee = try node.get("application_fee") + self.attemptCount = try node.get("attempt_count") + self.hasAttemptedCharge = try node.get("attempted") + self.charge = try node.get("charge") + self.isClosed = try node.get("closed") + self.customer = try node.get("customer") + self.date = try node.get("date") + self.description = try node.get("description") + self.discount = try node.get("discount") + self.endingBalance = try node.get("ending_balance") + self.isForgiven = try node.get("forgiven") + self.isLiveMode = try node.get("livemode") + self.isPaid = try node.get("paid") + self.nextPaymentAttempt = try node.get("next_payment_attempt") + self.periodStart = try node.get("period_start") + self.periodEnd = try node.get("period_end") + self.receiptNumber = try node.get("receipt_number") + self.startingBalance = try node.get("starting_balance") + self.statementDescriptor = try node.get("statement_descriptor") + self.subscription = try node.get("subscription") + self.subtotal = try node.get("subtotal") + self.total = try node.get("total") + self.tax = try node.get("tax") + self.taxPercent = try node.get("tax_percent") + + self.webhooksDeliveredAt = try node.get("webhooks_delivered_at") + + self.metadata = try node.get("metadata") + + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + + if let lines = node["lines"]?.object, let data = lines["data"]?.array { + self.lines = try data.map({ try InvoiceItem(node: $0) }) + } + + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount_due": self.amountDue, + "application_fee": self.applicationFee, + "attempt_count": self.attemptCount, + "attempted": self.hasAttemptedCharge, + "charge": self.charge, + "closed": self.isClosed, + "customer": self.customer, + "date": self.date, + "description": self.description, + "discount": self.discount, + "ending_balance": self.endingBalance, + "forgiven": self.isForgiven, + "livemode": self.isLiveMode, + "paid": self.isPaid, + "next_payment_attempt": self.nextPaymentAttempt, + "period_start": self.periodStart, + "period_end": self.periodEnd, + "receipt_number": self.receiptNumber, + "starting_balance": self.startingBalance, + "statement_descriptor": self.statementDescriptor, + "subscription": self.subscription, + "subtotal": self.subtotal, + "total": self.total, + "tax": self.tax, + "tax_percent": self.taxPercent, + "webhooks_delivered_at": self.webhooksDeliveredAt, + + "metadata": self.metadata, + + "currency": self.currency?.rawValue + ] + + return try Node(node: object) + } +} + +public final class InvoiceItem: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amount: Int + public private(set) var description: String? + public private(set) var isDiscountable: Bool + public private(set) var isLiveMode: Bool + public private(set) var periodStart: Date? + public private(set) var periodEnd: Date? + public private(set) var isProration: Bool + public private(set) var quantity: Int? + public private(set) var subscription: String? + public private(set) var subscriptionItem: String? + + public private(set) var plan: Plan? + + public private(set) var metadata: Node? + + public private(set) var currency: StripeCurrency? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amount = try node.get("amount") + self.description = try node.get("description") + self.isDiscountable = try node.get("discountable") + self.isLiveMode = try node.get("livemode") + self.isProration = try node.get("proration") + self.quantity = try node.get("quantity") + self.subscription = try node.get("subscription") + self.subscriptionItem = try node.get("subscription_item") + + self.plan = try node.get("plan") + + self.metadata = try node.get("metadata") + + if let period = node["period"]?.object { + self.periodStart = period["start"]?.date + self.periodEnd = period["end"]?.date + } + + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount": self.amount, + "description": self.description, + "discountable": self.isDiscountable, + "livemode": self.isLiveMode, + "proration": self.isProration, + "quantity": self.quantity, + "subscription": self.subscription, + "subscription_item": self.subscriptionItem, + "plan": self.plan, + "metadata": self.metadata, + "period": [ + "start": self.periodStart, + "end": self.periodEnd + ], + "currency": self.currency?.rawValue + ] + + return try Node(node: object) + } + +} From b4ba004aebc91ff06abd3b9166dc0c349891a843 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Mon, 4 Sep 2017 19:08:50 -0700 Subject: [PATCH 02/31] invoices: Add endpoints --- Sources/Stripe/API/Helpers/Endpoints.swift | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Sources/Stripe/API/Helpers/Endpoints.swift b/Sources/Stripe/API/Helpers/Endpoints.swift index 57e430f..3dabba7 100644 --- a/Sources/Stripe/API/Helpers/Endpoints.swift +++ b/Sources/Stripe/API/Helpers/Endpoints.swift @@ -94,21 +94,25 @@ internal enum API { /** SOURCES - Source objects allow you to accept a variety of payment methods. They represent a customer's payment instrument and can be used with the Stripe API just like a card object: once chargeable, they can be charged, or attached to customers. + Source objects allow you to accept a variety of payment methods. They represent a customer's payment instrument + and can be used with the Stripe API just like a card object: once chargeable, they can be charged, or attached + to customers. */ case sources case source(String) /** SUBSCRIPTION ITEMS - Subscription items allow you to create customer subscriptions with more than one plan, making it easy to represent complex billing relationships. + Subscription items allow you to create customer subscriptions with more than one plan, making it easy to represent + complex billing relationships. */ case subscriptionItem case subscriptionItems(String) /** SUBSCRIPTIONS - Subscriptions allow you to charge a customer's card on a recurring basis. A subscription ties a customer to a particular plan you've created. + Subscriptions allow you to charge a customer's card on a recurring basis. A subscription ties a customer to a + particular plan you've created. */ case subscription case subscriptions(String) @@ -116,7 +120,8 @@ internal enum API { /** ACCOUNTS - This is an object representing your Stripe account. You can retrieve it to see properties on the account like its current e-mail address or if the account is enabled yet to make live charges. + This is an object representing your Stripe account. You can retrieve it to see properties on the account like its + current e-mail address or if the account is enabled yet to make live charges. */ case account case accounts(String) @@ -161,6 +166,17 @@ internal enum API { case orderReturn case orderReturns(String) + /** + INVOICES + Invoices are statements of what a customer owes for a particular billing period, including subscriptions, + invoice items, and any automatic proration adjustments if necessary. + */ + case invoices + case invoice(String) + case payInvoice(String) + case invoiceLines(String) + case upcomingInvoices + var endpoint: String { switch self { case .balance: return APIBase + APIVersion + "balance" @@ -220,6 +236,12 @@ internal enum API { case .orderReturn: return APIBase + APIVersion + "order_returns" case .orderReturns(let id): return APIBase + APIVersion + "order_returns/\(id)" + + case .invoices: return APIBase + APIVersion + "invoices" + case .invoice(let id): return APIBase + APIVersion + "invoices/\(id)" + case .payInvoice(let id): return APIBase + APIVersion + "invoices/\(id)/pay" + case .invoiceLines(let id): return APIBase + APIVersion + "invoices/\(id)/lines" + case .upcomingInvoices: return APIBase + APIVersion + "invoices/upcoming" } } } From 5c87cc004a8a8dea6218a849021ccab5d0877be5 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Mon, 4 Sep 2017 19:28:19 -0700 Subject: [PATCH 03/31] invoice: Add the create invoice route --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 83 +++++++++++++++++++ Sources/Stripe/API/StripeClient.swift | 2 + 2 files changed, 85 insertions(+) create mode 100644 Sources/Stripe/API/Routes/InvoiceRoutes.swift diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift new file mode 100644 index 0000000..b80a441 --- /dev/null +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -0,0 +1,83 @@ +// +// InvoiceRoutes.swift +// Stripe +// +// Created by Anthony Castelli on 9/4/17. +// +// + +import Foundation +import Node +import HTTP + +public final class InvoiceRoutes { + + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + /** + If you need to invoice your customer outside the regular billing cycle, you can create an invoice that pulls in all + pending invoice items, including prorations. The customer’s billing cycle and regular subscription won’t be affected. + + Once you create the invoice, Stripe will attempt to collect payment according to your subscriptions settings, + though you can choose to pay it right away. + + - parameter customer: The ID of the customer to attach the invoice to + - parameter subscription: The ID of the subscription to invoice. If not set, the created invoice will include all + pending invoice items for the customer. If set, the created invoice will exclude pending + invoice items that pertain to other subscriptions. + - parameter fee: A fee to charge if you are using connected accounts (Must be in cents) + - parameter account: The account to transfer the fee to + - parameter description: A description for the invoice + - parameter metadata: Aditional metadata info + - parameter taxPercent: The percent tax rate applied to the invoice, represented as a decimal number. + - parameter statementDescriptor: Extra information about a charge for the customer’s credit card statement. + */ + + public func create(forCustomer customer: String, subscription: String? = nil, withFee fee: Int? = nil, toAccount account: String? = nil, description: String? = nil, metadata: Node? = nil, taxPercent: Double? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { + var body = Node([:]) + // Create the headers + var headers: [HeaderKey : String]? + if let account = account { + headers = [ + StripeHeader.Account: account + ] + + if let fee = fee { + body["application_fee"] = Node(fee) + } + } + + body["customer"] = Node(customer) + + if let subscription = subscription { + body["subscription"] = Node(subscription) + } + + if let description = description { + body["description"] = Node(description) + } + + if let taxPercent = taxPercent { + body["description"] = Node(taxPercent) + } + + if let statementDescriptor = statementDescriptor { + body["statement_descriptor"] = Node(statementDescriptor) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: headers) + } + + + +} diff --git a/Sources/Stripe/API/StripeClient.swift b/Sources/Stripe/API/StripeClient.swift index b2dface..bf2cc69 100644 --- a/Sources/Stripe/API/StripeClient.swift +++ b/Sources/Stripe/API/StripeClient.swift @@ -28,6 +28,7 @@ public class StripeClient { public private(set) var products: ProductRoutes! public private(set) var orders: OrderRoutes! public private(set) var orderReturns: OrderReturnRoutes! + public private(set) var invoices: InvoiceRoutes! public init(apiKey: String) throws { self.apiKey = apiKey @@ -50,5 +51,6 @@ public class StripeClient { self.products = ProductRoutes(client: self) self.orders = OrderRoutes(client: self) self.orderReturns = OrderReturnRoutes(client: self) + self.invoices = InvoiceRoutes(client: self) } } From 6a33815ca9f4f1261be4f0d351479f0c71631140 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Mon, 4 Sep 2017 20:32:59 -0700 Subject: [PATCH 04/31] invoices: Add fetching an invoice --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index b80a441..dbd037a 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -35,6 +35,8 @@ public final class InvoiceRoutes { - parameter metadata: Aditional metadata info - parameter taxPercent: The percent tax rate applied to the invoice, represented as a decimal number. - parameter statementDescriptor: Extra information about a charge for the customer’s credit card statement. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ public func create(forCustomer customer: String, subscription: String? = nil, withFee fee: Int? = nil, toAccount account: String? = nil, description: String? = nil, metadata: Node? = nil, taxPercent: Double? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { @@ -75,9 +77,18 @@ public final class InvoiceRoutes { } } - return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: headers) + return try StripeRequest(client: self.client, method: .post, route: .invoices, body: Body.data(body.formURLEncoded()), headers: headers) } - + /** + Retrieves the invoice with the given ID. + + - parameter invoice: The Invoice ID to fetch + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func fetch(invoice invoiceId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: nil, headers: nil) + } } From ea7ed145913b7f5a24cbd91696bbdc3e92ec5f7f Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:19:27 -0700 Subject: [PATCH 05/31] invoice: Add list items route --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 34 ++++++++ Sources/Stripe/Models/Invoices/Invoice.swift | 72 ---------------- .../Stripe/Models/Invoices/InvoiceItem.swift | 83 +++++++++++++++++++ .../Models/Invoices/InvoiceLineGroup.swift | 33 ++++++++ 4 files changed, 150 insertions(+), 72 deletions(-) create mode 100644 Sources/Stripe/Models/Invoices/InvoiceItem.swift create mode 100644 Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index dbd037a..1641842 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -91,4 +91,38 @@ public final class InvoiceRoutes { return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: nil, headers: nil) } + /** + When retrieving an invoice, you’ll get a lines property containing the total count of line items and the first handful + of those items. There is also a URL where you can retrieve the full (paginated) list of line items. + + - parameter invoiceId: The Invoice ID to fetch + - parameter customer: In the case of upcoming invoices, the customer of the upcoming invoice is required. In other + cases it is ignored. + - parameter coupon: For upcoming invoices, preview applying this coupon to the invoice. If a subscription or + subscription_items is provided, the invoice returned will preview updating or creating a + subscription with that coupon. Otherwise, it will preview applying that coupon to the customer + for the next upcoming invoice from among the customer’s subscriptions. Otherwise this parameter + is ignored. This will be unset if you POST an empty value. + - parameter filter: Parameters used to filter the results + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + + public func listItems(forInvoice invoiceId: String, customer: String? = nil, coupon: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let data = try filter?.createQuery() { + query = data + } + + if let customer = customer { + query["customer"] = customer + } + + if let coupon = coupon { + query["coupon"] = coupon + } + + return try StripeRequest(client: self.client, method: .get, route: .invoiceLines(invoiceId), query: query, body: nil, headers: nil) + } + } diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift index 4013f22..16ba118 100644 --- a/Sources/Stripe/Models/Invoices/Invoice.swift +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -138,75 +138,3 @@ public final class Invoice: StripeModelProtocol { } } -public final class InvoiceItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int - public private(set) var description: String? - public private(set) var isDiscountable: Bool - public private(set) var isLiveMode: Bool - public private(set) var periodStart: Date? - public private(set) var periodEnd: Date? - public private(set) var isProration: Bool - public private(set) var quantity: Int? - public private(set) var subscription: String? - public private(set) var subscriptionItem: String? - - public private(set) var plan: Plan? - - public private(set) var metadata: Node? - - public private(set) var currency: StripeCurrency? - - public init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.description = try node.get("description") - self.isDiscountable = try node.get("discountable") - self.isLiveMode = try node.get("livemode") - self.isProration = try node.get("proration") - self.quantity = try node.get("quantity") - self.subscription = try node.get("subscription") - self.subscriptionItem = try node.get("subscription_item") - - self.plan = try node.get("plan") - - self.metadata = try node.get("metadata") - - if let period = node["period"]?.object { - self.periodStart = period["start"]?.date - self.periodEnd = period["end"]?.date - } - - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "description": self.description, - "discountable": self.isDiscountable, - "livemode": self.isLiveMode, - "proration": self.isProration, - "quantity": self.quantity, - "subscription": self.subscription, - "subscription_item": self.subscriptionItem, - "plan": self.plan, - "metadata": self.metadata, - "period": [ - "start": self.periodStart, - "end": self.periodEnd - ], - "currency": self.currency?.rawValue - ] - - return try Node(node: object) - } - -} diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceItem.swift new file mode 100644 index 0000000..7edb509 --- /dev/null +++ b/Sources/Stripe/Models/Invoices/InvoiceItem.swift @@ -0,0 +1,83 @@ +// +// InvoiceItem.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import Foundation +import Vapor + +public final class InvoiceItem: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amount: Int + public private(set) var description: String? + public private(set) var isDiscountable: Bool + public private(set) var isLiveMode: Bool + public private(set) var periodStart: Date? + public private(set) var periodEnd: Date? + public private(set) var isProration: Bool + public private(set) var quantity: Int? + public private(set) var subscription: String? + public private(set) var subscriptionItem: String? + + public private(set) var plan: Plan? + + public private(set) var metadata: Node? + + public private(set) var currency: StripeCurrency? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amount = try node.get("amount") + self.description = try node.get("description") + self.isDiscountable = try node.get("discountable") + self.isLiveMode = try node.get("livemode") + self.isProration = try node.get("proration") + self.quantity = try node.get("quantity") + self.subscription = try node.get("subscription") + self.subscriptionItem = try node.get("subscription_item") + + self.plan = try node.get("plan") + + self.metadata = try node.get("metadata") + + if let period = node["period"]?.object { + self.periodStart = period["start"]?.date + self.periodEnd = period["end"]?.date + } + + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount": self.amount, + "description": self.description, + "discountable": self.isDiscountable, + "livemode": self.isLiveMode, + "proration": self.isProration, + "quantity": self.quantity, + "subscription": self.subscription, + "subscription_item": self.subscriptionItem, + "plan": self.plan, + "metadata": self.metadata, + "period": [ + "start": self.periodStart, + "end": self.periodEnd + ], + "currency": self.currency?.rawValue + ] + + return try Node(node: object) + } + +} diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift new file mode 100644 index 0000000..27af6ef --- /dev/null +++ b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift @@ -0,0 +1,33 @@ +// +// InvoiceLineGroup.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import Foundation +import Vapor + +public final class InvoiceLineGroup: StripeModelProtocol { + + public private(set) var object: String? + public private(set) var hasMore: Bool? + public private(set) var items: [InvoiceItem]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } + +} From f57ef03614e2653a9f56d029e20b3f29433d1479 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:24:01 -0700 Subject: [PATCH 06/31] invoice: Add upcoming invoice for customer --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 1641842..907d7ee 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -125,4 +125,40 @@ public final class InvoiceRoutes { return try StripeRequest(client: self.client, method: .get, route: .invoiceLines(invoiceId), query: query, body: nil, headers: nil) } + /** + At any time, you can preview the upcoming invoice for a customer. This will show you all the charges that are pending, + including subscription renewal charges, invoice item charges, etc. It will also show you any discount that is applicable + to the customer. + + - parameter customerId: The identifier of the customer whose upcoming invoice you’d like to retrieve. + - parameter coupon: The code of the coupon to apply. If subscription or subscription_items is provided, the invoice + returned will preview updating or creating a subscription with that coupon. Otherwise, it will + preview applying that coupon to the customer for the next upcoming invoice from among the customer’s + subscriptions. The invoice can be previewed without a coupon by passing this value as an empty string. + - parameter subscription: The identifier of the subscription for which you’d like to retrieve the upcoming invoice. If not provided, + but a subscription_items is provided, you will preview creating a subscription with those items. If neither + subscription nor subscription_items is provided, you will retrieve the next upcoming invoice from among the + customer’s subscriptions. + - parameter filter: Parameters used to filter the result + */ + public func upcomingInvoice(forCustomer customerId: String, coupon: String? = nil, subscription: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + query["customer"] = customerId + + if let data = try filter?.createQuery() { + query = data + } + + if let coupon = coupon { + query["coupon"] = coupon + } + + if let subscription = subscription { + query["subscription"] = subscription + } + + return try StripeRequest(client: self.client, method: .get, route: .upcomingInvoices, query: query, body: nil, headers: nil) + } + + } From b475e0898dd8ababfaed3bb951e8b0875765865d Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:32:58 -0700 Subject: [PATCH 07/31] invoice: Fix a typo with creating an invoice --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 907d7ee..61b0e2a 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -64,7 +64,7 @@ public final class InvoiceRoutes { } if let taxPercent = taxPercent { - body["description"] = Node(taxPercent) + body["tax_percent"] = Node(taxPercent) } if let statementDescriptor = statementDescriptor { From 8c89059ac71a71fac700c3eb154ad7e1ec01ca1d Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:33:10 -0700 Subject: [PATCH 08/31] invoice: Add invoice updating route --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 61b0e2a..4f53ed8 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -140,6 +140,8 @@ public final class InvoiceRoutes { subscription nor subscription_items is provided, you will retrieve the next upcoming invoice from among the customer’s subscriptions. - parameter filter: Parameters used to filter the result + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ public func upcomingInvoice(forCustomer customerId: String, coupon: String? = nil, subscription: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() @@ -160,5 +162,67 @@ public final class InvoiceRoutes { return try StripeRequest(client: self.client, method: .get, route: .upcomingInvoices, query: query, body: nil, headers: nil) } - + /** + Until an invoice is paid, it is marked as open (closed=false). If you’d like to stop Stripe from attempting to collect payment on an + invoice or would simply like to close the invoice out as no longer owed by the customer, you can update the closed parameter. + + - parameter invoiceId: The ID of the Invoice to update + - parameter closed: Boolean representing whether an invoice is closed or not. To close an invoice, pass true. + - parameter forgiven: Boolean representing whether an invoice is forgiven or not. To forgive an invoice, pass true. + Forgiving an invoice instructs us to update the subscription status as if the invoice were successfully paid. + Once an invoice has been forgiven, it cannot be unforgiven or reopened. + - parameter applicationFee: A fee in cents that will be applied to the invoice and transferred to the application owner’s Stripe account. + The request must be made with an OAuth key or the Stripe-Account header in order to take an application fee. + For more information, see the application fees documentation. + - parameter account: The account to transfer the fee to + - parameter description: A description for the invoice + - parameter metadata: Aditional metadata info + - parameter taxPercent: The percent tax rate applied to the invoice, represented as a decimal number. The tax rate of an attempted, + paid or forgiven invoice cannot be changed. + - parameter statementDescriptor: Extra information about a charge for the customer’s credit card statement. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func update(invoice invoiceId: String, closed: Bool? = nil, forgiven: Bool? = nil, applicationFee: Int? = nil, toAccount account: String? = nil, description: String? = nil, metadata: Node? = nil, taxPercent: Double? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { + var body = Node([:]) + // Create the headers + var headers: [HeaderKey : String]? + if let account = account { + headers = [ + StripeHeader.Account: account + ] + + if let fee = applicationFee { + body["application_fee"] = Node(fee) + } + } + + if let closed = closed { + body["closed"] = Node(closed) + } + + if let forgiven = forgiven { + body["forgiven"] = Node(forgiven) + } + + if let description = description { + body["description"] = Node(description) + } + + if let taxPercent = taxPercent { + body["tax_percent"] = Node(taxPercent) + } + + if let statementDescriptor = statementDescriptor { + body["statement_descriptor"] = Node(statementDescriptor) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: headers) + } } From 6610e457969af54846eea6d60f8bdabc70003e51 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:45:30 -0700 Subject: [PATCH 09/31] invoice: Add pay route --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 4f53ed8..0ead174 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -225,4 +225,25 @@ public final class InvoiceRoutes { return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: headers) } + + /** + Stripe automatically creates and then attempts to collect payment on invoices for customers on subscriptions according to your + subscriptions settings. However, if you’d like to attempt payment on an invoice out of the normal collection schedule or for some + other reason, you can do so. + + - parameter invoiceId: The ID of the invoice to pay. + - parameter source: A payment source to be charged. The source must be the ID of a source belonging to the customer associated + with the invoice being paid. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func pay(invoice invoiceId: String, source: String? = nil) throws -> StripeRequest { + var body = Node([:]) + + if let source = source { + body["source"] = Node(source) + } + + return try StripeRequest(client: self.client, method: .post, route: .payInvoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: nil) + } } From 0ac783019eec7e81a5e64f44d71444cd4ab24263 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:48:32 -0700 Subject: [PATCH 10/31] invoice: Add listing all invoices --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 20 +++++++++++++++++ Sources/Stripe/Models/Invoices/Invoice.swift | 22 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 0ead174..634b343 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -246,4 +246,24 @@ public final class InvoiceRoutes { return try StripeRequest(client: self.client, method: .post, route: .payInvoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: nil) } + + /** + List all Invoices + You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, + with the most recently created invoices appearing first. + + - parameter filter: A Filter item to pass query parameters when fetching results + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func listAll(customer: String? = nil, filter: StripeFilter?) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let customer = customer { + query["customer"] = customer + } + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .invoices, query: query, body: nil, headers: nil) + } } diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift index 16ba118..1648e4c 100644 --- a/Sources/Stripe/Models/Invoices/Invoice.swift +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -138,3 +138,25 @@ public final class Invoice: StripeModelProtocol { } } +public final class InvoiceList: StripeModelProtocol { + + public private(set) var object: String? + public private(set) var hasMore: Bool? + public private(set) var items: [Invoice]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } + +} From c8b2b8a562554636ca9d67164aceac2cd56d18e9 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 10:49:27 -0700 Subject: [PATCH 11/31] invoice: Add titles for documentation --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 634b343..ae2c98a 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -19,6 +19,7 @@ public final class InvoiceRoutes { } /** + Create invoice If you need to invoice your customer outside the regular billing cycle, you can create an invoice that pulls in all pending invoice items, including prorations. The customer’s billing cycle and regular subscription won’t be affected. @@ -81,6 +82,7 @@ public final class InvoiceRoutes { } /** + Fetch an invoice Retrieves the invoice with the given ID. - parameter invoice: The Invoice ID to fetch @@ -92,6 +94,7 @@ public final class InvoiceRoutes { } /** + List items for invoice When retrieving an invoice, you’ll get a lines property containing the total count of line items and the first handful of those items. There is also a URL where you can retrieve the full (paginated) list of line items. @@ -126,6 +129,7 @@ public final class InvoiceRoutes { } /** + List Upcoming invoice for Customer At any time, you can preview the upcoming invoice for a customer. This will show you all the charges that are pending, including subscription renewal charges, invoice item charges, etc. It will also show you any discount that is applicable to the customer. @@ -163,6 +167,7 @@ public final class InvoiceRoutes { } /** + Update Invoice Until an invoice is paid, it is marked as open (closed=false). If you’d like to stop Stripe from attempting to collect payment on an invoice or would simply like to close the invoice out as no longer owed by the customer, you can update the closed parameter. @@ -227,6 +232,7 @@ public final class InvoiceRoutes { } /** + Pay Invoice Stripe automatically creates and then attempts to collect payment on invoices for customers on subscriptions according to your subscriptions settings. However, if you’d like to attempt payment on an invoice out of the normal collection schedule or for some other reason, you can do so. From b548e2c13e8c474e4dbf0dd6cd924cb627ecdced Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 16:39:39 -0700 Subject: [PATCH 12/31] invoice: Rename InvoiceItem to InvoiceLineItem to better repsresent what it is --- Sources/Stripe/Models/Invoices/Invoice.swift | 4 ++-- Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift | 2 +- .../Invoices/{InvoiceItem.swift => InvoiceLineItem.swift} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename Sources/Stripe/Models/Invoices/{InvoiceItem.swift => InvoiceLineItem.swift} (96%) diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift index 1648e4c..8e199d4 100644 --- a/Sources/Stripe/Models/Invoices/Invoice.swift +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -52,7 +52,7 @@ public final class Invoice: StripeModelProtocol { public private(set) var metadata: Node? - public private(set) var lines: [InvoiceItem]? + public private(set) var lines: [InvoiceLineItem]? public private(set) var currency: StripeCurrency? public init(node: Node) throws { @@ -93,7 +93,7 @@ public final class Invoice: StripeModelProtocol { } if let lines = node["lines"]?.object, let data = lines["data"]?.array { - self.lines = try data.map({ try InvoiceItem(node: $0) }) + self.lines = try data.map({ try InvoiceLineItem(node: $0) }) } } diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift index 27af6ef..20f44a5 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift @@ -13,7 +13,7 @@ public final class InvoiceLineGroup: StripeModelProtocol { public private(set) var object: String? public private(set) var hasMore: Bool? - public private(set) var items: [InvoiceItem]? + public private(set) var items: [InvoiceLineItem]? public init(node: Node) throws { self.object = try node.get("object") diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift similarity index 96% rename from Sources/Stripe/Models/Invoices/InvoiceItem.swift rename to Sources/Stripe/Models/Invoices/InvoiceLineItem.swift index 7edb509..af7610d 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift @@ -1,5 +1,5 @@ // -// InvoiceItem.swift +// InvoiceLineItem.swift // Stripe // // Created by Anthony Castelli on 9/5/17. @@ -9,7 +9,7 @@ import Foundation import Vapor -public final class InvoiceItem: StripeModelProtocol { +public final class InvoiceLineItem: StripeModelProtocol { public private(set) var id: String? public private(set) var object: String? From 63ba5a0920f720fc45fa28e7cdede71254889760 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 17:56:42 -0700 Subject: [PATCH 13/31] invoices: Add support for invoice items --- .../Stripe/Models/Invoices/InvoiceItem.swift | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Sources/Stripe/Models/Invoices/InvoiceItem.swift diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceItem.swift new file mode 100644 index 0000000..2027fe6 --- /dev/null +++ b/Sources/Stripe/Models/Invoices/InvoiceItem.swift @@ -0,0 +1,114 @@ +// +// InvoiceItem.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import Foundation +import Vapor + +/** + Invoice Items + https://stripe.com/docs/api#invoiceitems + */ +public final class InvoiceItem: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amount: Int + public private(set) var customer: String? + public private(set) var date: Date? + public private(set) var description: String? + public private(set) var isDiscountable: Bool + public private(set) var isLiveMode: Bool + public private(set) var isProration: Bool + public private(set) var periodStart: Date? + public private(set) var periodEnd: Date? + public private(set) var quantity: Int? + public private(set) var subscription: String? + + public private(set) var metadata: Node? + + public private(set) var plan: Plan? + public private(set) var currency: StripeCurrency? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amount = try node.get("amount") + self.customer = try node.get("customer") + self.date = try node.get("date") + self.description = try node.get("description") + self.isDiscountable = try node.get("discountable") + self.isLiveMode = try node.get("livemode") + self.isProration = try node.get("proration") + self.quantity = try node.get("qantity") + self.subscription = try node.get("subscription") + + if let period = node["period"]?.object { + self.periodStart = period["start"]?.date + self.periodEnd = period["end"]?.date + } + + self.metadata = try node.get("metadata") + + self.plan = try? node.get("plan") + + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount": self.amount, + "customer": self.customer, + "date": self.date, + "description": self.description, + "discountable": self.isDiscountable, + "livemode": self.isLiveMode, + "proration": self.isProration, + "quantity": self.quantity, + "subscription": self.subscription, + + "period": [ + "start": self.periodStart, + "end": self.periodEnd + ], + + "metadata": self.metadata, + + "plan": self.plan, + "currency": self.currency?.rawValue + ] + + return try Node(node: object) + } +} + +public final class InvoiceItemList: StripeModelProtocol { + + public private(set) var object: String? + public private(set) var hasMore: Bool? + public private(set) var items: [Invoice]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } + +} From 6cf37dcd7f7b312d858924305e3b1ce49d6132c0 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 17:58:39 -0700 Subject: [PATCH 14/31] invoices: Add endpoints for invoice items --- Sources/Stripe/API/Helpers/Endpoints.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/Stripe/API/Helpers/Endpoints.swift b/Sources/Stripe/API/Helpers/Endpoints.swift index 3dabba7..e9938f8 100644 --- a/Sources/Stripe/API/Helpers/Endpoints.swift +++ b/Sources/Stripe/API/Helpers/Endpoints.swift @@ -177,6 +177,16 @@ internal enum API { case invoiceLines(String) case upcomingInvoices + /** + INVOICE ITEMS + Sometimes you want to add a charge or credit to a customer but only actually charge the customer's card at + the end of a regular billing cycle. This is useful for combining several charges to minimize per-transaction + fees or having Stripe tabulate your usage-based billing totals. + */ + case invoiceItems + case invoiceItem(String) + + var endpoint: String { switch self { case .balance: return APIBase + APIVersion + "balance" @@ -242,6 +252,9 @@ internal enum API { case .payInvoice(let id): return APIBase + APIVersion + "invoices/\(id)/pay" case .invoiceLines(let id): return APIBase + APIVersion + "invoices/\(id)/lines" case .upcomingInvoices: return APIBase + APIVersion + "invoices/upcoming" + + case .invoiceItems:return APIBase + APIVersion + "invoiceitems" + case .invoiceItem(let id): return APIBase + APIVersion + "invoiceitems/\(id)" } } } From 78f71b1a311ad8e25bc5046b615b0182406d2267 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 18:08:22 -0700 Subject: [PATCH 15/31] invoices: Add create route for invoice items --- .../Stripe/API/Routes/InvoiceItemRoutes.swift | 81 +++++++++++++++++++ Sources/Stripe/API/StripeClient.swift | 2 + 2 files changed, 83 insertions(+) create mode 100644 Sources/Stripe/API/Routes/InvoiceItemRoutes.swift diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift new file mode 100644 index 0000000..bfb7936 --- /dev/null +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -0,0 +1,81 @@ +// +// InvoiceItemRoutes.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import Foundation +import Node +import HTTP + +public final class InvoiceItemRoutes { + + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + /** + Create an invoice item + Adds an arbitrary charge or credit to the customer’s upcoming invoice. + + - parameter customer: The ID of the customer who will be billed when this invoice item is billed. + - parameter amount: The integer amount in cents of the charge to be applied to the upcoming invoice. + To apply a credit to the customer’s account, pass a negative amount. + - parameter currency: Three-letter ISO currency code, in lowercase. Must be a supported currency. + - parameter invoice: The ID of an existing invoice to add this invoice item to. When left blank, the + invoice item will be added to the next upcoming scheduled invoice. Use this when adding + invoice items in response to an invoice.created webhook. You cannot add an invoice item + to an invoice that has already been paid, attempted or closed. + - parameter subscription: The ID of a subscription to add this invoice item to. When left blank, the invoice item + will be be added to the next upcoming scheduled invoice. When set, scheduled invoices for + subscriptions other than the specified subscription will ignore the invoice item. Use this + when you want to express that an invoice item has been accrued within the context of a + particular subscription. + - parameter description: An arbitrary string which you can attach to the invoice item. The description is displayed + in the invoice for easy tracking. This will be unset if you POST an empty value. + - parameter discountable: Controls whether discounts apply to this invoice item. Defaults to false for prorations or + negative invoice items, and true for all other invoice items. + - parameter metadata: A set of key/value pairs that you can attach to an invoice item object. It can be useful for + storing additional information about the invoice item in a structured format. You can unset + individual keys if you POST an empty value for that key. You can clear all keys if you POST + an empty value for metadata. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func createItem(forCustomer customer: String, amount: Int, inCurrency currency: StripeCurrency, toInvoice invoice: String? = nil, toSubscription subscription: String? = nil, description: String? = nil, discountable: Bool? = nil, metadata: Node? = nil) throws -> StripeRequest { + var body = Node([:]) + + body["customer"] = Node(customer) + body["amount"] = Node(amount) + body["currency"] = Node(currency.rawValue) + + if let subscription = subscription { + body["subscription"] = Node(subscription) + } + + if let invoice = invoice { + body["invoice"] = Node(invoice) + } + + if let description = description { + body["description"] = Node(description) + } + + if let discountable = discountable { + body["discountable"] = Node(discountable) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .invoiceItems, body: Body.data(body.formURLEncoded()), headers: nil) + } + +} diff --git a/Sources/Stripe/API/StripeClient.swift b/Sources/Stripe/API/StripeClient.swift index bf2cc69..383e180 100644 --- a/Sources/Stripe/API/StripeClient.swift +++ b/Sources/Stripe/API/StripeClient.swift @@ -29,6 +29,7 @@ public class StripeClient { public private(set) var orders: OrderRoutes! public private(set) var orderReturns: OrderReturnRoutes! public private(set) var invoices: InvoiceRoutes! + public private(set) var invoiceItems: InvoiceItemRoutes! public init(apiKey: String) throws { self.apiKey = apiKey @@ -52,5 +53,6 @@ public class StripeClient { self.orders = OrderRoutes(client: self) self.orderReturns = OrderReturnRoutes(client: self) self.invoices = InvoiceRoutes(client: self) + self.invoiceItems = InvoiceItemRoutes(client: self) } } From 8e713dd3585a86016c13f63d139ee1eac8cf12f0 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 18:09:17 -0700 Subject: [PATCH 16/31] invoices: Add invoice item fetch route --- Sources/Stripe/API/Routes/InvoiceItemRoutes.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index bfb7936..59813a9 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -78,4 +78,16 @@ public final class InvoiceItemRoutes { return try StripeRequest(client: self.client, method: .post, route: .invoiceItems, body: Body.data(body.formURLEncoded()), headers: nil) } + /** + Fetch an invoice + Retrieves the invoice with the given ID. + + - parameter invoice: The ID of the desired invoice item. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func fetch(invoiceItem invoiceItemId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) + } + } From 6cb1ae9f43058fba9d30277ba8bb953c94aaa4de Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 18:11:38 -0700 Subject: [PATCH 17/31] invoices: Add update route --- .../Stripe/API/Routes/InvoiceItemRoutes.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index 59813a9..801dc6c 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -90,4 +90,44 @@ public final class InvoiceItemRoutes { return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) } + /** + Update an invoice item + Updates the amount or description of an invoice item on an upcoming invoice. Updating an invoice item + is only possible before the invoice it’s attached to is closed. + + - parameter amount: The integer amount in cents of the charge to be applied to the upcoming invoice. + To apply a credit to the customer’s account, pass a negative amount. + - parameter description: An arbitrary string which you can attach to the invoice item. The description is displayed + in the invoice for easy tracking. This will be unset if you POST an empty value. + - parameter discountable: Controls whether discounts apply to this invoice item. Defaults to false for prorations or + negative invoice items, and true for all other invoice items. + - parameter metadata: A set of key/value pairs that you can attach to an invoice item object. It can be useful for + storing additional information about the invoice item in a structured format. You can unset + individual keys if you POST an empty value for that key. You can clear all keys if you POST + an empty value for metadata. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func update(invoiceItem invoiceItemId: String, amount: Int, description: String? = nil, discountable: Bool? = nil, metadata: Node? = nil) throws -> StripeRequest { + var body = Node([:]) + + body["amount"] = Node(amount) + + if let description = description { + body["description"] = Node(description) + } + + if let discountable = discountable { + body["discountable"] = Node(discountable) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: Body.data(body.formURLEncoded()), headers: nil) + } + } From fed53f5155b977363654b66a9a31e1daafba063c Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 18:13:00 -0700 Subject: [PATCH 18/31] invoices: Add invoice item delete route --- Sources/Stripe/API/Routes/InvoiceItemRoutes.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index 801dc6c..d99a7ea 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -130,4 +130,17 @@ public final class InvoiceItemRoutes { return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: Body.data(body.formURLEncoded()), headers: nil) } + /** + Delete an invoice item + Removes an invoice item from the upcoming invoice. Removing an invoice item is only possible before the + invoice it’s attached to is closed. + + - parameter invoice: The ID of the desired invoice item. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func delete(invoiceItem invoiceItemId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .delete, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) + } + } From ba917a3caed66b14386467ef8d5adceaf90427f5 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 18:14:11 -0700 Subject: [PATCH 19/31] invoices: Add fetch all route --- .../Stripe/API/Routes/InvoiceItemRoutes.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index d99a7ea..6a2c4cb 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -143,4 +143,24 @@ public final class InvoiceItemRoutes { return try StripeRequest(client: self.client, method: .delete, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) } + /** + List all invoice items + Returns a list of your invoice items. Invoice items are returned sorted by creation date, with the most + recently created invoice items appearing first. + + - parameter filter: A Filter item to pass query parameters when fetching results + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + public func listAll(customer: String? = nil, filter: StripeFilter?) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let customer = customer { + query["customer"] = customer + } + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .invoiceItems, query: query, body: nil, headers: nil) + } + } From 130ea8293697ce8ba7f642ab09e217b383480ba3 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Tue, 5 Sep 2017 21:19:35 -0700 Subject: [PATCH 20/31] test: Start work in invoice tests --- Tests/StripeTests/InvoiceTests.swift | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Tests/StripeTests/InvoiceTests.swift diff --git a/Tests/StripeTests/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift new file mode 100644 index 0000000..e232ecd --- /dev/null +++ b/Tests/StripeTests/InvoiceTests.swift @@ -0,0 +1,61 @@ +// +// InvoiceTests.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor + +class InvoiceTests: XCTestCase { + + var drop: Droplet? + var invoiceId: String = "" + + override func setUp() { + super.setUp() + do { + self.drop = try self.makeDroplet() + self.invoiceId = try drop?.stripe?.invoices.create(forCustomer: "").serializedResponse().id ?? "" + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + self.drop = nil + self.invoiceId = "" + super.tearDown() + } + + func testExample() { + + } + +} From 6ab05df3e1c45232038ced943aebdd7b124b0f3b Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 10:53:58 -0700 Subject: [PATCH 21/31] request: Whiteline fix --- Sources/Stripe/API/StripeRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Stripe/API/StripeRequest.swift b/Sources/Stripe/API/StripeRequest.swift index 8e8975f..0a82cbc 100644 --- a/Sources/Stripe/API/StripeRequest.swift +++ b/Sources/Stripe/API/StripeRequest.swift @@ -34,7 +34,7 @@ public class StripeRequest { allHeaders[$0.key] = $0.value } } - + switch method { case .get: self.response = try self.httpClient.get(route.endpoint, query: query, allHeaders, body, through: []) case .post: self.response = try self.httpClient.post(route.endpoint, query: query, allHeaders, body, through: []) From 9352392d1572f27b92b3682d35e549325672f94b Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 10:54:11 -0700 Subject: [PATCH 22/31] invoice: Fix up a route declaration --- Sources/Stripe/API/Routes/InvoiceRoutes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index ae2c98a..bb590a9 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -262,7 +262,7 @@ public final class InvoiceRoutes { - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func listAll(customer: String? = nil, filter: StripeFilter?) throws -> StripeRequest { + public func listAll(customer: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let customer = customer { query["customer"] = customer From af4b7569fe995eb1c4653e31fab572e7c7b4ac83 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 10:54:18 -0700 Subject: [PATCH 23/31] test: Implement invoice tests --- Tests/StripeTests/InvoiceTests.swift | 259 ++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 6 deletions(-) diff --git a/Tests/StripeTests/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift index e232ecd..addd487 100644 --- a/Tests/StripeTests/InvoiceTests.swift +++ b/Tests/StripeTests/InvoiceTests.swift @@ -10,17 +10,77 @@ import XCTest @testable import Stripe @testable import Vapor +@testable import Random class InvoiceTests: XCTestCase { var drop: Droplet? - var invoiceId: String = "" + var customerId = "" + var subscriptionId = "" + var invoiceId = "" + var invoiceItemId = "" override func setUp() { super.setUp() do { self.drop = try self.makeDroplet() - self.invoiceId = try drop?.stripe?.invoices.create(forCustomer: "").serializedResponse().id ?? "" + + let planId = try self.drop?.stripe?.plans.create( + id: Data(bytes: URandom.bytes(count: 16)).base64String, + amount: 10_00, + currency: .usd, + interval: .week, + name: "Test Plan", + intervalCount: 5, + statementDescriptor: "Test Plan", + trialPeriodDays: nil, + metadata: nil + ).serializedResponse().id ?? "" + + let paymentTokenSource = try self.drop?.stripe?.tokens.createCardToken( + withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card", + customer: nil, + currency: nil + ).serializedResponse().id ?? "" + + self.customerId = try self.drop?.stripe?.customer.create( + accountBalance: nil, + businessVATId: nil, + coupon: nil, + defaultSource: nil, + description: nil, + email: nil, + shipping: nil, + source: paymentTokenSource + ).serializedResponse().id ?? "" + + self.subscriptionId = try self.drop?.stripe?.subscriptions.create( + forCustomer: self.customerId, + plan: planId, + applicationFeePercent: nil, + couponId: nil, + items: nil, + quantity: nil, + source: nil, + taxPercent: nil, + trialEnd: nil, + trialPeriodDays: nil + ).serializedResponse().id ?? "" + + self.invoiceItemId = try self.drop?.stripe?.invoiceItems.createItem( + forCustomer: self.customerId, + amount: 10_000, inCurrency: .usd + ).serializedResponse().id ?? "" + + self.invoiceId = try drop?.stripe?.invoices.create( + forCustomer: self.customerId, + subscription: self.subscriptionId + ).serializedResponse().id ?? "" + } catch let error as StripeError { switch error { case .apiConnectionError: @@ -42,20 +102,207 @@ class InvoiceTests: XCTestCase { default: XCTFail(error.localizedDescription) } - } - catch { + } catch { fatalError("Setup failed: \(error.localizedDescription)") } } override func tearDown() { self.drop = nil + self.customerId = "" + self.subscriptionId = "" self.invoiceId = "" + self.invoiceItemId = "" super.tearDown() } - func testExample() { - + func testCreatingInvoice() throws { + do { + let object = try drop?.stripe?.invoices.create( + forCustomer: self.customerId, + subscription: self.subscriptionId + ).serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testFetchingInvoice() throws { + do { + print(self.invoiceId) + let object = try drop?.stripe?.invoices.fetch(invoice: self.invoiceId).serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testFetchingInvoiceItems() throws { + do { + let object = try drop?.stripe?.invoices.listItems(forInvoice: self.invoiceId) + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testFetchUpcomingInvoice() throws { + do { + let object = try drop?.stripe?.invoices.upcomingInvoice(forCustomer: self.customerId).serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateInvoice() throws { + do { + let object = try drop?.stripe?.invoices.update( + invoice: self.invoiceId, + description: "Update Invoice" + ).serializedResponse() + + XCTAssertEqual("Update Invoice", object?.description) + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllInvoices() throws { + do { + let object = try drop?.stripe?.invoices.listAll().serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } } } From d7ed30ee0ca7261032b0578eecf076d1b0ab4f9c Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:03:57 -0700 Subject: [PATCH 24/31] invoices: Fix a node typo --- Sources/Stripe/Models/Invoices/InvoiceItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceItem.swift index 2027fe6..a482266 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceItem.swift @@ -94,7 +94,7 @@ public final class InvoiceItemList: StripeModelProtocol { public private(set) var object: String? public private(set) var hasMore: Bool? - public private(set) var items: [Invoice]? + public private(set) var items: [InvoiceItem]? public init(node: Node) throws { self.object = try node.get("object") From 4eb06da3de1712ed40c39b17441e0b2c66b7ebee Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:04:05 -0700 Subject: [PATCH 25/31] invoices: Fix a route declaration --- Sources/Stripe/API/Routes/InvoiceItemRoutes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index 6a2c4cb..7abeaee 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -152,7 +152,7 @@ public final class InvoiceItemRoutes { - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func listAll(customer: String? = nil, filter: StripeFilter?) throws -> StripeRequest { + public func listAll(customer: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let customer = customer { query["customer"] = customer From 29eccbe3ab7a1b42ae740e55a393221ee81cf3de Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:04:12 -0700 Subject: [PATCH 26/31] tests: Add Invoice Item tests --- Tests/StripeTests/InvoiceItemTests.swift | 244 +++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 Tests/StripeTests/InvoiceItemTests.swift diff --git a/Tests/StripeTests/InvoiceItemTests.swift b/Tests/StripeTests/InvoiceItemTests.swift new file mode 100644 index 0000000..ddcf614 --- /dev/null +++ b/Tests/StripeTests/InvoiceItemTests.swift @@ -0,0 +1,244 @@ +// +// InvoiceTests.swift +// Stripe +// +// Created by Anthony Castelli on 9/5/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Random + +class InvoiceItemTests: XCTestCase { + + var drop: Droplet? + var customerId = "" + var invoiceItemId = "" + + override func setUp() { + super.setUp() + do { + self.drop = try self.makeDroplet() + + let paymentTokenSource = try self.drop?.stripe?.tokens.createCardToken( + withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card", + customer: nil, + currency: nil + ).serializedResponse().id ?? "" + + self.customerId = try self.drop?.stripe?.customer.create( + accountBalance: nil, + businessVATId: nil, + coupon: nil, + defaultSource: nil, + description: nil, + email: nil, + shipping: nil, + source: paymentTokenSource + ).serializedResponse().id ?? "" + + self.invoiceItemId = try self.drop?.stripe?.invoiceItems.createItem( + forCustomer: self.customerId, + amount: 10_000, inCurrency: .usd + ).serializedResponse().id ?? "" + + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + self.drop = nil + self.customerId = "" + self.invoiceItemId = "" + super.tearDown() + } + + func testCreatingItem() throws { + do { + let object = try self.drop?.stripe?.invoiceItems.createItem( + forCustomer: self.customerId, + amount: 10_000, inCurrency: .usd + ).serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testFetchingItem() throws { + do { + let object = try self.drop?.stripe?.invoiceItems.fetch(invoiceItem: self.invoiceItemId).serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testDeletingItem() throws { + do { + let object = try self.drop?.stripe?.invoiceItems.delete(invoiceItem: self.invoiceItemId) + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateItem() throws { + do { + let object = try self.drop?.stripe?.invoiceItems.update( + invoiceItem: self.invoiceItemId, + amount: 10_000, + description: "Update Invoice" + ).serializedResponse() + + XCTAssertEqual("Update Invoice", object?.description) + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllItems() throws { + do { + let object = try self.drop?.stripe?.invoiceItems.listAll().serializedResponse() + XCTAssertNotNil(object) + } catch let error as StripeError { + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + +} From 69b2e633f09e8b129f0ef36fef9357fbfff54c3f Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:04:50 -0700 Subject: [PATCH 27/31] tests: Clean up for consistancy --- Tests/StripeTests/InvoiceTests.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Tests/StripeTests/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift index addd487..cdd12dc 100644 --- a/Tests/StripeTests/InvoiceTests.swift +++ b/Tests/StripeTests/InvoiceTests.swift @@ -118,7 +118,7 @@ class InvoiceTests: XCTestCase { func testCreatingInvoice() throws { do { - let object = try drop?.stripe?.invoices.create( + let object = try self.drop?.stripe?.invoices.create( forCustomer: self.customerId, subscription: self.subscriptionId ).serializedResponse() @@ -151,8 +151,7 @@ class InvoiceTests: XCTestCase { func testFetchingInvoice() throws { do { - print(self.invoiceId) - let object = try drop?.stripe?.invoices.fetch(invoice: self.invoiceId).serializedResponse() + let object = try self.drop?.stripe?.invoices.fetch(invoice: self.invoiceId).serializedResponse() XCTAssertNotNil(object) } catch let error as StripeError { switch error { @@ -182,7 +181,7 @@ class InvoiceTests: XCTestCase { func testFetchingInvoiceItems() throws { do { - let object = try drop?.stripe?.invoices.listItems(forInvoice: self.invoiceId) + let object = try self.drop?.stripe?.invoices.listItems(forInvoice: self.invoiceId) XCTAssertNotNil(object) } catch let error as StripeError { switch error { @@ -212,7 +211,7 @@ class InvoiceTests: XCTestCase { func testFetchUpcomingInvoice() throws { do { - let object = try drop?.stripe?.invoices.upcomingInvoice(forCustomer: self.customerId).serializedResponse() + let object = try self.drop?.stripe?.invoices.upcomingInvoice(forCustomer: self.customerId).serializedResponse() XCTAssertNotNil(object) } catch let error as StripeError { switch error { @@ -242,7 +241,7 @@ class InvoiceTests: XCTestCase { func testUpdateInvoice() throws { do { - let object = try drop?.stripe?.invoices.update( + let object = try self.drop?.stripe?.invoices.update( invoice: self.invoiceId, description: "Update Invoice" ).serializedResponse() @@ -277,7 +276,7 @@ class InvoiceTests: XCTestCase { func testListAllInvoices() throws { do { - let object = try drop?.stripe?.invoices.listAll().serializedResponse() + let object = try self.drop?.stripe?.invoices.listAll().serializedResponse() XCTAssertNotNil(object) } catch let error as StripeError { switch error { From 4ed1317e821b0b47ccdbdf83a835f2c4acdf4857 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:08:27 -0700 Subject: [PATCH 28/31] tests: Add linux tests --- Tests/LinuxMain.swift | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index dc858f1..511de3d 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 0.7.2 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.8.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import XCTest @@ -72,6 +72,27 @@ static var allTests = [ ] } +extension InvoiceItemTests { +static var allTests = [ + ("testCreatingItem", testCreatingItem), + ("testFetchingItem", testFetchingItem), + ("testDeletingItem", testDeletingItem), + ("testUpdateItem", testUpdateItem), + ("testListAllItems", testListAllItems), +] +} + +extension InvoiceTests { +static var allTests = [ + ("testCreatingInvoice", testCreatingInvoice), + ("testFetchingInvoice", testFetchingInvoice), + ("testFetchingInvoiceItems", testFetchingInvoiceItems), + ("testFetchUpcomingInvoice", testFetchUpcomingInvoice), + ("testUpdateInvoice", testUpdateInvoice), + ("testListAllInvoices", testListAllInvoices), +] +} + extension OrderReturnTests { static var allTests = [ ("testRetrieveOrderReturn", testRetrieveOrderReturn), @@ -179,6 +200,8 @@ XCTMain([ testCase(CouponTests.allTests), testCase(CustomerTests.allTests), testCase(DisputeTests.allTests), + testCase(InvoiceItemTests.allTests), + testCase(InvoiceTests.allTests), testCase(OrderReturnTests.allTests), testCase(OrderTests.allTests), testCase(PlanTests.allTests), From f9fa8d61e7a24be31b47872cda8ac2dc80629aa4 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:19:21 -0700 Subject: [PATCH 29/31] tests: Create an invoice item when creating the invoice --- Tests/StripeTests/InvoiceTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/StripeTests/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift index cdd12dc..f54717d 100644 --- a/Tests/StripeTests/InvoiceTests.swift +++ b/Tests/StripeTests/InvoiceTests.swift @@ -118,6 +118,12 @@ class InvoiceTests: XCTestCase { func testCreatingInvoice() throws { do { + let invoiceItem = try self.drop?.stripe?.invoiceItems.createItem( + forCustomer: self.customerId, + amount: 10_000, inCurrency: .usd + ).serializedResponse().id ?? "" + XCTAssertNotNil(invoiceItem) + let object = try self.drop?.stripe?.invoices.create( forCustomer: self.customerId, subscription: self.subscriptionId From 40823ff923640cd02305bffa049b45501f891fd4 Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 11:22:13 -0700 Subject: [PATCH 30/31] readme: Clean up the readme --- README.md | 54 +++++++----------------------------------------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 2d597c6..ded9f0d 100644 --- a/README.md +++ b/README.md @@ -63,64 +63,21 @@ XCTMain([ ## Whats Implemented * [x] Balance Fetching - * [x] History - * [x] Balance by ID * [x] Charges - * [x] Creating Charges - * [x] Retrieving a charge by id - * [x] Listing all charges - * [x] Updating a charge - * [x] Capturing a charge * [x] Customers - * [x] Creating - * [x] Updating - * [x] Deleting - * [x] Fetching by Customer ID - * [x] Listing All Customers (With filters) * [x] Coupons - * [x] Creating - * [x] Updating - * [x] Deleting - * [x] Fetching by Coupon ID - * [x] Listing All Coupons (With filters) * [x] Plans - * [x] Creating - * [x] Updating - * [x] Deleting - * [x] Fetching by Plan ID - * [x] Listing All Plans (With filters) * [x] Refunds - * [x] Creating a Refund - * [x] Retrieval - * [x] Updating - * [x] Listing all * [x] Tokens - * [x] Card Creation - * [x] Bank Creation - * [x] Token Retrieval * [x] Sources - * [x] Creating - * [x] Updating - * [x] Fetching by Source ID -* [x] Subscriptions - * [x] Creating - * [x] Updating - * [x] Deleting - * [x] Fetching by subscription ID - * [x] Listing All Subscriptions (With filters) +* [x] Subscriptions * [x] Connect account - * [x] Creating - * [x] Updating - * [x] Deleting - * [x] Fetching by account ID - * [x] Listing All Accounts (With filters) - * [x] Rejecting accounts - * [x] Creating dashboard login link for express accounts * [x] Orders * [x] Order Items * [x] Products -* [x] Disputes -* [ ] Cards +* [x] Disputes +* [x] Invoices +* [x] Invoice Items [stripe_home]: http://stripe.com "Stripe" [stripe_api]: https://stripe.com/docs/api "Stripe API Endpoints" @@ -129,3 +86,6 @@ XCTMain([ ## License Vapor Stripe Provider is available under the MIT license. See the [LICENSE](LICENSE) file for more info. + +## Want to help? +Feel free to submit a pull request whether it's a clean up, a new approach to handling things, adding a new part of the API, or even if it's just a typo. All help is welcomed! 😀 From e3d96aa7ae6d22dc5cbcdc8be32416daa85a30ef Mon Sep 17 00:00:00 2001 From: Anthony Castelli Date: Wed, 6 Sep 2017 17:16:08 -0700 Subject: [PATCH 31/31] invoices: Remove blank lines --- Sources/Stripe/API/Routes/InvoiceItemRoutes.swift | 1 - Sources/Stripe/Models/Invoices/Invoice.swift | 2 -- Sources/Stripe/Models/Invoices/InvoiceItem.swift | 3 --- Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift | 1 - Sources/Stripe/Models/Invoices/InvoiceLineItem.swift | 1 - Tests/StripeTests/InvoiceItemTests.swift | 1 - Tests/StripeTests/InvoiceTests.swift | 1 - 7 files changed, 10 deletions(-) diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index 7abeaee..5c84cbd 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -162,5 +162,4 @@ public final class InvoiceItemRoutes { } return try StripeRequest(client: self.client, method: .get, route: .invoiceItems, query: query, body: nil, headers: nil) } - } diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift index 8e199d4..2bb49c0 100644 --- a/Sources/Stripe/Models/Invoices/Invoice.swift +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -95,7 +95,6 @@ public final class Invoice: StripeModelProtocol { if let lines = node["lines"]?.object, let data = lines["data"]?.array { self.lines = try data.map({ try InvoiceLineItem(node: $0) }) } - } public func makeNode(in context: Context?) throws -> Node { @@ -158,5 +157,4 @@ public final class InvoiceList: StripeModelProtocol { ] return try Node(node: object) } - } diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceItem.swift index a482266..1f61821 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceItem.swift @@ -74,14 +74,11 @@ public final class InvoiceItem: StripeModelProtocol { "proration": self.isProration, "quantity": self.quantity, "subscription": self.subscription, - "period": [ "start": self.periodStart, "end": self.periodEnd ], - "metadata": self.metadata, - "plan": self.plan, "currency": self.currency?.rawValue ] diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift index 20f44a5..ae93fde 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift @@ -29,5 +29,4 @@ public final class InvoiceLineGroup: StripeModelProtocol { ] return try Node(node: object) } - } diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift index af7610d..933b044 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift @@ -79,5 +79,4 @@ public final class InvoiceLineItem: StripeModelProtocol { return try Node(node: object) } - } diff --git a/Tests/StripeTests/InvoiceItemTests.swift b/Tests/StripeTests/InvoiceItemTests.swift index ddcf614..b78edc0 100644 --- a/Tests/StripeTests/InvoiceItemTests.swift +++ b/Tests/StripeTests/InvoiceItemTests.swift @@ -240,5 +240,4 @@ class InvoiceItemTests: XCTestCase { XCTFail(error.localizedDescription) } } - } diff --git a/Tests/StripeTests/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift index f54717d..7b076fd 100644 --- a/Tests/StripeTests/InvoiceTests.swift +++ b/Tests/StripeTests/InvoiceTests.swift @@ -309,5 +309,4 @@ class InvoiceTests: XCTestCase { XCTFail(error.localizedDescription) } } - }