業務でNetSuiteのWebServiceをC#に追加することになったけど、英語のドキュメントばかりで大変だったので記録。
コードはコピペと手打ちなので、ミスがあればご指摘いただければと思います。
前提
- 認証方法はTBA(Token-Based Authentication)方式
- 認証に必要なAccount, Token id, Token secret, Consumer key, Consumer secretの発行などは正しくされているとする
- C#は.NET Framework 4.7.2
1. Postmanを使って確認
Postmanを使ってAPI叩けるかを確認します。
認証用のtokenとかが正しいかを確認できます。
1-1. サンプルアプリをダウンロード
以下のURLのAccountIDを入力し、サンプルアプリをダウンロードします。
https://<accountID>.app.netsuite.com/app/external/integration/integrationDownloadPage.nl
1-2. Postmanにサンプルアプリをインポート
Postmanはインストールしなくても使えるので、ブラウザで進めます。
左上の「import」からインポートできます。
インポートできると「NetSuite REST API Tutorial」のコレクションが作成されます。
1-3. 認証情報を追加
追加された「NetSuite REST API Tutorial」をクリックし、Authorizationを編集します。
画像と同じものを選択しつつ、Account, Token id, Token secret, Consumer key, Consumer secretを設定していきます。
オレンジ色の「{{xxx}}」は変数です。
「Variables」タブに切り替えれば、変数名の宣言と値の設定が可能です。
1-4. TBA認証で通過できるかを確認する
「0 Test」の「Example 0.1: Test Request」を開きます。
「REST_SERVISE」変数に「https://.suitetalk.api.netsuite.com/services/rest」を入れ、Sendを押します。
200番台のレスポンスが返ってくれば認証は通過できています。
2. .NETのサンプルアプリを実行
公式が用意している.NET用のサンプルアプリをダウンロードして、実行してみます。
2-1. .NETのサンプルアプリをダウンロードする
以下のサイトからサンプルアプリをダウンロードします。
リンクをクリックしてもダウンロードできないので、リンクをコピーしてから、ブラウザのアドレスバーに直接貼り付けてください。
2-2. サンプルアプリを修正する
サンプルアプリを実行するため、2つのファイルを修正します。
// 省略
<appSettings>
<!-- If set to false, login parameter values will be used from below. If set
to true, the user will be prompted for values. -->
<add key="promptForLogin" value="false"/>
<!-- Login field values -->
<add key="login.email" value="EMAIL"/>
<add key="login.password" value="PASSWORD"/>
<add key="login.roleNSkey" value="3"/>
<add key="login.acct" value="ACCOUNT"/>
<add key="login.appId" value="8CA80836-4422-4A14-B91D-B386AE9815FD"/>
<add key="login.useTba" value="false"/>
<add key="login.tbaConsumerKey" value="CONSUMERKEY"/>
<add key="login.tbaConsumerSecret" value="CONSUMERSECRET"/>
<add key="login.tbaTokenId" value="TOKENID"/>
<add key="login.tbaTokenSecret" value="TOKENSECRET"/>
</appSettings>
// 省略
login.useTbaをtrueにし、ACCOUNT, CONSUMERKEY, CONSUMERSECRET, TOKENID, TOKENSECRETを指定します。
TBA認証を使うので、その他の項目は変更しなくて大丈夫です。
namespace NSClient
{
public class NSClient
{
// 省略
private TokenPassportSignature ComputeSignature(string compId, string consumerKey, string consumerSecret,
string tokenId, string tokenSecret, string nonce, long timestamp)
{
string baseString = compId + "&" + consumerKey + "&" + tokenId + "&" + nonce + "&" + timestamp;
string key = consumerSecret + "&" + tokenSecret;
string signature = "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyBytes = encoding.GetBytes(key);
byte[] baseStringBytes = encoding.GetBytes(baseString);
// HMAC-SHA1 -> HMAC-SHA256 に変更
using (var hmacSha256 = new HMACSHA256(keyBytes))
{
byte[] hashBaseString = hmacSha1.ComputeHash(baseStringBytes);
signature = Convert.ToBase64String(hashBaseString);
}
TokenPassportSignature sign = new TokenPassportSignature();
// ここも HMAC-SHA1 -> HMAC-SHA256 に変更
sign.algorithm = "HMAC-SHA256";
sign.Value = signature;
return sign;
}
// 省略
}
}
2-3. サンプルアプリを実行する
VS 2022を管理者として実行し、NSClient.slnを開き、実行してみます。
カスタマ取得をしてみます。
Internal IDが一致すると、画像のように値を取得できます。
3. C#アプリにNetSuiteのWebServiceを追加
サンプルアプリを参考に、C#アプリに必要な処理を移植していきます。
3-1. サービス参照からWebServiceを追加する
ソリューションを右クリックし、「追加 > サービス参照」をクリックします。
次に左下の「詳細設計」をクリック。
「Web参照の追加」をクリック。
URLに「https://webservices.netsuite.com/wsdl/v2021_2_0/netsuite.wsdl 」を入力し、「参照の追加」をクリックして追加します。
次にTBA方式の認証をするにあたって必要なコードをサンプルアプリから移植。
とりあえずNSClient.csファイルを作成します。
using xxx.com.netsuite.WebService;
// 省略
namespace xxx.yyy
{
public class NSClient
{
private NetSuiteService _service;
public NetSuiteService Service {
get
{
_service.tokenPassport = CreateTokenPassport();
return _service;
}
}
public bool IsAuthenticated { get; private set; }
/// <summary>
/// Constructor
/// </summary>
public NSClient()
{
IsAuthenticated = false;
_service = new DataCenterAwareNetSuiteService();
_service.Timeout = 1000 * 60 * 60 * 2;
}
public TokenPassport CreateTokenPassport()
{
string account = "Your Account";
string consumerKey = "Your ConsumerKey";
string consumerSecret = "Your ConsumerSecret";
string tokenId = "Your TokenId";
string tokenSecret = "Your TokenSecret";
string nonce = ComputeNonce();
long timestamp = ComputeTimestamp();
TokenPassportSignature signature = ComputeSignature(account, consumerKey, consumerSecret, tokenId, tokenSecret, nonce, timestamp);
TokenPassport tokenPassport = new TokenPassport();
tokenPassport.account = account;
tokenPassport.consumerKey = consumerKey;
tokenPassport.token = tokenId;
tokenPassport.nonce = nonce;
tokenPassport.timestamp = timestamp;
tokenPassport.signature = signature;
return tokenPassport;
}
private string ComputeNonce()
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] data = new byte[20];
rng.GetBytes(data);
int value = Math.Abs(BitConverter.ToInt32(data, 0));
return value.ToString();
}
private long ComputeTimestamp()
{
return ((long) (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
}
private TokenPassportSignature ComputeSignature(string compId, string consumerKey, string consumerSecret,
string tokenId, string tokenSecret, string nonce, long timestamp)
{
string baseString = compId + "&" + consumerKey + "&" + tokenId + "&" + nonce + "&" + timestamp;
string key = consumerSecret + "&" + tokenSecret;
string signature = "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyBytes = encoding.GetBytes(key);
byte[] baseStringBytes = encoding.GetBytes(baseString);
using (var hmacSha256 = new HMACSHA256(keyBytes))
{
byte[] hashBaseString = hmacSha1.ComputeHash(baseStringBytes);
signature = Convert.ToBase64String(hashBaseString);
}
TokenPassportSignature sign = new TokenPassportSignature();
sign.algorithm = "HMAC-SHA256";
sign.Value = signature;
return sign;
}
public String GetStatusDetails(Status status)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i=0; i < status.statusDetail.Length; i++)
{
sb.Append( "[Code=" + status.statusDetail[i].code + "] " + status.statusDetail[i].message + "\n" );
}
return sb.ToString();
}
}
class DataCenterAwareNetSuiteService : NetSuiteService
{
private System.Uri OriginalUri;
public DataCenterAwareNetSuiteService()
: base()
{
OriginalUri = new System.Uri(this.Url);
string account = "Your Account";
DataCenterUrls urls = getDataCenterUrls(account).dataCenterUrls;
Uri dataCenterUri = new Uri(urls.webservicesDomain + OriginalUri.PathAndQuery);
this.Url = dataCenterUri.ToString();
}
}
class NSBase
{
protected static NSClient _client;
public static NSClient Client
{
get
{
if (_client == null)
{
_client = new NSClient();
}
return _client;
}
set
{
_client = value;
}
}
}
// NSEntity.csから移植
class NSEntity : NSBase
{
// internalIdが一致するCustomerを一件取得
public static string GetCustomer(string internalId)
{
RecordRef recordRef = new RecordRef();
recordRef.internalId = internalId;
// recordRef.type:テーブルの指定
recordRef.type = RecordType.customer;
recordRef.typeSpecified = true;
ReadResponse response = Client.Service.get(recordRef);
Customer customer = (Customer)response.record;
ProcessCustomerReadResponse(response, customer);
}
// internalIdが一致するCustomerを全権取得
public static List<string> getCustomerList(string[] internalIds)
{
RecordRef[] recordRef = new RecordRef[internalIds.Length];
for (int i = 0; i < internalIds.Length; i++)
{
RecordRef recordRef = new RecordRef();
recordRef.internalId = internalIds[i];
recordRef.type = RecordType.customer;
recordRef.typeSpecified = true;
}
ReadResponse[] getResponse = Client.Service.getList(recordRefs).readResponse;
var returnStringList = new List<string>();
for (int i = 0; i < getResponse.Length; i++ )
{
returnStringList.Add(ProcessCustomerReadResponse(getResponse[i], internalIds[i]));
}
return returnStringList;
}
private static bool ProcessCustomerReadResponse(ReadResponse response, Customer customer)
{
// Process the response
if (response.status.isSuccess)
{
return (
" internalId=" + customer.internalId +
"\n entityId=" + customer.entityId +
(customer.companyName == null ? "" : ("\n companyName=" + customer.companyName)) +
(customer.entityStatus == null ? "" : ("\n status=" + customer.entityStatus.name)) +
(customer.email == null ? "" : ("\n email=" + customer.email)) +
(customer.phone == null ? "" : ("\n phone=" + customer.phone)) +
"\n isInactive=" + customer.isInactive +
(!customer.dateCreatedSpecified ? "" : ("\n dateCreated=" + customer.dateCreated.ToShortDateString())));
}
else
{
return Client.GetStatusDetails(response.status);
}
}
}
}
あとは「NSEntity.GetCustomer(internalId)」のように呼べばOK
4. その他
4-1. WebServiceのリリース状況について
WebServiceのリリース状況に関しては、documentationタグの値を確認してください。
参考