PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.6.6
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.6.6
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Abilities / Abilities / CreateInvoice.php
surecart / app / src / Abilities / Abilities Last commit date
AbstractAbility.php 2 weeks ago ArchiveProduct.php 4 months ago CancelSubscription.php 4 months ago CreateCoupon.php 4 months ago CreateCustomer.php 4 months ago CreateFulfillment.php 4 months ago CreateInvoice.php 2 months ago CreatePrice.php 2 months ago CreateProduct.php 4 months ago CreatePromotion.php 4 months ago CreateRefund.php 2 months ago DeleteCoupon.php 4 months ago DeleteCustomer.php 4 months ago DeleteFulfillment.php 4 months ago DeletePromotion.php 4 months ago DuplicateProduct.php 4 months ago FilterCustomers.php 2 months ago FilterOrders.php 2 months ago FilterSubscriptions.php 2 months ago GetAbandonedCheckout.php 3 months ago GetCoupon.php 4 months ago GetCustomer.php 4 months ago GetCustomersFilterSchema.php 2 months ago GetFulfillment.php 4 months ago GetFulfillmentItem.php 4 months ago GetLicense.php 4 months ago GetOrder.php 4 months ago GetOrderStatistics.php 4 months ago GetOrdersFilterSchema.php 2 months ago GetProduct.php 4 months ago GetPromotion.php 4 months ago GetRefund.php 4 months ago GetStoreDashboard.php 4 months ago GetStoreInfo.php 4 months ago GetSubscription.php 4 months ago GetSubscriptionsFilterSchema.php 2 months ago ListAbandonedCheckouts.php 3 months ago ListCoupons.php 4 months ago ListCustomers.php 4 months ago ListFulfillments.php 4 months ago ListLicenses.php 4 months ago ListOrders.php 4 months ago ListPrices.php 4 months ago ListProducts.php 4 months ago ListPromotions.php 4 months ago ListRefunds.php 4 months ago ListSubscriptions.php 4 months ago UpdateCoupon.php 4 months ago UpdateCustomer.php 4 months ago UpdateFulfillment.php 4 months ago UpdateInvoice.php 4 months ago UpdatePrice.php 4 months ago UpdateProduct.php 4 months ago UpdatePromotion.php 4 months ago UpdateSubscriptionRenewalDate.php 4 months ago
CreateInvoice.php
275 lines
1 <?php
2
3 namespace SureCart\Abilities\Abilities;
4
5 use SureCart\Models\Checkout;
6 use SureCart\Models\Invoice;
7 use SureCart\Models\LineItem;
8
9 /**
10 * Create a new invoice for a customer.
11 */
12 class CreateInvoice extends AbstractAbility {
13
14 /**
15 * {@inheritDoc}
16 */
17 public function get_name(): string {
18 return 'surecart/create-invoice';
19 }
20
21 /**
22 * {@inheritDoc}
23 */
24 public function get_label(): string {
25 return __( 'Create Invoice', 'surecart' );
26 }
27
28 /**
29 * {@inheritDoc}
30 */
31 public function get_description(): string {
32 return __( 'Create a new SureCart invoice for a customer. Requires a customer ID, a price ID for the line item, and a due date. For multi-variant products, a variant_id is required to specify which variant to invoice. Optionally set a custom ad-hoc amount, quantity, and a memo/description. The invoice is created in draft status and then opened so the customer can pay.', 'surecart' );
33 }
34
35 /**
36 * {@inheritDoc}
37 */
38 public function get_annotations(): array {
39 return array(
40 'readonly' => false,
41 'destructive' => false,
42 'idempotent' => false,
43 );
44 }
45
46 /**
47 * {@inheritDoc}
48 */
49 public function get_instructions(): string {
50 return 'This ability creates a full invoice in one step. It requires a customer_id and a price_id (use surecart/list-prices to find one). For products with multiple variants, you MUST also provide a variant_id — use surecart/get-product to retrieve the product and inspect its variants array to find the correct variant ID. Without a variant_id on multi-variant products, the invoice will fail with "Item no longer available". If the price has ad_hoc enabled, you can override the amount with ad_hoc_amount in smallest currency unit (e.g., 9999 for $99.99). A due_date in YYYY-MM-DD format is required. The invoice is created as a draft, populated with the line item and customer, then opened automatically. Each call creates a new invoice — do not call multiple times for the same invoice. Always confirm the details with the user before creating.';
51 }
52
53 /**
54 * {@inheritDoc}
55 */
56 public function check_permission(): bool {
57 return current_user_can( 'publish_sc_invoices' );
58 }
59
60 /**
61 * {@inheritDoc}
62 */
63 public function get_input_schema(): array {
64 return array(
65 'type' => 'object',
66 'properties' => array(
67 'customer_id' => array(
68 'type' => 'string',
69 'description' => __( 'The SureCart customer ID to invoice.', 'surecart' ),
70 ),
71 'price_id' => array(
72 'type' => 'string',
73 'description' => __( 'The price ID to add as a line item. Use surecart/list-prices to find available prices.', 'surecart' ),
74 ),
75 'variant_id' => array(
76 'type' => 'string',
77 'description' => __( 'The variant ID for multi-variant products. Required when the product has variants. Use surecart/get-product to see available variants and their IDs.', 'surecart' ),
78 ),
79 'due_date' => array(
80 'type' => 'string',
81 'description' => __( 'Due date in YYYY-MM-DD format.', 'surecart' ),
82 ),
83 'ad_hoc_amount' => array(
84 'type' => 'integer',
85 'description' => __( 'Custom amount in smallest currency unit (e.g., 9999 for $99.99). Only works with ad-hoc enabled prices.', 'surecart' ),
86 ),
87 'quantity' => array(
88 'type' => 'integer',
89 'description' => __( 'Line item quantity. Defaults to 1.', 'surecart' ),
90 'default' => 1,
91 ),
92 'description' => array(
93 'type' => 'string',
94 'description' => __( 'Invoice memo or description.', 'surecart' ),
95 ),
96 'notifications_enabled' => array(
97 'type' => 'boolean',
98 'description' => __( 'Whether to send an email notification to the customer when the invoice is opened. Defaults to true.', 'surecart' ),
99 'default' => true,
100 ),
101 ),
102 'required' => array( 'customer_id', 'price_id', 'due_date' ),
103 );
104 }
105
106 /**
107 * {@inheritDoc}
108 */
109 public function get_output_schema(): array {
110 return array(
111 'type' => 'object',
112 'properties' => array(
113 'success' => array( 'type' => 'boolean' ),
114 'invoice' => array( 'type' => 'object' ),
115 ),
116 );
117 }
118
119 /**
120 * {@inheritDoc}
121 *
122 * @param array $input The input data.
123 */
124 public function execute( array $input ) {
125 $customer_id = sanitize_text_field( $input['customer_id'] ?? '' );
126 if ( empty( $customer_id ) ) {
127 return $this->error( 'missing_customer_id', __( 'A customer ID is required.', 'surecart' ) );
128 }
129
130 $price_id = sanitize_text_field( $input['price_id'] ?? '' );
131 if ( empty( $price_id ) ) {
132 return $this->error( 'missing_price_id', __( 'A price ID is required.', 'surecart' ) );
133 }
134
135 $due_date = sanitize_text_field( $input['due_date'] ?? '' );
136 if ( empty( $due_date ) ) {
137 return $this->error( 'missing_due_date', __( 'A due date is required.', 'surecart' ) );
138 }
139
140 if ( ! $this->is_valid_date( $due_date ) ) {
141 return $this->error( 'invalid_date', __( 'due_date must be in YYYY-MM-DD format.', 'surecart' ) );
142 }
143
144 $quantity = absint( $input['quantity'] ?? 1 );
145 if ( $quantity < 1 ) {
146 $quantity = 1;
147 }
148
149 // Step 1: Create a draft invoice.
150 // Do not hardcode live_mode — let SureCart inherit the store's active mode.
151 // This ensures invoices can be created for both live and test mode customers.
152 $invoice = Invoice::create( array() );
153
154 if ( is_wp_error( $invoice ) ) {
155 return $invoice;
156 }
157
158 $checkout_id = $invoice->checkout ?? '';
159 if ( is_object( $checkout_id ) ) {
160 $checkout_id = $checkout_id->id ?? '';
161 }
162
163 if ( empty( $checkout_id ) ) {
164 return $this->error( 'no_checkout', __( 'Invoice was created but no associated checkout was found.', 'surecart' ) );
165 }
166
167 // Step 2: Update the checkout with the customer.
168 // Expand customer.shipping_address so we can sync it to the checkout (mirrors admin UI behavior).
169 $checkout = Checkout::with(
170 array(
171 'customer',
172 'customer.shipping_address',
173 )
174 )->update(
175 array(
176 'id' => $checkout_id,
177 'customer_id' => $customer_id,
178 )
179 );
180
181 if ( is_wp_error( $checkout ) ) {
182 return $checkout;
183 }
184
185 // Step 2b: Sync the customer's shipping address to the checkout.
186 // The admin UI does this as a second PATCH after attaching the customer.
187 // Without this, open() fails with "Please enter additional shipping address information".
188 if ( ! empty( $checkout->customer->shipping_address ) ) {
189 $shipping_address = $checkout->customer->shipping_address;
190
191 // Convert the address model/object to an array for the update.
192 $address_data = is_object( $shipping_address ) ? (array) $shipping_address : $shipping_address;
193
194 $checkout = Checkout::update(
195 array(
196 'id' => $checkout_id,
197 'customer_id' => $customer_id,
198 'shipping_address' => $address_data,
199 )
200 );
201
202 if ( is_wp_error( $checkout ) ) {
203 return $checkout;
204 }
205 }
206
207 // Step 3: Add a line item to the checkout.
208 $line_item_data = array(
209 'checkout' => $checkout_id,
210 'price' => $price_id,
211 'quantity' => $quantity,
212 );
213
214 if ( ! empty( $input['variant_id'] ) ) {
215 $line_item_data['variant'] = sanitize_text_field( $input['variant_id'] );
216 }
217
218 if ( isset( $input['ad_hoc_amount'] ) ) {
219 $ad_hoc_amount = intval( $input['ad_hoc_amount'] );
220 if ( $ad_hoc_amount < 0 ) {
221 return $this->error( 'invalid_ad_hoc_amount', __( 'ad_hoc_amount must be zero or greater. Negative invoice amounts are not supported.', 'surecart' ) );
222 }
223 $line_item_data['ad_hoc_amount'] = $ad_hoc_amount;
224 }
225
226 $line_item = LineItem::create( $line_item_data );
227
228 if ( is_wp_error( $line_item ) ) {
229 return $line_item;
230 }
231
232 // Step 4: Update the invoice with due date and memo.
233 $invoice_update_data = array(
234 'id' => $invoice->id,
235 'due_date' => ( new \DateTime( $due_date ) )->getTimestamp(),
236 );
237
238 if ( ! empty( $input['description'] ) ) {
239 $invoice_update_data['memo'] = sanitize_textarea_field( $input['description'] );
240 }
241
242 // Persist notifications_enabled before opening so the open action respects it.
243 if ( isset( $input['notifications_enabled'] ) ) {
244 $invoice_update_data['notifications_enabled'] = (bool) $input['notifications_enabled'];
245 }
246
247 $updated_invoice = Invoice::update( $invoice_update_data );
248
249 if ( is_wp_error( $updated_invoice ) ) {
250 return $updated_invoice;
251 }
252
253 // Step 5: Open the invoice so the customer can pay.
254 $opened_invoice = Invoice::with(
255 array(
256 'checkout',
257 'checkout.line_items',
258 'line_item.price',
259 'price.product',
260 'checkout.customer',
261 )
262 )->open( $updated_invoice->id );
263
264 if ( is_wp_error( $opened_invoice ) ) {
265 return $opened_invoice;
266 }
267
268 return $this->success(
269 array(
270 'invoice' => $this->model_to_array( $opened_invoice ),
271 )
272 );
273 }
274 }
275