Sunday, November 19, 2023

ExpressVPN STEAL my credit card info to renew subscription

Yes, it is ExpressVPN, one of top VPN in the world.

And it ExpressVPN steal my card info to renew subscription, let's see how it happen.

In short:
1. 2022/8/18, I purchased 1 year + 3 months plan($99.95) with credit card, Subscription ID 948*

2. The credit card expired in 2023.

3. 2023/11/17, my ExpressVPN plan expired.

4. It's Black Friday time, I compared few VPNs, give ExpressVPN another try, use new credit card to purchase 1 year + 3 months plan($99.95) again, Subscription ID 1039*

5. 2023/11/18, ExpressVPN use my new credit card info to renewing Subscription ID 948*, charged US$116.95.

I have to say, it was STEAL, ExpressVPN steal my credit card info that for new subscription to renew another subscription without asking.

Ok let's see the detail.

You can see, my first plan purchased at 2022/8/18, Subscription ID 948*.

And my credit card used for Subscription ID 948* was expired, ExpressVPN sent a email notice me, but I didn't renew card info because ExpressVPN increased the price.

Time moved to 2023/11/17, my ExpressVPN goes offline, after check their web I know my plan was expired.
After doing some research, I decide give ExpressVPN another try, because I am lazy to uninstall and install new VPN software.
So I purchase the Black Friday deal, 1 year + 3 months plan for $99.95 with my new credit card, Subscription ID 1039*.

The next day, 2023/11/18, I received an email said my ExpressVPN has renewing.

It was weird, so I checked ExpressVPN's website to figure out what happed.

I have two subscription ID, start with 948 (old one) and 1039(new one).

Subscription ID 948*  : expired at 2023/11/17, credit card expired so it can't renew.
Subscription ID 1039*: purchased at 2023/11/17 with new credit card.

Next day, I guess ExpressVPN system detect I have a working credit card, so it renew my old subscription without asking, using the new credit card info.

Subscription ID 948*  : renew at 2023/11/18 with new credit card info.

That's why I will say ExpressVPN steal my card info to renew subscription, ExpressVPN using my credit card to process unauthorized payment.

Finally, beware ExpressVPN didn't notice you they will store your credit card information, it only said "Your payment information is fully protected.".

Yea, not even in their TOS, I checked ExpressVPN Terms of Service at 2023/11/19, it didn't mention it will store/save your credit card info. 

ExpressVPN is not bad, speed is ok, response time sometimes better than without VPN, I can play LOL with ExpressVPN and have lower ping, but I can't trust a company who steal your credit card info.

I had ask customer service to refund my 2 subscriptions, hope it working or I have to issue VISA for suspicious transactions. 

Wednesday, January 11, 2023


很久沒寫文章了,看到PTT上有篇標題叫做「[問卦] PTT投降仔都沒想過台灣打贏戰爭嗎?」,回了之後想說別浪費,就潤一下搬到blog來。

Wednesday, June 22, 2022

[.NET 6] Custom Converter for System.Text.Json

Some people asked a question about custom converter for System.Text.Json on Facebook .NET group.
He want user get the specific format from DateTimeOffset (not DateTime)

DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fffzzz");
//"2022-06-22 06:28:55.207+00:00"

If using default converter, the result will follow ISO 8601 format

return DateTimeOffset.UtcNow;

And the don't want to use ToString() to achieve it everytime, so the best way is add the custom converter to JsonOptions

Monday, January 3, 2022

WSL2 - Temporary failure in name resolution

WSL2 is good thing, but dunno why some bug it never fixed.

If you are first time install WSL2 and reboot or something like it, it may become no internet connectivity and show error
Temporary failure in name resolution

To fix it, please follow this gitgub issue, JohnnyQuest1983's reply.

The steps can be simple like this
  1. set generateResolvConf=false in /etc/wsl.conf
  2. set dns in /etc/resolv.conf

then use exit or wsl --shutdown, reboot wsl, and it worked, the internet back.

Thursday, October 14, 2021

[.NET 6] Implement custom LogProvider with RabbitMQ into ILogger

In .NET 6, ILogger is very easy to use, but on modern project, we will write log to somewhere else and use log viewer to read it. For example: sent log to RabbitMQ and read on Kibana

Here is a demo to show how to lmplement a custom logging provider with RabbitMQ into ILogger in .NET 6 RC1, basically same as this article but with some modify.

Step 0 : Before Start

In this demo we choose RabbitMQ.Client to communite with RabbitMQ, you can choose other client if you want, just install it from nuget, easy.

Another thing is RabbitMQ server, if you don't have one, you can use docker to create one.

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management

Tuesday, September 28, 2021

[.NET 6] System.StackOverflowException after upgrade VS2022 to Preview 4.1 from 3.1

After upgrade VS2022 to Preview 4.1 from 3.1, my project throw StackOverflowException (after fix lots of reference error)
  Message=Exception of type 'System.StackOverflowException' was thrown.

Stack overflow.
Repeat 2291 times:
   at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1[[System.__Canon, System.Private.CoreLib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnChangeTokenFired()
   at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1+<>c[[System.__Canon, System.Private.CoreLib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].<RegisterChangeTokenCallback>b__7_0(System.Object)
   at System.Threading.CancellationTokenSource.Invoke(System.Delegate, System.Object, System.Threading.CancellationTokenSource)
   at System.Threading.CancellationTokenSource.Register(System.Delegate, System.Object, System.Threading.SynchronizationContext, System.Threading.ExecutionContext)
   at System.Threading.CancellationToken.Register(System.Delegate, System.Object, Boolean, Boolean)
   at System.Threading.CancellationToken.Register(System.Action`1<System.Object>, System.Object)
   at Microsoft.Extensions.Configuration.ConfigurationReloadToken.RegisterChangeCallback(System.Action`1<System.Object>, System.Object)
   at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1[[System.__Canon, System.Private.CoreLib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].RegisterChangeTokenCallback(Microsoft.Extensions.Primitives.IChangeToken)
   at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1[[System.__Canon, System.Private.CoreLib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnChangeTokenFired()
   at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1+<>c[[System.__Canon, System.Private.CoreLib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].<RegisterChangeTokenCallback>b__7_0(System.Object)
   at System.Threading.CancellationTokenSource.Invoke(System.Delegate, System.Object, System.Threading.CancellationTokenSource)
   at System.Threading.CancellationTokenSource+CallbackNode+<>c.<ExecuteCallback>b__9_0(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Threading.CancellationTokenSource+CallbackNode.ExecuteCallback()
   at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean)
   at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean)
   at System.Threading.CancellationTokenSource.Cancel()
   at Microsoft.Extensions.Configuration.ConfigurationManager.RaiseChanged()
   at Microsoft.Extensions.Configuration.ConfigurationManager.AddSource(Microsoft.Extensions.Configuration.IConfigurationSource)
   at Microsoft.Extensions.Configuration.ConfigurationManager.Microsoft.Extensions.Configuration.IConfigurationBuilder.Add(Microsoft.Extensions.Configuration.IConfigurationSource)
   at Microsoft.Extensions.Configuration.ChainedBuilderExtensions.AddConfiguration(Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfiguration, Boolean)
   at Microsoft.Extensions.Configuration.ChainedBuilderExtensions.AddConfiguration(Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfiguration)
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<Main>$(System.String[])

I know post to MS's developer forum is useless, and VS2022 can't downgrade now, so the only way is try and error by myself.

Turn out, the stackoverflow exception cause by Microsoft.Extensions.Configuration from this code:
After more dig-in, I found the framework already called this on Hosting , so maybe the duplicate call cause the stackoverflow exception, but still not sure why it worked on VS2022 preview 3.1, not work on preview 4.1.


Remove the line
(or similar) on your Program.cs

Wednesday, September 22, 2021

[JAVA] HMAC SHA384 example code

It was a bit hard to find correct HMAC SHA384 code for JAVA, so here to share my example code, hope can save some time to try and error.

import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;

public class SecureUtils {

private static String bytesToHex(final byte[] hash) {
    final StringBuffer hexString = new StringBuffer();
    for (int i = 0; i < hash.length; i++) {
        final String hex = Integer.toHexString(0xff & hash[i]);
        if (hex.length() == 1) {
    return hexString.toString();

public static void main(String[] args) throws NoSuchAlgorithmException {
    final String nonce = "test"; //your key
    final String message = "Password"; //your plain txt
    SecretKeySpec signingKey = new SecretKeySpec(nonce.getBytes(),"HmacSHA384");
    final Mac mac = Mac.getInstance("HmacSHA384");
        catch(Exception ex){    }

You can test this example in here 

Check result with online hmac generator

Those code modify from stackoverflow HMAC SHA-384 in Java

Sunday, January 31, 2021

[敗家] Alienware m17 R3

Alienware m17 R3

上一台筆電是2013年買的Alienware M14x R2,拿來寫code已經有點慢了,最近終於找了機會買了新筆電,17吋螢幕真的比較適用啊。




Sunday, November 8, 2020

[.NET Core] 400 Bad Request / 'MS-ASPNETCORE-TOKEN' does not match the expected pairing token

At this time, I am testing an old code build by .NET Core 2.0, when I tring to send request to the service, I always got 400 Bad Request, but I can know the service got request because I am running debug mode, it shows request coming and exception happend.

Since I can't find out the error just by 400 bad request, I open "Application Insights Search" in Visual Studio and found this error.

After some digging on stackoverflow, I found the error cause by a old version bug.

This question "Cannot debug in Visual Studio after changing the port number?" 's answer has good explain about this error.

You are getting this error message because Kestrel expected to run behind IIS, and received a direct request. And it noticed that because IIS was not there to inject the MS-ASPNETCORE-TOKEN header. --Gerardo Grignoli

Here have some options to solve the problem:

  1. Call UseUrls() before UseIISIntegration() 
  2. Use Kestrel to debug, not IIS Express 
  3. Upgrade the framework

That's all,

Sunday, November 1, 2020

[.NET core] Localization with Enumeration

In .NET core, doing i18n is more easy than .NET Framework in my opinions, just give appropriate setting, then you can easily assign i18n string to anyplace you want.

Let's see how it work.

1.Add resource file 

Usually I will put my resource file in [Resources] folder, remember put cultureInfo name on resx file like this.

2.Setup Startup.cs

Add following code (depending how many language you support) into your IServiceCollection
//set Support Cultures and default
services.Configure(options =>
    var supportedCultures = new[]{
        new CultureInfo("en-US"),
        new CultureInfo("zh-TW"),
        new CultureInfo("ja-JP")
    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
//set resource path
services.AddLocalization(options => options.ResourcesPath = "Resources");

Make sure your ResourcesPath set to right path.

3.DI Localizer

Since we are using .NET Core, so we need to DI our Localizer before we using it, for example if you are use it on a controller.
private readonly IStringLocalizer _localizer;
public YourController(IStringLocalizerFactory factory)
   _localizer = factory.Create("Message", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);

The "Message" is my resource name, you can DI multi Localizer if you have not only one resource group.


Here is the content of my resource, the usage for Localizer is easy, just use it like a dictionary.
var str = _localizer["Cool"];

If you wanna get a localization string for enum, it was easy too, for example your enum called "WeatherSummary".
var str = _localizer[WeatherSummary.Cool.ToString()];

It was very simple, don't need add custom thing like LocalizedDescriptionAttribute for enum i18n in .NET Framework.

You can check my demo code on github .NET-Core-Localization-and-Enum

Hope you enjoy it.