Skip to content

Commit

Permalink
Merge pull request #278 from woocommerce/fix/import-end-and-cancelled…
Browse files Browse the repository at this point in the history
…-dates

allow importing cancelled date, and fix handling of end_date
  • Loading branch information
dkoo committed Mar 14, 2024
2 parents ce32a9d + fbe2b7c commit 8ac107c
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 10 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ To import your subscriptions to WooCommerce via CSV, you need to:
1. Check **Email Passwords** to email customers that are newly created their account details
1. Check **Add memberships** to grant subscribers any membership/s plan corresponding to subscription products
1. Click **Upload and file and import**
1. Review each column of data in your file to make sure the Importer has mapped it to the correct column header.
1. Review each column of data in your file to make sure the Importer has mapped it to the correct column header.
1. Click **Test CSV**
1. If there are errors, fix up the CSV file and return to step 1.
1. If there are no errors, click **Run Import**
Expand All @@ -70,7 +70,7 @@ Import options:
![](https://cldup.com/YFwi6NIp-L.png)

#### Run in Test Mode
Running the import in test mode will analyse each row of your CSV and notify you of any [warnings or errors](#list-of-warnings-and-errors) with that data.
Running the import in test mode will analyse each row of your CSV and notify you of any [warnings or errors](#list-of-warnings-and-errors) with that data.

It will not import any subscription data in your store's database, like users or subscriptions.

Expand All @@ -90,7 +90,7 @@ When the **Email Passwords** option is enabled, if the Importer creates a new us

If left unticked, the new users created will need to go through the "forgot your password" process which will let them reset their details via email.

Please note: the minimum requirement for creating a new user is an email address. If no username is given, the importer will to create a username from the email. Say you you need to create a new user and have only given the email address, janedoe@example.com, the importer will try a new user with username janedoe. If this username is already taken, we then try the username janedoe1, janedoe2 and so on; until it finds a free username (i.e janedoe102).
Please note: the minimum requirement for creating a new user is an email address. If no username is given, the importer will to create a username from the email. Say you you need to create a new user and have only given the email address, janedoe@example.com, the importer will try a new user with username janedoe. If this username is already taken, we then try the username janedoe1, janedoe2 and so on; until it finds a free username (i.e janedoe102).

#### Add Memberships

Expand Down Expand Up @@ -247,7 +247,8 @@ Please follow these general rules when formatting your CSV file:
|`start_date`|`Y-m-d H:i:s`|The start time to set on the subscription. Must be in the past.|The current time.|
|`trial_end_date`|`Y-m-d H:i:s`|A date in the past or future on which a the subscriptions trial period will end. If set, the trial end date must come after the start date.|-|
|`next_payment_date`|`Y-m-d H:i:s`|The date to process the next renewal payment. If set, the next payment date must come after the start date and trial end date and be in the future. If left empty, when the status is next updated to `wc-active` the next payment date will be calculated based on the start or trial end date and billing period/interval.|-|
|`end_date`|`Y-m-d H:i:s`|The date on which the subscription will expire, if in the future, or was cancelled or expired, if in the past. Leave empty to have the subscription continue to renew until manually cancelled.|-|
|`cancelled_date`|`Y-m-d H:i:s`|The date on which the subscription was set to be cancelled, either by the customer or via admin action. If setting this date, the subscription status must be one of the following: `cancelled`, `trash`, `expired`, `switched`, `pending-cancel`. Leave empty if the subscription was never set to be cancelled.|-|
|`end_date`|`Y-m-d H:i:s`|The date on which the subscription will cancel/expire, if in the future, or did cancel/expire, if in the past. Leave empty to have the subscription continue to renew until manually cancelled. If setting this with `cancelled_date`, the `cancelled_date` must occur before `end_date`. If setting this without `cancelled_date`, the `cancelled_date` will be set to the same time as `end_date`.|-|
|`billing_period`|`string`|The time period used for calculating renewal payment dates. Must be either: `day`, `week`, `month`, `year`. An invalid or empty billing period will cause an error during the import and the subscription will not be imported.|-|
|`billing_interval`|`int`|The interval used for calculating renewal payment dates. Must be an integer value to represent how many subscription periods between each payment. For example, a `2` here and `week` for the `billing_period` will create a subscription processes a renewal payment every two weeks.|`1`|
|`order_items`|`mixed`|The product line items on the subscription used to set the line items on renewal orders. Can be a product or variation ID or a more advanced set of data as detailed in the [Importing Order Items](#importing-order-items-product-line-items) section.|-|
Expand Down Expand Up @@ -497,7 +498,7 @@ If the later approach is taken, we strongly recommend that you notify customers
> PayPal Standard Note: Unfortunately, this approach isn't suitable for PayPal Standard. With PayPal Standard, if no valid PayPal subscription ID is provided, exceptions will be thrown whenever you or the customer changes the status of the subscription with your store, meaning its impossible to change the status of imported subscriptions. This is because Subscriptions attempts to communicate this state change to PayPal, but is unable to connect it to a valid subscription there. If you need to take this approach for customers using PayPal Standard, temporarily use Stripe or a fake payment method instead.
#### How can I check if a payment method can be imported with automatic payments?
WooCommerce Subscriptions v2.0 introduced a new way for payment gateways to register the payment meta data they require for processing automatic recurring payments.
WooCommerce Subscriptions v2.0 introduced a new way for payment gateways to register the payment meta data they require for processing automatic recurring payments.

To support this method, the payment gateway extension must use the filter: `'woocommerce_subscription_payment_meta'`.

Expand Down
2 changes: 1 addition & 1 deletion includes/class-wcs-exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public static function write_subscriptions_csv_row( $subscription ) {
if ( ! empty( $meta_string ) ) {
$meta_string .= '+';
}

// Prevent array to string notice caused by Composite Products when using Subscribe All The Things
if ( is_array( $meta_value ) ) {
$meta_value = json_encode( $meta_value );
Expand Down
1 change: 1 addition & 0 deletions includes/class-wcs-import-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ public function save_mapping() {
'trial_end_date' => '',
'next_payment_date' => '',
'last_payment_date' => '',
'cancelled_date' => '',
'end_date' => '',
'billing_first_name' => '',
'billing_last_name' => '',
Expand Down
25 changes: 21 additions & 4 deletions includes/class-wcs-importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,16 @@ public static function import_subscription( $data ) {
}
}

$status = 'pending';
if ( empty( $data[ self::$fields['subscription_status'] ] ) ) {
$status = 'pending';
$result['warning'][] = esc_html__( 'No subscription status was specified. The subscription will be created with the status "pending". ', 'wcs-import-export' );
} else {
$status = ( 'wc-' === substr( $data[ self::$fields['subscription_status'] ], 0, 3 ) ) ? substr( $data[ self::$fields['subscription_status'] ], 3 ) : $data[ self::$fields['subscription_status'] ];
}

$dates_to_update = array( 'start' => ( ! empty( $data[ self::$fields['start_date'] ] ) ) ? gmdate( 'Y-m-d H:i:s', strtotime( $data[ self::$fields['start_date'] ] ) ) : gmdate( 'Y-m-d H:i:s', time() - 1 ) );

foreach ( array( 'trial_end_date', 'next_payment_date', 'end_date', 'last_payment_date' ) as $date_type ) {
foreach ( array( 'trial_end_date', 'next_payment_date', 'cancelled_date', 'end_date', 'last_payment_date' ) as $date_type ) {
$dates_to_update[ $date_type ] = ( ! empty( $data[ self::$fields[ $date_type ] ] ) ) ? gmdate( 'Y-m-d H:i:s', strtotime( $data[ self::$fields[ $date_type ] ] ) ) : '';
}

Expand All @@ -330,6 +330,13 @@ public static function import_subscription( $data ) {
}

switch ( $date_type ) {
case 'cancelled_date':
if ( ! in_array( $status, wcs_get_subscription_ended_statuses() ) ) {
$result['error'][] = sprintf( __( 'Cannot set a %s date for an active subscription.', 'wcs-import-export' ), $date_type );
}
if ( ! empty( $dates_to_update['end_date'] ) && strtotime( $datetime ) > strtotime( $dates_to_update['end_date'] ) ) {
$result['error'][] = sprintf( __( 'The %s date must occur before the end date.', 'wcs-import-export' ), $date_type );
}
case 'end_date' :
if ( ! empty( $dates_to_update['next_payment_date'] ) && strtotime( $datetime ) <= strtotime( $dates_to_update['next_payment_date'] ) ) {
$result['error'][] = sprintf( __( 'The %s date must occur after the next payment date.', 'wcs-import-export' ), $date_type );
Expand Down Expand Up @@ -375,6 +382,7 @@ public static function import_subscription( $data ) {
'created_via' => 'importer',
'customer_note' => ( ! empty( $data[ self::$fields['customer_note'] ] ) ) ? $data[ self::$fields['customer_note'] ] : '',
'currency' => ( ! empty( $data[ self::$fields['order_currency'] ] ) ) ? $data[ self::$fields['order_currency'] ] : '',
'status' => in_array( $status, wcs_get_subscription_ended_statuses() ) ? $status : 'pending', // Subscription must be in pending status to auto-calculate next_payment_date, but must be a non-active status in order to apply cancelled_date and end_date.
)
);

Expand Down Expand Up @@ -406,6 +414,7 @@ public static function import_subscription( $data ) {
// Now that we've set all the meta data, reinit the object so the data is set
$subscription = wcs_get_subscription( $subscription_id );

// Update dates while in "pending" status so missing next payment dates get auto-calculated.
$subscription->update_dates( $dates_to_update );

if ( ! $set_manual && ! in_array( $status, wcs_get_subscription_ended_statuses() ) ) { // don't bother trying to set payment meta on a subscription that won't ever renew
Expand Down Expand Up @@ -494,6 +503,7 @@ public static function import_subscription( $data ) {
add_filter( 'woocommerce_can_subscription_be_updated_to_cancelled', '__return_true' );
add_filter( 'woocommerce_can_subscription_be_updated_to_pending-cancel', '__return_true' );

// Update status again for active subscriptions.
$subscription->update_status( $status );

remove_filter( 'woocommerce_can_subscription_be_updated_to_cancelled', '__return_true' );
Expand All @@ -504,9 +514,7 @@ public static function import_subscription( $data ) {
self::maybe_add_memberships( $user_id, $subscription->get_id(), $product_id );
}
}
}

if ( ! self::$test_mode ) {
$subscription->save();
}

Expand All @@ -529,6 +537,15 @@ public static function import_subscription( $data ) {
$result['status'] = 'failed';
WCS_Import_Logger::log( sprintf( 'Row #%s failed: %s', $result['row_number'], print_r( $result['error'], true ) ) );
}

/**
* Action hook to allow for custom actions after a subscription has been imported and can be manipulated.
*
* @param WC_Subscription $subscription The subscription object created by the importer.
* @param array $result The result of the import.
* @paran array $data The raw data from the import CSV.
*/
do_action( 'woocommerce_subscription_imported_via_csv', $subscription, $result, $data );
}

array_push( self::$results, $result );
Expand Down

0 comments on commit 8ac107c

Please sign in to comment.