Suppose a user wants to cancel their subscription, so I issue a command like this:
stripe_subscription.delete(at_period_end: true)
Later, though—before the period ends—the user changes their mind. Is there a call I can issue to undo the scheduled cancellation?
If not, what's the best way to implement this? My best guess looks like this:
new_subscription = stripe_customer.subscriptions.create(plan: stripe_subscription.plan.id, trial_end: stripe_subscription.current_period_end)
stripe_subscription.delete() # if permitted
self.stripe_subscription = new_subscription
save!
Is there something better I can do?
If the subscription is still active, you just have to update the current subscription and pass the plan id once again and it would resume the subscription.
In PHP you would do:
$customer = Stripe_Customer::retrieve("cus_XXX");
$subscription = $customer->subscriptions->retrieve("sub_YYY");
$subscription->plan = "myPlanId";
$subscription->save();
This is covered in one of Stripe's support article in more details here.
Not sure if the accepted answer is an old one, but for the latest version of Stripe you have to do this (in PHP):
$sub->cancel_at_period_end = false;
return $sub->save();
From the dashboard. Go to your Subscription details and click Edit Details top corner right. A modal untitled "Reactivate subscription" should pop saying
This subscription is set to cancel at the end of the period. You can
reactivate the subscription and it'll continue as usual.
Click Reactivate
EDIT
It seems that the actions now appear on mouse over the remaining days before cancellation:
Note that it wont show if the subscription is already canceled.
As per the above answer from Supersan, this solution also works for C# using the latest Stripe.net library 21.4.1
var items = new [] { new SubscriptionItemUpdateOption() { Id = i.Id, PlanId = i.Plan.Id } }.ToList();
var updateOptions = new SubscriptionUpdateOptions { Items = updatedItems, CancelAtPeriodEnd = false };
await subscriptionService.UpdateAsync(externalSubscriptionId, updateOptions);
Where I've just set the Id and Plan Id to whatever they were before. Not sure if these are necessary if you aren't using multiple plan subscriptions.
Related
in Stripe version 2020-03-02 I was able to retrieve a subscription and its associated credit_card in one go like this:
stripe_subscription = Stripe::Subscription.retrieve({:id => stripe_subscription_id, :expand => [:customer]})
stripe_customer = stripe_subscription.customer
stripe_credit_card = stripe_customer.sources.data.first
In version 2020-08-27 this seems no longer possible since Stripe won't recognise the sources attribute on customer.
So how can I retrieve a subscription and its credit card with one request only?
Since sources on Customer is not included by default, you have to explicitly include it when you expand the related properties. Your code would look like this:
stripe_subscription = Stripe::Subscription.retrieve({
id: stripe_subscription_id,
expand: ['customer.sources'],
})
stripe_customer = stripe_subscription.customer
stripe_credit_card = stripe_customer.sources.data.first
The expand feature is quite powerful and lets you expand multiple separate properties or chain expansion like we did above. I recommend reading the detailed documentation that Stripe shipped.
Is there any already-programmed method to get the last correctly-paid order for a given subscription?
$subscription->get_last_order() will return the last associated order, no matter if that order involved a correct-payment or not.
$subscription->get_related_orders() will return the whole list of orders, and the list can include pending-payment or failed orders.
I think if you wrap / trigger $subscription->get_last_order() with the woocommerce_subscription_payment_complete action (https://docs.woocommerce.com/document/subscriptions/develop/action-reference/) you would essentially achieve that objective. That hook fires both for initial subscription orders and renewal orders and will ensure the $last_order is paid for. Something like this:
add_action( 'woocommerce_subscription_payment_complete', 'set_last_order' );
function set_last_order( $subscription ) {
$last_order = $subscription->get_last_order( 'all', 'any' );
// If you want to be able to reference that $last_order at any time
// then you could just save/update that order ID to post meta so
// that you can grab it any time outside of the action.
}
}
I know that seems a little clunky, but it's the best way I can think of. The only other option that comes to mind would be to loop through $subscription->get_related_orders() checking is_paid() from high IDs to low IDs and grabbing the first one from there.
Im trying to create a customer deposit record in Netsuite using suitescript 1.0.
The original code I had in place which had been working perfectly up until the 2016.2 release broke it.
The update broke it, in that it would override the value submitted in the payment field and instantly make it the full amount of the sales order from the sales order ID. Which is not what we need it to do.
Original Code
function createDeposit(request,response)
{
var record = nlapiCreateRecord('customerdeposit');
record.setFieldValue('salesorder','1260');
record.setFieldValue('customer','1170');
record.setFieldValue('payment','100');
record.setFieldValue('account','2');
record.setFieldValue('memo','this is a test');
deposit = nlapiSubmitRecord(record,true,false);
response.write(deposit);
}
After a reply on the Netsuite user group prompted me to use the {recordmode:'dynamic'} attributes I am getting a strange error..
Test Replacement Function which doesnt work
function createDeposit(request,response)
{
var record = nlapiCreateRecord('customerdeposit',{recordmode:'dynamic'});
record.setFieldValue('salesorder','1260');
record.setFieldValue('customer','1170');
record.setFieldValue('payment','100');
record.setFieldValue('account','2');
record.setFieldValue('memo','this is a test');
deposit = nlapiSubmitRecord(record,true,false);
response.write(deposit);
}
The error message Im getting now is
Invalid salesorder reference key 1260 for customer .
The thing I dont get is how it is now considered NULL, when the value is hardcoded into this test script after I apply the {recordmode:'dynamic'} value.
Ive tried a wide variety of things, but as I dont have Netsuite support, its proving to be something I simply cant figure out.
Any hints, suggestions would be greatly appreciated as Ive been on this for several days
When you use dynamic the order you set fields makes a difference. So when you set the sales order prior to setting the customer you are actually getting the error message "Invalid salesorder reference key 1260 for customer blank"
What I do is create the customer deposit like:
var depRec = nlapiCreateRecord('customerdeposit', {entity:soRec.getFieldValue('entity'), salesorder:soId});
also setting the undeposited funds flag seems to be required (but not always for some reason) so since you are supplying an account id also do this:
depRec.setFieldValue('undepfunds', 'F');
So I would want below functionality;
Connect to GMAIL for Business using service account (Already DONE)
Get emails from gmail (Got some API)
Connect to office 365 using oAuth access token (Will be done, I think no issues in it)
Copy the gmail message to office 365 message.
How can I do it?
Here is the code done so far to download message from Google;
Console.WriteLine("Connect to Google API");
Console.WriteLine("=====================");
String serviceAccountEmail = "3512650851-4tpr9073rju4deqtfjp210j07q52hu2j#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"My Project-d3e5dda28438.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = "<UserEmail for which to download message>",
Scopes = new[] { GmailService.Scope.GmailCompose, GmailService.Scope.GmailModify }
}.FromCertificate(certificate));
var gmailservice = new Google.Apis.Gmail.v1.GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyNewProject",
});
try
{
ListMessagesResponse messages = gmailservice.Users.Messages.List("<User Email>").Execute();
IList<Google.Apis.Gmail.v1.Data.Thread> threads = gmailservice.Users.Threads.List("<User Email>").Execute().Threads;
List<Message> responsemessages = new List<Message>();
responsemessages.AddRange(messages.Messages);
foreach(Message msg in responsemessages)
{
Google.Apis.Gmail.v1.UsersResource.MessagesResource.GetRequest gr = gmailservice.Users.Messages.Get("<User Email>", msg.Id);
gr.Format = Google.Apis.Gmail.v1.UsersResource.MessagesResource.GetRequest.FormatEnum.Full;
Message m = gr.Execute();
if (gr.Format == Google.Apis.Gmail.v1.UsersResource.MessagesResource.GetRequest.FormatEnum.Raw)
{
byte[] decodedByte = FromBase64ForUrlString(m.Raw);
string base64Encoded = Convert.ToString(decodedByte);
MailMessage msg2 = new MailMessage();
//msg2.LoadMessage(decodedByte);
}
}
}
catch (Exception ex) { }
Note: The code is very rough for now. Will make it more formal later..
So basically the question is, How can I upload the message row format to office 365 or is there any COPY api?
I am not aware of any C# library/API that handles email synchronization, but maybe Google finds you something.
If not you will have to 'roll your own'. We are doing exactly that with calendar synchronization (in Delphi). The steps to take are:
[Note that I am answering for full synchronization as your question title says]
Analyze the email formats for both systems in detail. Set up data/storage structures that can handle all formats and their differences. You may have to resort to using 'extended/user defined/custom' properties to store properties of system Y in system X, when not present there. You will surely have to use custom properties for storing typical synchronization data: date of last synchronization, date of last change, maybe mutual IDs*
Read emails from both systems over a certain 'synchronization period'.
Do your own comparison looking for differences (added, deleted, modified emails)
Your comparison may have to take configuration/settings into account like Do we synchronize both ways?, When an email is modified on both sides, which one takes precedence?. That's not really necessary, you can define sensible defaults for that. Many synchronisation systems do, they don't ask your for any configuration, but then the user sometimes has to figure out Huh, why did it update this way?).
Write modifications to the external systems.
No small task, I can tell you, so I doubt it fits your your requirement pragmatically ;-) So first invest heavily in searching if someone has already done this.
(And note that asking for libraries is off-topic in SO)
* You will even have to store 'my own ID' as a custom property for each email. If you don't do that you can't distinguish emails that were copied in the external system.
In CRM 2011, under Account, there is the ability to add Connection. After clicking add Connection, you can browse/search for Name which defaults to "Contact". Is there a way to switch "Contact" to "Account" by default without having to switch the select box?
Apparently is just doing this:
document.getElementById("record2id").setAttribute("defaulttype", "1");
But i do a little search and this not work for the dialog of connections, check this alternative.
This doesn't work with connections.
With connections the object type code for the lookup is set in the Mscrm.Connection.preSelectObjectType function in Microsoft Dynamics CRM\CRMWeb_static\entities\connection.js.
There is a line like
$v_2.set_defaultType($v_3);
where the object type is set. $v_3 is set depending on the chosen role.
So you need to change it to
$v_2.set_defaultType(Mscrm.EntityTypeCode.Account.toString());
But you will lose the role based lookup configuration, so you might want to modify that. Plus it is unsupported and you will need to take into account the updating behavior when installing new rollups that change the connection.js (i.e. copy newer connection.js files by hand from an updated system, and customize them again).
Here are two approaches. Both works, but the first adds the type record Icon to the loockup field even if it empy. The second doesn't do that but a little bit more risky as it depends on the internal method names.
1st method:
if (IsNull(Xrm.Page.getAttribute('record2id').getValue())) {$("#record2id")[0].DataValue = [{ "type": scrm.EntityTypeCode.SystemUser.toString() }];}
2nd Method
document.original_preSelectObjectType = Mscrm.Connection.preSelectObjectType;
Mscrm.Connection.preSelectObjectType = function (roleLookup, peerRoleLookup) {
if (IsNull(roleLookup.DataValue) && IsNull(peerRoleLookup.DataValue) && !window.event.srcElement.DataValue) {
var $v_0 = window.event.srcElement;
$v_0.defaulttype = Mscrm.EntityTypeCode.SystemUser.toString();
$v_0.DefaultViewId = "";
$v_0.Lookup(true, false, null, false);
}
else {
document.original_preSelectObjectType(roleLookup, peerRoleLookup); }}