概述
epiapi是一家专注于企业级即插即用跨境金融服务产品的科技公司,在母公司Wyre的平台基础上,epiapi开发的REST API产品,可以为全球客户提供方便快捷的虚拟银行服务。
我们的api使用HTTPS建立交互,JSON返回错误请求。为了更好地帮助您探索API产品,我们在生产环境之外额外设置了测试环境,任何在测试环境下的转账请求均不会被执行。测试及生产环境下,皆需要api key进行验签。
开始之前
此api标准执行手册乃针对在美国亚马逊平台使用虚拟银行服务进行收款的api产品。按照指引,开发者在通过必要的KYC信息收集后,可以通过api执行创建虚拟银行账户,接收银行账号信息,监测入账,申请提现到国内银行账户等操作。在开始之前,请确保您已就此项业务合作与epiapi公司签署纸质协议,如若未签署协议或有其他问题,请通过 contact@epiapi.com联系我们。
Check the English Version.
开始
1.阅读此文档
如果看到这里,恭喜您已经完成第一步,(记得看完)。
2.注册测试用账户
点击这里注册一个在测试环境下的账户。测试环境下无需上传任何文件。
3.验证你的账户
通过support@epiapi.com联系我们,我们可以为您验证测试账户并添加测试用款项。
4.寻求帮助
在账户验证通过后,我们将发送给您Slack或其他聊天工具(如钉钉、QQ)的加入邀请链接,方便您随时联系我们。
5.注册生产环境账户
在所有测试通过后,您可以点击这里创建一个真实账户。生产环境下,您必须填写真实信息并上传需要的文件才能通过账户验证。
6.上线!
完成所有步骤,可以上线啦!
概述
支持国家(持续更新)
我们的虚拟银行服务目前支持通过ACH接收美元到美国银行账户。
国家
- 美国
- 欧洲(即将上线)
- 澳大利亚(即将上线)
epiapi一直积极地在全球开展业务,如有更新,我们将以邮件形式通知您。
货币
- 美元USD
- 欧元EUR(即将上线)
- 英镑GBP(即将上线)
- 澳元AUD(即将上线)
请求方法
我们提供REST风格的api,以JSON响应。
调试时请留意,若您收到的异常响应不是以上格式,请检查您使用的服务器。
For successful API calls:
<!--成功的API请求示例如下-->:
{
"parameter": "en",
"parameter": "ABCDEF"
}
For unsuccessful API calls:
<!--失败的API请求示例如下-->:
{
"language":"en",
"exceptionId":"ABCDEF",
"compositeType":"",
"subType":"",
"message":"Error Message",
"type":"ErrorTypeException",
"transient":false
}
生产/测试环境下的终端节点
我们使用生产、测试两种环境,testwyre
用于测试环境,sendwyre
用于生产环境。
环境 | 终端节点 |
---|---|
测试 | https://api.testwyre.com |
生产 | https://api.sendwyre.com |
分页机制
我们将返回数设定为25项/页,在任何请求中, 您都可以通过使用下面的参数进行调整。
参数 | 描述 |
---|---|
offset | 返回记录开始位置 (默认: 0). |
limit | 每页返回记录数量 (默认: 25). |
from | 查询范围内首条记录的创建时间,UNIX毫秒单位。(默认:0) |
to | 查询范围内首条记录的创建时间,UNIX毫秒单位。(默认:现在时间) |
验签
require 'uri'
require 'net/http'
require 'digest/hmac'
require 'json'
class WyreApi
ACCOUNT_ID = 'YOUR_ACCOUNT_ID_HERE'
API_KEY = 'YOUR_API_KEY_HERE'
SEC_KEY = 'YOUR_SECRET_KEY_HERE'
API_URL = 'https://api.testwyre.com'
def create_transfer options
api_post '/transfers', options
end
private
def api_post path, post_data = {}
params = {
'timestamp' => (Time.now.to_i * 1000).to_s
}
url = API_URL + path + '?' + URI.encode_www_form(params)
headers = {
'X-Api-Key' => API_KEY,
'X-Api-Signature' => calc_auth_sig_hash(url + post_data.to_json.to_s),
'X-Api-Version' => '2'
}
uri = URI API_URL
Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
http.request_post(url, post_data.to_json.to_s, headers) do |res|
response = JSON.parse res.body
raise response['message'] if res.code != '200'
return response
end
end
end
def calc_auth_sig_hash url_body
return Digest::HMAC.hexdigest url_body, SEC_KEY, Digest::SHA256
end
end
api = WyreApi.new
api.create_transfer({'sourceAmount'=>50,'sourceCurrency'=>'USD','dest'=>'richard@epiapi.com', 'destCurrency'=>'USD', 'message'=>'buy Richard pizza')
#dependencies:
#python3
#pip3 install requests
import json
import hmac
import time
from requests import request
class MassPay_API(object):
def __init__(self, account_id, api_version, api_key, api_secret):
self.account_id = account_id
self.api_url = 'https://api.testwyre.com/{}'.format(api_version)
self.api_version = api_version
self.api_key = api_key
self.api_secret = api_secret
#authentication decorator. May raise ValueError if no json content is returned
def authenticate_request(func):
def wrap(self, *args, **kwargs):
url, method, body = func(self, *args, **kwargs)
params = {}
timestamp = int(time.time() * 1000)
url += '?timestamp={}'.format(timestamp)
bodyJson = json.dumps(body) if body != '' else ''
headers = {}
headers['Content-Type'] = 'application/json'
headers['X-Api-Version'] = self.api_version
headers['X-Api-Key'] = self.api_key
headers['X-Api-Signature'] = hmac.new(self.api_secret.encode('utf-8'), (url + bodyJson).encode('utf-8'), 'SHA256').hexdigest()
print(headers['X-Api-Signature'])
resp = request(method=method, url=url, params=params, data=(json.dumps(body) if body != '' else None), json=None, headers=headers)
if resp.text is not None: #Wyre will always try to give an err body
return resp.status_code, resp.json()
return 404, {}
return wrap
@authenticate_request
def retrieve_exchange_rates(self):
url = self.api_url + '/rates'
method = 'GET'
body = ''
return url, method, body
@authenticate_request
def retrieve_account(self):
url = self.api_url + '/account'
method = 'GET'
body = ''
return url, method, body
@authenticate_request
def create_transfer(self, sourceAmount, sourceCurrency, destAmount, destCurrency, destAddress, message, autoConfirm):
url = self.api_url + '/transfers'
method = 'POST'
#ONLY use either sourceAmount or destAmount, see documentation
body = {'sourceCurrency':sourceCurrency,
'dest':destAddress,
'destCurrency':destCurrency,
'message':message}
if sourceAmount:
body["sourceAmount"] = sourceAmount
elif destAmount:
body["destAmount"] = destAmount
if autoConfirm:
body['autoConfirm'] = True
return url, method, body
@authenticate_request
def confirm_transfer(self, transfer_id):
url = self.api_url + '/transfer/{}/confirm'.format(transfer_id)
method = 'POST'
body = ''
return url, method, body
@authenticate_request
def status_transfer(self, transfer_id):
url = self.api_url + '/transfer/{}'.format(transfer_id)
method = 'GET'
body = ''
return url, method, body
#USAGE Example
account_id = "YOUR_ACCOUNT_ID_HERE" #optional
api_key = "YOUR_API_KEY_HERE"
secret_key = "YOUR_SECRET_KEY_HERE"
api_version = "2"
#create Wyre MassPay API object
Wyre = MassPay_API(account_id, api_version, api_key, secret_key)
#get account info
http_code, account = Wyre.retrieve_account()
print(account)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.Integer;
import java.lang.String;
import java.lang.StringBuffer;
import java.net.HttpURLConnection;
import java.net.URL;
public class TestAuth {
public static void main(String[] args) {
String apiKey = "YOUR_API_KEY_HERE";
String secretKey = "YOUR_SECRET_KEY_HERE";
String url = "https://api.testwyre.com/account";
String method = "GET";
String data = "";
String result = executeWyreRequest(url, "", method, apiKey, secretKey);
System.out.println(result);
url = "https://api.testwyre.com/transfers";
method = "POST";
data = "{" +
" \"dest\": \"richard@epiaapi.com\"," +
" \"destCurrency\": \"USD\"," +
" \"sourceCurrency\" : \"USD\"," +
" \"sourceAmount\" : \"50\"," +
" \"message\": \"buy Richard pizza\"" +
"}";
result = executeWyreRequest(url, data, method, apiKey, secretKey);
System.out.println(result);
}
public static String executeWyreRequest(String targetURL, String requestBody, String method, String apiKey, String secretKey) {
URL url;
HttpURLConnection connection = null;
try {
targetURL += ((targetURL.indexOf("?")>0)?"&":"?") + "timestamp=" + System.currentTimeMillis();
//Create connection
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
System.out.println(connection.getRequestMethod());
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Length", Integer.toString(requestBody.getBytes().length));
//Specify API v2
connection.setRequestProperty("X-Api-Version","2");
// Provide API key and signature
connection.setRequestProperty("X-Api-Key", apiKey);
connection.setRequestProperty("X-Api-Signature",computeSignature(secretKey,targetURL,requestBody));
//Send request
if(method.equals("POST")) {
connection.setDoOutput(true);
connection.setRequestMethod(method);
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.write(requestBody.getBytes("UTF-8"));
wr.flush();
wr.close();
}
//Get Response
InputStream is;
if (connection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
is = connection.getInputStream();
} else {
is = connection.getErrorStream();
}
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if(connection != null) {
connection.disconnect();
}
}
}
public static String computeSignature(String secretKey, String url, String reqData) {
String data = url + reqData;
System.out.println(data);
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256Hmac.init(key);
byte[] macData = sha256Hmac.doFinal(data.getBytes("UTF-8"));
String result = "";
for (final byte element : macData){
result += Integer.toString((element & 0xff) + 0x100, 16).substring(1);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
<?php
function make_authenticated_request($endpoint, $method, $body) {
$url = 'https://api.testwyre.com';
$api_key = "YOUR_API_KEY_HERE";
$secret_key = "YOUR_SECRET_KEY_HERE";
$timestamp = floor(microtime(true)*1000);
$request_url = $url . $endpoint;
if(strpos($request_url,"?"))
$request_url .= '×tamp=' . $timestamp;
else
$request_url .= '?timestamp=' . $timestamp;
if(!empty($body))
$body = json_encode($body, JSON_FORCE_OBJECT);
else
$body = '';
$headers = array(
"Content-Type: application/json",
"X-Api-Key: ". $api_key,
"X-Api-Signature: ". calc_auth_sig_hash($secret_key, $request_url . $body),
"X-Api-Version: 2"
);
$curl = curl_init();
if($method=="POST"){
$options = array(
CURLOPT_URL => $request_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true);
}else {
$options = array(
CURLOPT_URL => $request_url,
CURLOPT_RETURNTRANSFER => true);
}
curl_setopt_array($curl, $options);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($curl);
curl_close($curl);
var_dump($result);
return json_decode($result, true);
}
function calc_auth_sig_hash($seckey, $val) {
$hash = hash_hmac('sha256', $val, $seckey);
return $hash;
}
echo make_authenticated_request("/account", "GET", array());
$transfer = array(
"sourceCurrency"=>"USD",
"dest"=>"richard@epiapi.com",
"sourceAmount"=> 50,
"destCurrency"=>"USD",
"amountIncludesFees"=>True
"message"=> "buy Richard pizza"
);
echo make_authenticated_request("/transfers", "POST", $transfer);
?>
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace testauthwyre
{
class MainClass
{
public static void Main(string[] args)
{
WyreApi wyre = new WyreApi();
Console.WriteLine(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
Console.WriteLine((long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds);
HttpWebResponse accountResponse = wyre.Get("/account");
Console.WriteLine(GetResponseBody(accountResponse));
Dictionary<string, object> body = new Dictionary<string, object>();
body.Add("sourceCurrency", "USD");
body.Add("sourceAmount", "50");
body.Add("dest", "richard@epiapi.com");
HttpWebResponse transferResponse = wyre.Post("/transfers", body);
Console.WriteLine(GetResponseBody(transferResponse));
}
private static string GetResponseBody(HttpWebResponse response)
{
return JObject.Parse(new StreamReader(response.GetResponseStream()).ReadToEnd()).ToString(Formatting.Indented);
}
}
public class WyreApi
{
private const String domain = "https://api.testwyre.com";
private const String apiKey = "YOUR_API_KEY_HERE";
private const String secKey = "YOUR_SECRET_KEY_HERE";
public HttpWebResponse Get(string path)
{
return Get(path, new Dictionary<string, object>());
}
public HttpWebResponse Get(string path, Dictionary<string, object> queryParams)
{
return Request("GET", path, queryParams);
}
public HttpWebResponse Post(string path, Dictionary<string, object> body)
{
return Request("POST", path, body);
}
private HttpWebResponse Request(string method, string path, Dictionary<string, object> body)
{
Dictionary<string, object> queryParams = new Dictionary<string, object>();
if (method.Equals("GET"))
queryParams = body;
queryParams.Add("timestamp", GetTimestamp());
string queryString = queryParams.Aggregate("", (previous, current) => previous + "&" + current.Key + "=" + current.Value).Remove(0, 1);
string url = domain + path + "?" + queryString;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.ContentType = "application/json";
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (!method.Equals("GET"))
{
url += JsonConvert.SerializeObject(body);
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
writer.Write(JsonConvert.SerializeObject(body));
}
request.Headers["X-Api-Key"] = apiKey;
request.Headers["X-Api-Signature"] = CalcAuthSigHash(secKey, url);
request.Headers["X-Api-Version"] = "2";
try
{
return (HttpWebResponse)request.GetResponse();
}
catch(WebException e)
{
string msg = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
Console.WriteLine(msg);
throw new SystemException(msg);
}
}
private byte[] GetBytes(string str)
{
return Encoding.UTF8.GetBytes(str);
}
private string GetString(byte[] bytes)
{
return BitConverter.ToString(bytes);
}
private long GetTimestamp()
{
// return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() // .NET 4.6
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}
private String CalcAuthSigHash(string key, string value)
{
HMACSHA256 hmac = new HMACSHA256(GetBytes(key));
string hash = GetString(hmac.ComputeHash(GetBytes(value))).Replace("-", "");
return hash;
}
}
}
我们采用一系列加密机制来确保您发送请求的安全性。以下是关于如何发送安全的带验签请求的指南。
发送带验签请求时,您需要将一系列值放入HTTP的请求头。
HTTP 请求头 | 描述 |
---|---|
X-Api-Key | 您的 API key, 点击 这里 |
X-Api-Signature | 用于验证的签名,由账户持有者发送。具体请查看下一步计算签名 |
除此以外,您应在发送请求时将包含GET
参数的时间戳放在Url之后,UNIX毫秒单位。我们会通过时间戳来避免重复请求攻击。
计算签名
如果发送GET
请求,请将此Url计算入签名:https://api.testwyre.com/v2/rates?timestamp=1426252182534
如果发送POST
请求,请将此Url计算入签名:https://api.testwyre.com/v2/transfers?timestamp=1426252182534
请留意发送POST
请求时,必须将请求体(request body)加在URL字符串之后。请求体必须按原样发送,不得修改。服务器会据此计算其内容。
计算签名步骤:
1.request URL 加 HTTP 请求体,串联生成 UTF-8 编码字符串。GET
的请求体则创建空白字符串。
2.使用HMAC-SHA256协议,以及您的API密钥计算签名。
资源名称
SRN是我们平台约定的,调用库内各项数据时使用的资源名称。许多我们的API交互和数据库框架都需要用到SRN,它大大增加了集成开发的灵活性,也为后续隔离开发其他服务提供便利。所有资源名称皆遵照如下格式:
类型 | 描述 |
---|---|
contact | 联系人识别号 (contact:CO-123123123) |
paymentmethod | 收款方式 (paymentMethod:PA-123123123) |
邮箱地址 (email:test@epiapi.com) | |
cellphone | 手机号码 (cellphone:+8615555555555) |
account | 平台账户号 (account:AC-123123123) |
wallet | 子钱包 (wallet:WA-123123123) |
transfer | 转账 (可能包含结汇) (transfer:TF-123123123) |
费用
我们将根据双方签署的纸质协议计算费用。VBA+每日结算的标准服务收费为0.25%。
错误处理
请求成功后,返回值为HTTP 200,请求体具体格式取决于终端节点。
如果出现错误,返回值为4XX或5XX的状态码,具体描述参见下表:
错误 | 描述 | HTTPs状态码 |
---|---|---|
ValidationException | 请求执行失败 | 400 |
UnknownException | 系统内部错误,极少出现 | 500 |
InsufficientFundsException | 账户内没有足够余额来执行请求 | 400 |
RateLimitException | 请求受限,请联系客服增加请求次数 | 429 |
AccessDeniedException | 没有权限执行相关请求 | 401 |
TransferException | 转账请求出现问题 | 400 |
NotFoundException | 请求中有相关内容未能查找到 | 400 |
ValidationException | 请求中有相关内容未能通过验证 | 400 |
CustomerSupportException | 请通过 support@epiapi.com 联系客服来解决此问题 | 400 |
MFARequiredException | 需要多重验证来执行请求,通常在使用API密钥的情况下不会出现此类情况 | 400 |
所有错误描述都会附带子类型参数来解释该错误的详细信息。除此之外,一些验证失败返回值会带有两个字段,问题字段和问题值,详细指出错误位置。
一些常见的验证失败子类:
FIELD_REQUIRED 字段未填写
INVALID_VALUE 无效值
TRANSACTION_AMOUNT_TOO_SMALL 交易值过小
UNSUPPORTED_SOURCE_CURRENCY 非支持货币种类
CANNOT_SEND_SELF_FUNDS 自账户不能转账
INVALID_PAYMENT_METHOD 收款方无效
PAYMENT_METHOD_INACTIVE 收款方未激活
PAYMENT_METHOD_UNSUPPORTED_CHARGE_CURRENCY 不支持该币种转账
PAYMENT_METHOD_UNCHARGEABLE 不支持此付款方式充值
PAYMENT_METHOD_UNSUPPORTED_DEPOSIT_CURRENCY 不支持该币种充值
操作指南
以下指南将为您详细解读通过api交互创建美元收款产品的全部步骤。
虚拟银行
在epiapi平台搭建自己的美元收款产品
epiapi提供多种api集成方式,帮助您按需求搭建不同类型收款产品。为了帮助开发者更好地理解我们的系统如何运行,以下将为您提供一个典型的使用案例。
这是一家在线平台如何利用epiapi搭建产品,从而为其用户提供独立账号的美元收款服务的整个流程,包括创建虚拟银行子钱包,接收款项,并结算至用户账户等。
1.使用KYC数据创建子钱包
epiapi使用子钱包(wallet)
这一概念来描述个人商家的账户,子钱包统一创建在您的母账户(account)
之下。创建后,每一个子钱包都具有独立的收款功能。请注意在Type
字段必须为type=VBA
,否则将无法收到虚拟银行服务账号的银行信息。
POST
https://api.testwyre.com/v2/wallets
{ "name":"youruniquename", "type":"VBA", "callbackUrl":"https://your.website.io/callback", "vbaVerificationData":{ "entityType":"CORP", "entityScope":"Shopping/Retail", "email":"test+merchant@epiapi.com", "phoneNumber":"13111111111", "ip": "127.0.0.1", "nameCn":"法人姓名", "nameEn":"Legal Rep", "dateOfBirth":1514736000, "address":{ "city":"北京", "country":"CN", "postalCode":"210000", "street1":"东四北大街107号", "street2":"克林大厦107室"}, "idNumber":"432524199902287897", "idDoc":null, "merchantIds":[{ "merchantId":"AAAAAAAAAAA", "merchantType":"Amazon"}], "expectedMonthlySales":40000, "shopName":"MyBrand", "website":"https://merchant.website.com", "repAaddress":{ "city":"北京", "country":"CN", "postalCode":"210000", "street1":"东四北大街107号", "street2":"克林大厦107室"}, "companyNameCn":"ABC有限责任公司", "companyNameEn":"ABC Company Limited", "registrationNumber":"123456789", "coiDoc":null, "salesDoc":null, "dateOfEstablishment":1514736000, "beneficialOwners":[{ "fullName":"Richard", "idNumber":"1231321312", "idDoc":null}] } }
KYC 资料
字段(* 代表必填) | 描述 |
---|---|
"entityType" * | 可填"CORP"、“M”、“F” |
"entityScope" * | "Shopping/Retail" |
"email" * | "test+merchant@epiapi.com" |
"phoneNumber" * | "13111111111" |
"ip" * | “登陆店铺常用IP地址” |
"nameCn" * | "法人姓名" |
"nameEn" * | "Legal Rep" 拼音,全大写 |
"dateOfBirth" * | UNIX毫秒单位 |
"address" * | (street 2 可以不填) |
"street1" | "东四北大街107号" |
"street2" | "克林大厦107室" |
"city" | "beijing" |
"state" | "beijing" |
"country" | "CN" |
"postalCode" | "100007" |
"idNumber" * | "432524199902287897" |
"idDoc" * | null |
"merchantId" | "A00000" |
"merchantType" | "Amazon" |
"expectedMonthlySales" | 40000 |
"shopName" | "MyBrand" |
"website" | "https://merchant.website.com" |
"repAaddress"(* 限企业) | (street 2 可以不填) |
"street1" | "东四北大街107号" |
"street2" | "克林大厦107室" |
"city" | "beijing" |
"state" | "beijing" |
"country" | "CN" |
"postalCode" | "100007" |
"companyNameCn"(* 限企业) | "ABC有限责任公司" |
"companyNameEn"( * 限企业) | "ABC Company Limited" |
"registrationNumber"(* 限企业) | "123456789" |
"coiDoc"(* 限企业) | null |
"salesDoc"(流水额高的商户可能需要提供) | null |
"dateOfEstablishment" * | UNIX毫秒单位 |
"beneficialOwners"(仅限香港注册企业) | |
"fullName" | "Richard" |
"idNumber" | "1231321312" |
"idDoc" | null |
curl -v -XPOST 'https://api.testwyre.com/v2/wallets' \
-H "Content-Type: application/json" \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}" \
-d '{"type":"VBA","name":"{your-unique-identifier}"
{
"callbackUrl":"https://your.website.io/callback",
"name":"12345678977897800",
"type":"VBA",
"vbaVerificationData":{
"entityType":"CORP",
"entityScope":"Shopping/Retail",
"email":"test+merchant@epiapi.com",
"phoneNumber":"13111111111",
"ip":"127.0.0.1",
"nameCn":"法人姓名",
"nameEn":"Legal Rep",
"dateOfBirth":1514736000,
"address":{
"city":"北京",
"country":"CN",
"postalCode":"210000",
"street1":"东四北大街107号",
"street2":"克林大厦107室"},
"idNumber":"432524199902287897",
"idDoc":null,
"merchantIds":[{
"merchantId":"AAAAAAAAAAA",
"merchantType":"Amazon"}],
"expectedMonthlySales":40000,
"shopName":"MyBrand",
"website":"https://merchant.website.com",
"repAaddress":{
"city":"北京",
"country":"CN",
"postalCode":"210000",
"street1":"东四北大街107号",
"street2":"克林大厦107室"},
"companyNameCn":"ABC有限责任公司",
"companyNameEn":"ABC Company Limited",
"registrationNumber":"123456789",
"coiDoc":null,
"salesDoc":null,
"dateOfEstablishment":1514736000,
"beneficialOwners":[{
"fullName":"Richard",
"idNumber":"1231321312",
"idDoc":null}]
}
}
平台用户申请使用VBA服务时,为这个用户创建钱包。
注:
1.根据相关法规要求,为了给用户开通虚拟银行服务,您需要从用户处收集必要的KYC信息并将其通过终端节点传送给我们。钱包会立即开通,相关的银行信息将在之后该账户被银行批准时提供。(步骤3)
2.每个子钱包将在创建时自动生成一个子钱包id。
3.请将返回记录中的walletId
记录下来,稍后将会用到。
2.上传KYC文件
POST
https://api.testwyre.com/v2/documents
创建VBA子钱包需要提供以下文件来通过KYC审查,通过后才能获得accountNumber
。通过此终端节点上传完文件后,返回记录中将包含步骤3所需的documentId
。
更新时请在Url之后加入如下参数:
字段 |描述 --------- | ownerSrn: |"wallet:[WALLET_ID]" filename |文件名,(可填): "coiDoc.pdf"
示例 POST
https://api.testwyre.com/v2/documents?ownerSrn=wallet:WA-123123123&filename=coiDoc.pdf
在请求体中包含原始字节文件上传。
public static String computeSignature2(String secretKey,String url, byte[] reqData) throws UnsupportedEncodingException {
byte[] urlBytes = url.getBytes("UTF-8");
byte[] data = new byte[urlBytes.length+reqData.length];
System.arraycopy(urlBytes,0,data,0,urlBytes.length);
System.arraycopy(reqData,0,data,urlBytes.length,reqData.length);
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256Hmac.init(key);
byte[] macData = sha256Hmac.doFinal(data);
StringBuffer result = new StringBuffer();
for (final byte element : macData){
result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
}
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
最后,请求头中应反映文件类型,允许上传的文件类型有:
文件类型 |
---|
"application/msword" |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" |
"application/pdf" |
"image/jpg" |
"image/jpeg" |
"image/png" |
文件上传成功后,返回将接收到名为"document"的对象。将每一项的文件id( 如DO-123123123 )记录下来,填入创建钱包请求体的相关字段中。
字段 | 描述 |
---|---|
idDoc | 身份证明(中国大陆居民此项必须提供二代身份证) |
coiDoc | 企业营业执照 |
salesDoc | 网店销售流水* |
对于亚马逊商户-请使用ListFinancialEventsGroup接口获取并上传内容为近三个月销售记录的xml文件。
3. 填入KYC数据
一旦文件上传成功后,将生成与子钱包关联的资源名称。为了区别每个文件,请将Id一一对应填入vbaVerificationData
字段的相关字段:
{ "vbaVerificationData":{ "coiDoc":"DO-123123123", "idDoc":"DO-123123124", "salesDoc":"DO-123123125" } }
4. 查询银行信息
银行信息将作为object
添加到子钱包里的vbaData
字段下。
{ "vbaData": { "bankName":"Evolve Bank & Trust", "bankAddress": "6070 Poplar Ave #100, Memphis TN 38119", "beneficiaryName":"Zhang San", "bankAccountNumber":"123123123", "routingNumber":"084106768"} }
使用 GET
https://api.testwyre.com/v2/wallet/[walletID] 查询子钱包的银行信息。将查询请求发送次数限制为每小时1次,查询到信息后即停止查询请求。稍后我们将为此类场景单独创建一个回调。
一旦收到信息后,您可以将银行信息发给用户,让用户在自己的店铺后台进行收款信息更新。
5.接收款项与回调
如果提前设置了callbackUrl
,当用户的accountNumber
接收到一笔款项,且款项成功入账后(从非合作平台打入的款项将被拒绝),您将收到入账的返回信息。
查看回调。
如果需要验证回调,可以使用Id
来GET
必要的信息。epiapi系统内每笔转账都有独立的id (如 TF-123123123) 来供查询,查看:
https://api.testwyre.com/v2/transfer/[id]
外部入账如亚马逊款项入账的情况,则会创建交易id (如 TR-123123123) 来供查询,查看: https://api.testwyre.com/v2//transaction/[id]
Result Format
{ "createdAt":1531445525097, "id":"TR-F3947W8C2C6", "source":"transfer:TF-HP4A42EC6DX", "dest":"wallet:WA-CF9RFZ7QU6W", "currency":"USD", "amount":5005.00, "status":"CONFIRMED", "confirmedAt":1531445525097, "cancelledAt":null, "reversedAt":null, "message":"Deposit for transfer TF-HP4A42EC6DX", "allowOverdraft":true, "authorizer":"account:AC-XXXXXXXXX", "senderProvidedId":null, "reversedBy":null, "fees":0, "feesDest":null, "metadata":{ "Description":"Pending: Transfer of $5005.00 to wallet:WA-CF9RFZ7QU6W", "transferId":"TF-HP4A42EC6DX"}, "tags":[], "sourceFees":null, "destFees":null }
6. 将美元从子钱包转至母账户
POST
https://api.testwyre.com/v2/transfers
{ "source": "wallet:walletId", "dest": "account:accountId", "sourceCurrency": "USD", "destCurrency": "USD", "destAmount": 1000, "autoConfirm": "true", "message":"Merchant Test" }
点击这里查询母账户id。
然后将美元款项转账至事先准备好的结算账户(注:账户创建时,epiapi默认将结算账户创建为paymentMethod
)。
{ "source": "account:accountId", "dest": "paymentMethod:paymentMethodId", "sourceCurrency": "USD", "destCurrency": "USD", "sourceAmount": 1000, "amountIncludesFees": "true", "autoConfirm": "true", "message":"Merchant Test" }
将autoConfirm
(自动确认)的值设置为‘true’,来自动确认转账。
账户
账户信息
此终端节点将用于取回您账户的相关信息
定义
GET
https://api.testwyre.com/v2/account
查询availableBalance(可用余额)
来获取账户余额信息,此项将返回按照目的币种汇率换算后可转账金额。
字段 | 描述 |
---|---|
ID | 账户内部识别号 |
createdAt | 账户创建时间 |
updatedAt | 账户最后更新时间 |
loginAt | 账户最后登录时间 |
rank | 账户排名,主要用于识别账户权限,例如购买外汇上限 |
profile | 账户信息,一组用户有权限更改的字段 |
paymentMethods | 账户收款方式 |
identities | 一组关联账户个人身份信息的数组,如电话邮箱等,此处包含的信息需与账户创建验证时一致 |
depositAddresses | 账户下储存货币地址 |
totalBalances | 账户下所有余额,包括处理中和可用余额 |
availableBalances | 账户下目前可用余额,如果需要查询账户内有多少余额可供提现操作,请查询此项 |
绑定的邮箱 | |
cellphone | 绑定的手机号 |
Result Format
{ "id": "121pd02kt0rnb24nclsg4bglanimurqp", "createdAt": 1404177262332, "updatedAt": 1404177262332, "loginAt": 1404177262332, "rank": 0, "profile": { "firstName": "", "lastName": "", "locale": "EN_us", "address": { "street1": null, "street2": null, "city": null, "state": null, "postalCode": null, "country": "USA" }, "businessAccount": true, "taxId": null, "doingBusinessAs": null, "website": null, "dateOfBirth": 1404177262332, "notifyEmail": true, "notifyCellphone": true, "notifyApnsDevice": true, "mfaRequiredForPwChange": false, "mfaRequiredForDcPurchase": false, "mfaRequiredForSendingFunds": false, "authyPhoneNumber": null }, "paymentMethods": [], "identities": [ { "srn": "email:richard@apiepi.com", "createdAt": 1404177262332, "verifiedAt": 1404177262332 } ], "depositAddresses": { "BTC": "1H9K67J9NcYtzmFGojR9cgM5ybxEddySwu" }, "totalBalances": { "USD": 11.8934023 }, "availableBalances": { "USD": 10.8934023, }, "email": "richard@apiepi.com", "cellphone": "+12312313112" }
子钱包
epiapi使用子钱包来指代每个商户的独立收款账户。在使用VBA进行跨境收款时,所有子钱包创建类型均为type=VBA
。以下图表显示了一组典型的收款流程。
创建子钱包
curl -v -XPOST 'https://api.testwyre.com/v2/wallets' \
-H "Content-Type: application/json" \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}" \
-d '{"type":"ENTERPRISE","name":"{your-unique-identifier}",
"callbackUrl":"https://your.website.io/callback",
"notes":"Notes about the sub account"}'
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.Integer;
import java.lang.String;
import java.lang.StringBuffer;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
public static void main(String[] args) {
String accountId = "k3f48j0rb2rp65c0sdog67vi43u80jas";
String apiKey = "fll36l3t35udalcqlh4ng6bm4qpbgher";
String secretKey = "tr3epinbk3maist0n3ijk18bm6dikrq6";
String url = "https://api.testwyre.com/v2/wallets";
String method = "POST";
String data = "";
String result = excuteWyereRequest(url, "", method, apiKey, secretKey);
System.out.println(result);
data = "{" +
" \"type\":\"ENTERPRISE\"," +
" \"name\":\"{your-unique-identifier}\"," +
" \"callbackUrl\":\"https://your.website.io/callback\"," +
" \"notes\":\"Notes about the user\"," +
" \"verificationData\": {" +
" \"firstName\":\"{users-first-name}\"," +
" \"middleName\":\"{users-middle-name}\"," +
" \"lastName\":\"{users-last-name}\"," +
" \"ssn\":\"0000\"," +
" \"passport\":\"123456\"," +
" \"birthDay\":\"1\"," +
" \"birthMonth\":\"1\"," +
" \"birthYear\":\"1970\"," +
" \"phoneNumber\":\"+15555555555\"," +
" \"address\": {" +
" \"street1\":\"1 Market Street\"," +
" \"street2\":\"Suit 420\"," +
" \"city\":\"San Francisco\"," +
" \"state\":\"CA\"," +
" \"postalCode\":\"94105\"," +
" \"country\":\"US\"" +
" }" +
" }" +
"}";
result = excuteWyreRequest(url, data, method, apiKey, secretKey);
System.out.println(result);
}
public static String excuteWyreRequest(String targetURL, String requestBody, String method, String apiKey, String secretKey) {
URL url;
HttpURLConnection connection = null;
try {
targetURL += ((targetURL.indexOf("?")>0)?"&":"?") + "timestamp=" + System.currentTimeMillis();
//Create connection
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
System.out.println(connection.getRequestMethod());
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Length", Integer.toString(requestBody.getBytes().length));
//Specify API v2
connection.setRequestProperty("X-Api-Version","2");
// Provide API key and signature
connection.setRequestProperty("X-Api-Key", apiKey);
connection.setRequestProperty("X-Api-Signature",computeSignature(secretKey,targetURL,requestBody));
//Send request
if(method.equals("POST")) {
connection.setDoOutput(true);
connection.setRequestMethod(method);
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(requestBody);
wr.flush();
wr.close();
}
//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if(connection != null) {
connection.disconnect();
}
}
}
public static String computeSignature(String secretKey, String url, String reqData) {
String data = url + reqData;
System.out.println(data);
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256Hmac.init(key);
byte[] macData = sha256Hmac.doFinal(data.getBytes());
String result = "";
for (final byte element : macData){
result += Integer.toString((element & 0xff) + 0x100, 16).substring(1);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
#dependencies:
#python3
#pip3 install requests
import json
import hmac
import time
from requests import request
class MassPay_API(object):
def __init__(self, account_id, api_version, api_key, api_secret):
self.account_id = account_id
self.api_url = 'https://api.testwyre.com/{}'.format(api_version)
self.api_version = api_version
self.api_key = api_key
self.api_secret = api_secret
#authentication decorator. May raise ValueError if no json content is returned
def authenticate_request(func):
def wrap(self, *args, **kwargs):
url, method, body = func(self, *args, **kwargs)
params = {}
timestamp = int(time.time() * 1000)
url += '?timestamp={}'.format(timestamp)
bodyJson = json.dumps(body) if body != '' else ''
headers = {}
headers['Content-Type'] = 'application/json'
headers['X-Api-Version'] = self.api_version
headers['X-Api-Key'] = self.api_key
headers['X-Api-Signature'] = hmac.new(self.api_secret.encode('utf-8'), (url + bodyJson).encode('utf-8'), 'SHA256').hexdigest()
print(headers['X-Api-Signature'])
resp = request(method=method, url=url, params=params, data=(json.dumps(body) if body != '' else None), json=None, headers=headers)
if resp.text is not None: #Wyre will always try to give an err body
return resp.status_code, resp.json()
return 404, {}
return wrap
@authenticate_request
def create_user(self, name, callbackUrl, notes, verificationData):
url = self.api_url + '/wallets'
method = 'POST'
body = {'name':name,
'verificationData':verificationData,
'type':'ENTERPRISE'}
if callbackUrl:
body["callbackUrl"] = callbackUrl
if notes:
body['notes'] = notes
return url, method, body
#USAGE Example
account_id = "YOUR_ACCOUNT_ID_HERE" #optional
api_key = "YOUR_API_KEY_HERE"
secret_key = "YOUR_SECRET_KEY_HERE"
api_version = "2"
#create Wyre MassPay API object
Wyre = MassPay_API(account_id, api_version, api_key, secret_key)
#create user and print result
http_code, result = Wyre.create_user(
"{your-unique-identifier}",
"https://your.website.io/callback",
None, #notes
{
"firstName": "{users-first-name}",
"middleName": "{users-middle-name}",
"lastName": "{users-last-name}",
"ssn": "0000",
"passport": "123456",
"birthDay": "1",
"birthMonth": "1",
"birthYear": "1970",
"phoneNumber": "+15555555555",
"address": {
"street1":"1 Market Street",
"street2":"Suite 420",
"city":"San Francisco",
"state":"CA",
"postalCode":"94105",
"country":"US"
}
})
print(result)
users_srn = result['srn'] #grab our srn identifier for the user
'''
'''php
<?php
function make_authenticated_request($endpoint, $method, $body) {
$url = 'https://api.testwyre.com';
$api_key = "bh405n7stsuo5ut30iftrsl71b4iqjnv";
$secret_key = "a19cvrchgja82urvn47kirrlrrb7stgg";
$timestamp = floor(microtime(true)*1000);
$request_url = $url . $endpoint;
if(strpos($request_url,"?"))
$request_url .= '×tamp=' . $timestamp;
else
$request_url .= '?timestamp=' . $timestamp;
if(!empty($body))
$body = json_encode($body, JSON_FORCE_OBJECT);
else
$body = '';
$headers = array(
"Content-Type: application/json",
"X-Api-Key: ". $api_key,
"X-Api-Signature: ". calc_auth_sig_hash($secret_key, $request_url . $body),
"X-Api-Version: 2"
);
$curl = curl_init();
if($method=="POST"){
$options = array(
CURLOPT_URL => $request_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true);
}else {
$options = array(
CURLOPT_URL => $request_url,
CURLOPT_RETURNTRANSFER => true);
}
curl_setopt_array($curl, $options);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($curl);
curl_close($curl);
var_dump($result);
return json_decode($result, true);
}
function calc_auth_sig_hash($seckey, $val) {
$hash = hash_hmac('sha256', $val, $seckey);
return $hash;
}
$userData = array(
"type"=>"ENTERPRISE",
"name"=>"{your-unique-identifier}",
"callbackUrl"=>"https://your.website.io/callback",
"notes"=> "Notes about the user",
"verificationData"=> array(
"firstName"=> "{users-first-name}",
"middleName"=> "{users-middle-name}",
"lastName"=> "{users-last-name}",
"ssn"=> "0000",
"passport"=> "12345",
"birthDay"=> "1",
"birthMonth"=> "1",
"birthYear"=> "1970",
"phoneNumber"=> "+15555555555",
"address"=> array(
"street1":"1 Market Street",
"street2":"Suite 420",
"city":"San Francisco",
"state":"CA",
"postalCode":"94105",
"country":"US"
)
)
);
echo make_authenticated_request("/wallets", "POST", $userData);
?>
定义
POST
https://api.testwyre.com/v2/wallets
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
name | string | 用户名 | yes |
callbackUrl | string | 钱包数据或信息更新时将通过callbackUrl发送回传 | no |
type | string | 创建钱包类型,默认为 DEFAULT |
no |
notes | string | 关于此用户的附注 | no |
Result Format
{ "name" : "{your-unique-identifier}", "id" : "WA-AYBNA3lBiWAM4l3", "depositAddresses" : { "BTC" : "2ShL7kzSNNxedit6hC2fjSQhVcAucTeS1m7" }, "totalBalances" : { "BTC" : 0 }, "availableBalances" : { "BTC" : 0 }, "srn" : "wallet:AYBNA3lBiWAM4l3", "balances" : { "BTC" : 0 }, "callbackUrl" : "https://your.website.io/callback", "notes" : "Notes about the user" }
创建多个子钱包
curl -XPOST 'https://api.testwyre.com/v2/wallets/batch?pretty' \
-H 'Content-Type:application/json' \
-d '{
"wallets":[
{"name":"walletOne"},
{"name":"walletTwo"},
{"name":"walletThree"}
]
}'
此终端节点允许通过一个请求创建多个子钱包(每用户一个)。
定义
POST
https://api.testwyre.com/v2/wallets/batch
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
wallets | array | 每个子钱包对象的数组 | yes |
Result Format
{ "name" : "walletOne", "id" : "AxVA57edP0H33x3", "notes" : null, "srn" : "wallet:AxVA57edP0H33x3", "callbackUrl" : null, "verificationData" : null, "depositAddresses" : { "BTC" : "2ShKKFb9gEP5uvRXtMbs7ykJAMPgoSSnSWB" }, "totalBalances" : { "BTC" : 0 }, "availableBalances" : { "BTC" : 0 }, "balances" : { "BTC" : 0 } }, { "name" : "walletTwo", "id" : "AtEhoXje3C1V5zq", "notes" : null, "srn" : "wallet:AtEhoXje3C1V5zq", "callbackUrl" : null, "verificationData" : null, "depositAddresses" : { "BTC" : "2ShKndBJNHvzABhBzLxvfzzD2vt64C36dPc" }, "totalBalances" : { "BTC" : 0 }, "availableBalances" : { "BTC" : 0 }, "balances" : { "BTC" : 0 } }, { "name" : "walletThree", "id" : "U07tSKMvofeMmx0", "notes" : null, "srn" : "wallet:U07tSKMvofeMmx0", "callbackUrl" : null, "verificationData" : null, "depositAddresses" : { "BTC" : "2ShJsBPUb4HrNtgaNZk3YQSi2ynpZ5YY7sT" }, "totalBalances" : { "BTC" : 0 }, "availableBalances" : { "BTC" : 0 }, "balances" : { "BTC" : 0 } }
查询子钱包
通过user ID查询子钱包:
curl -v -XGET 'https://api.testwyre.com/v2/wallet/{wallet-id}' \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}" \
通过用户名查询子钱包:
curl -v -XGET 'https://api.testwyre.com/v2/wallet' \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}" \
-d name={your-identifier}
此终端节点允许通过id或名字查询子钱包余额。
定义
GET
https://api.testwyre.com/v2/wallet/{walletId}
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
walletId | string | 子钱包所属用户id | yes |
定义
GET
https://api.testwyre.com/v2/wallet/
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
name | string | 子钱包所属用户名 | yes |
Results Format
{ "owner": "account:[account-ID]", "callbackUrl": null, "depositAddresses": { "BTC": "1FNAkNVt3gXdS3PZ1tDvetbcafKPsJPQTG" }, "totalBalances": { "USD": 4.96 }, "availableBalances": { "USD": 4.96 }, "verificationData": null, "balances": { "USD": 4.96 }, "srn": "wallet:[Wallet-ID]", "createdAt": 1497861843000, "notes": "test1", "name": "richard", "id": "[Wallet-ID]" }
编辑钱包
curl -v -XPOST 'https://api.testwyre.com/v2/wallet/{wallet-id}/update' \
-H "Content-Type: application/json" \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}" \
-d '{"name":"{your-unique-identifier}","notes":"Updated notes about the sub account"}'
#dependencies:
#python3
#pip3 install requests
import json
import hmac
import time
from requests import request
class MassPay_API(object):
def __init__(self, account_id, api_version, api_key, api_secret):
self.account_id = account_id
self.api_url = 'https://api.testwyre.com/{}'.format(api_version)
self.api_version = api_version
self.api_key = api_key
self.api_secret = api_secret
#authentication decorator. May raise ValueError if no json content is returned
def authenticate_request(func):
def wrap(self, *args, **kwargs):
url, method, body = func(self, *args, **kwargs)
params = {}
timestamp = int(time.time() * 1000)
url += '?timestamp={}'.format(timestamp)
bodyJson = json.dumps(body) if body != '' else ''
headers = {}
headers['Content-Type'] = 'application/json'
headers['X-Api-Version'] = self.api_version
headers['X-Api-Key'] = self.api_key
headers['X-Api-Signature'] = hmac.new(self.api_secret.encode('utf-8'), (url + bodyJson).encode('utf-8'), 'SHA256').hexdigest()
print(headers['X-Api-Signature'])
resp = request(method=method, url=url, params=params, data=(json.dumps(body) if body != '' else None), json=None, headers=headers)
if resp.text is not None: #Wyre will always try to give an err body
return resp.status_code, resp.json()
return 404, {}
return wrap
@authenticate_request
def update_user(self, walletId, name, callbackUrl, notes, verificationData):
url = self.api_url + '/wallet/' + walletId + '/update'
method = 'POST'
body = {'name':name}
if callbackUrl:
body["callbackUrl"] = callbackUrl
if notes:
body['notes'] = notes
if verificationData:
body['verificationData'] = verificationData
return url, method, body
#USAGE Example
account_id = "YOUR_ACCOUNT_ID_HERE" #optional
api_key = "YOUR_API_KEY_HERE"
secret_key = "YOUR_SECRET_KEY_HERE"
api_version = "2"
#create Wyre MassPay API object
Wyre = MassPay_API(account_id, api_version, api_key, secret_key)
#create user and print result
http_code, result = Wyre.update_user(
"{wallet-id}",
"{your-unique-identifier}",
None, #callbackUrl
"Updated notes for user",
None #verification data
)
print(result)
users_srn = result['srn'] #grab our srn identifier for the user
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.Integer;
import java.lang.String;
import java.lang.StringBuffer;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
public static void main(String[] args) {
String accountId = "k3f48j0rb2rp65c0sdog67vi43u80jas";
String apiKey = "fll36l3t35udalcqlh4ng6bm4qpbgher";
String secretKey = "tr3epinbk3maist0n3ijk18bm6dikrq6";
String walletId = "{wallet-id}";
String url = "https://api.testwyre.com/v2/wallet/"+ walletId +"/update";
String method = "POST";
String data = "";
String result = excuteWyreRequest(url, "", method, apiKey, secretKey);
System.out.println(result);
data = "{" +
" \"name\":\"{your-unique-identifier}\"," +
" \"notes\":\"Updated notes about the user\"" +
"}";
result = excuteWyreRequest(url, data, method, apiKey, secretKey);
System.out.println(result);
}
public static String excuteWyreRequest(String targetURL, String requestBody, String method, String apiKey, String secretKey) {
URL url;
HttpURLConnection connection = null;
try {
targetURL += ((targetURL.indexOf("?")>0)?"&":"?") + "timestamp=" + System.currentTimeMillis();
//Create connection
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
System.out.println(connection.getRequestMethod());
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Length", Integer.toString(requestBody.getBytes().length));
//Specify API v2
connection.setRequestProperty("X-Api-Version","2");
// Provide API key and signature
connection.setRequestProperty("X-Api-Key", apiKey);
connection.setRequestProperty("X-Api-Signature",computeSignature(secretKey,targetURL,requestBody));
//Send request
if(method.equals("POST")) {
connection.setDoOutput(true);
connection.setRequestMethod(method);
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(requestBody);
wr.flush();
wr.close();
}
//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if(connection != null) {
connection.disconnect();
}
}
}
public static String computeSignature(String secretKey, String url, String reqData) {
String data = url + reqData;
System.out.println(data);
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256Hmac.init(key);
byte[] macData = sha256Hmac.doFinal(data.getBytes());
String result = "";
for (final byte element : macData){
result += Integer.toString((element & 0xff) + 0x100, 16).substring(1);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
此终端节点允许更新母账户下子钱包的信息。
定义
POST
https://api.testwyre.com/v2/wallet/{walletId}
删除钱包
curl -v -XDELETE 'https://api.testwyre.com/v2/wallet/{wallet-id}' \
-H "X-Api-Key: {api-key}" \
-H "X-Api-Signature: {signature}"
此终端节点用于删除母账户下子钱包。请知悉出于合规考虑,子钱包的相关数据仍会储存在我们的系统后台。子钱包删除后,关联的收款虚拟银行账户也将被关闭,不能再用于收款。
定义
DELETE
https://api.testwyre.com/v2/wallet/{walletId}
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
walletId | string | 子钱包id | yes |
列出子钱包
此终端节点用于列出母账户下已创建的所有子钱包。
定义
GET
https://api.testwyre.com/v2/wallets
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
limit | string | 返回记录数 | yes |
offset | string | 跳过记录数 | yes |
Result Format
{ "data": { "owner": "account:XAV3CRAC94P", "balances": {}, "srn": "wallet:WA-XM4L3JMUQGF", "createdAt": 1508433396000, "callbackUrl": "https://shake.webscript.io/callback", "depositAddresses": { "BTC": "1Q9TqsVwuZf6bYqtxxjqdataXx81x3Q1h7" }, "totalBalances": {}, "availableBalances": {}, "notes": "nope", "name": "Person A", "id": "WA-XM4L3JMUQGF" } }
转账
介绍
转账是Epi API中最重要的操作之一,我们的转账API功能强大,非常灵活,除外部转账,内部的资金移动也同样支持,母账户管理、子钱包间交易等渠道皆可。除此之外,您可指定不同的来源和目的币种,资金将在后台自动完成处理。
任何在平台上的资金移动都被视为交易操作,所以过程中款项将会呈现不同的处理状态,直至操作完成,款项被转入您指定的收入方。
创建转账
定义
POST
https://api.testwyre.com/v2/transfer/
此终端节点用于创建一笔新转账。
{ "source":"account:AC-123123123", "sourceCurrency":"USD", "sourceAmount":"5", "dest":"email:sam@testwyre.com", "destCurrency":"CNY", "message": "" }
参数
参数 | 类型 | 描述 | 是否需要 |
---|---|---|---|
source | string | 识别转出账户的系统资源名称 | no |
sourceAmount | double | 来源币种转出金额,与 sourceCurrency 成对. 只写 sourceAmount 或 destAmount ,不要都写 |
yes |
sourceCurrency | string | 来源币种的ISO 3166-1三位字母代码 | yes |
dest | string | 收入方的收款银行信息或子钱包信息,请留意此处手机号码默认为美国手机号,国际号码请加‘+’和国家码 | yes |
destAmount | double | 目的币种收入金额,只写sourceAmount OR destAmount ,不要都写 |
yes |
destCurrency | string | 目的币种的ISO 3166-1三位字母代码,来源币种可与目的币种相同或不同,不同时将自动进行兑换 | yes |
message | string | 用户可见的附言,此处非必填 | no |
callbackUrl | string | 状态返回时的回调url,此处非必填 | no |
autoConfirm | boolean | 自动确认转账,此处非必填 | no |
customId | string | 用户的内部识别号码或标签,必须唯一,否则转账将失败,此处非必填 | no |
amountIncludesFees | boolean | 此处的值若为true,则默认填入的转出/收入金额包含了需扣除的手续费,不会再产生其他费用,此处非必填 | no |
preview | boolean | 此处的值若为true,将会生成转账信息的预览,而不实际操作转账 | no |
muteMessages | boolean | 此处的值若为true,将禁用邮件、信息等通知款项收入方 | no |
转账创建后状态为UNCONFIRMED(未确认)有30秒的时间用来检查并确认请求,过时无效。无效时请重新创建转账请求,然后确认,也可以设置autoConfirm
的值为true
来自动确认。
检查一笔转账请求时请注意以下几项:
exchangeRate
- 转账交易时的汇率,汇率将以来源货币为基准表示,如需转换为目的货币,用汇率乘以来源货币即可,此项包含epiapi扣除的手续费及按需产生的其他费用。
sourceAmount/destAmount
- 转出金额/收入金额,请仔细检查此处的金额是否符合预计转出/收入的款项金额,此处的值取决于amountIncludesFees
参数。
Result Format
{ "id": "TF-VWGF3WW6JU4", "status": "PENDING", "failureReason": null, "language": "en", "createdAt": 1525196883000, "updatedAt": 1525196883000, "completedAt": 1525196884000, "cancelledAt": null, "expiresAt": 1525456083000, "owner": "account:AC-PJZEFT7JP6J", "source": "service:Fiat Credits", "dest": "wallet:WA-AFFGZJJ7X82", "sourceCurrency": "USD", "sourceAmount": 10, "destCurrency": "USD", "destAmount": 10, "exchangeRate": null, "message": null, "totalFees": 0, "fees": { "USD": 0 }, "customId": null, "reversingSubStatus": null, "reversalReason": null, "pendingSubStatus": null, "destName": "amandawallet", "sourceName": "Wyre", "blockchainTx": null, "statusHistories": [ { "id": "HNUBAMZ4YQQ", "createdAt": 1525196884000, "statusDetail": "Initiating Transfer", "state": "INITIATED", "failedState": null }, { "id": "V8L2MJNPF6D", "createdAt": 1525196884000, "statusDetail": "Transfer Pending", "state": "PENDING", "failedState": null } ] }
确认转账
此终端节点用于确认款项划转。成功创建一笔转账后会受到200回调,此时有30秒时间来确认。请留意创建转账后生成的transferId
,如果需要在创建转账后自动确认,创建时请设置参数autoConfirm
的值为‘true’。
定义
POST
https://api.testwyre.com/v2/transfer/transferId:/confirm
参数
参数 | 类型 | 描述 |
---|---|---|
transferId | string | 需确认的transferId |
Result Format
{ "id": "TF-VWGF3WW6JU4", "status": "COMPLETED", "failureReason": null, "language": "en", "createdAt": 1525196883000, "updatedAt": 1525196883000, "completedAt": 1525196884000, "cancelledAt": null, "expiresAt": 1525456083000, "owner": "account:AC-PJZEFT7JP6J", "source": "service:Fiat Credits", "dest": "wallet:WA-AFFGZJJ7X82", "sourceCurrency": "USD", "sourceAmount": 10, "destCurrency": "USD", "destAmount": 10, "exchangeRate": null, "message": null, "totalFees": 0, "fees": { "USD": 0 }, "customId": null, "reversingSubStatus": null, "reversalReason": null, "pendingSubStatus": null, "destName": "amandawallet", "sourceName": "Wyre", "blockchainTx": null, "statusHistories": [ { "id": "HNUBAMZ4YQQ", "createdAt": 1525196884000, "statusDetail": "Initiating Transfer", "state": "INITIATED", "failedState": null }, { "id": "V8L2MJNPF6D", "createdAt": 1525196884000, "statusDetail": "Transfer Completed", "state": "COMPLETED", "failedState": null } ] }
查询转账
此终端节点用于查询已创建的某笔转账的详细信息。
定义
GET
https://api.testwyre.com/v2/transfer?customId=
参数
参数 | 类型 | 描述 |
---|---|---|
customId | string | 创建转账时提供的识别号 |
Result Format
{ "id": "TF-VWGF3WW6JU4", "status": "COMPLETED", "failureReason": null, "language": "en", "createdAt": 1525196883000, "updatedAt": 1525196883000, "completedAt": 1525196884000, "cancelledAt": null, "expiresAt": 1525456083000, "owner": "account:AC-PJZEFT7JP6J", "source": "service:Fiat Credits", "dest": "wallet:WA-AFFGZJJ7X82", "sourceCurrency": "USD", "sourceAmount": 10, "destCurrency": "USD", "destAmount": 10, "exchangeRate": null, "message": null, "totalFees": 0, "fees": { "USD": 0 }, "customId": null, "reversingSubStatus": null, "reversalReason": null, "pendingSubStatus": null, "destName": "amandawallet", "sourceName": "Wyre", "blockchainTx": null, "statusHistories": [ { "id": "HNUBAMZ4YQQ", "createdAt": 1525196884000, "statusDetail": "Initiating Transfer", "state": "INITIATED", "failedState": null }, { "id": "V8L2MJNPF6D", "createdAt": 1525196884000, "statusDetail": "Transfer Completed", "state": "COMPLETED", "failedState": null } ] }
转账状态
定义
GET
https://api.testwyre.com/v2/transfer/:transferId
参数
参数 | 类型 | 描述 |
---|---|---|
transferId | string | 生成的transferId |
Result Format
{ "id": "TF-VWGF3WW6JU4", "status": "COMPLETED", "failureReason": null, "language": "en", "createdAt": 1525196883000, "updatedAt": 1525196883000, "completedAt": 1525196884000, "cancelledAt": null, "expiresAt": 1525456083000, "owner": "account:AC-PJZEFT7JP6J", "source": "service:Fiat Credits", "dest": "wallet:WA-AFFGZJJ7X82", "sourceCurrency": "USD", "sourceAmount": 10, "destCurrency": "USD", "destAmount": 10, "exchangeRate": null, "message": null, "totalFees": 0, "fees": { "USD": 0 }, "customId": null, "reversingSubStatus": null, "reversalReason": null, "pendingSubStatus": null, "destName": "amandawallet", "sourceName": "Wyre", "blockchainTx": null, "statusHistories": [ { "id": "HNUBAMZ4YQQ", "createdAt": 1525196884000, "statusDetail": "Initiating Transfer", "state": "INITIATED", "failedState": null }, { "id": "V8L2MJNPF6D", "createdAt": 1525196884000, "statusDetail": "Transfer Completed", "state": "COMPLETED", "failedState": null } ] }
转账显示为PENDING(处理中)状态后,我们将开始转移款项至收款方。在此之后该笔转账会根据情况显示为COMPLETE(完成)或FAILED(失败)状态。
回调
我们提供一系列HTTP回调来方便您通知您的用户存款及转账状态。
回调发出
任何影响账户余额的操作都会导致发出回调,例如:
- 处理中的入账
- 已确认的处理中转账
- 进行中的转账
可能会出现一笔转账收到两次回调的情况,尤其是区块链上的相关操作,此时将在交易第一次被监测到和交易确认后分别收到两次回调。
接收回调和重试
您的系统应以HTTP 200响应回调请求,我们仅会尝试发送一次,此处将在稍后更新时转为自动重试。目前我们可以手动重新发送。
Result Format
{ "id": "TF-VWGF3WW6JU4", "status": "COMPLETED", "failureReason": null, "language": "en", "createdAt": 1525196883000, "updatedAt": 1525196883000, "completedAt": 1525196884000, "cancelledAt": null, "expiresAt": 1525456083000, "owner": "account:AC-PJZEFT7JP6J", "source": "service:Fiat Credits", "dest": "wallet:WA-AFFGZJJ7X82", "sourceCurrency": "USD", "sourceAmount": 10, "destCurrency": "USD", "destAmount": 10, "exchangeRate": null, "message": null, "totalFees": 0, "fees": { "USD": 0 }, "customId": null, "reversingSubStatus": null, "reversalReason": null, "pendingSubStatus": null, "destName": "amandawallet", "sourceName": "Wyre", "blockchainTx": null, "statusHistories": [ { "id": "HNUBAMZ4YQQ", "createdAt": 1525196884000, "statusDetail": "Initiating Transfer", "state": "INITIATED", "failedState": null }, { "id": "V8L2MJNPF6D", "createdAt": 1525196884000, "statusDetail": "Transfer Completed", "state": "COMPLETED", "failedState": null } ] }
回调载荷为JSON格式,内容标示了触发该次回调的操作。
收款方式
添加银行信息
{
"paymentMethodType":"INTERNATIONAL_TRANSFER",
"country": "US",
"currency": "USD",
"beneficiaryType": "INDIVIDUAL",
"beneficiaryAddress": "112 Brannan St",
"beneficiaryAddress2": "", //Optional
"beneficiaryCity": "San Francisco",
"beneficiaryState": "CA",
"beneficiaryPostal": "94108",
"beneficiaryPhoneNumber": "+14102239203",
"beneficiaryDobDay": "15",
"beneficiaryDobMonth":"12",
"beneficiaryDobYear":"1989",
"paymentType" : "LOCAL_BANK_WIRE", // LOCAL_BANK_WIRE
"firstNameOnAccount": "Billy-Bob",
"lastNameOnAccount":"Jones",
"accountNumber": "0000000000000",
"routingNumber": "0000000000",
"accountType": "CHECKING", //CHECKING or SAVINGS
"chargeablePM": "true"
}
这里需填写您账户汇出款项银行的信息。 我们将通过此字段来确认款项的汇出方,这将帮助我们了解款项的来源。
{
"paymentMethodType":"INTERNATIONAL_TRANSFER",
"country": "US",
"currency": "USD",
"beneficiaryType": "CORPORATE",
"beneficiaryCompanyName":"",
"beneficiaryAddress": "112 Brannan St",
"beneficiaryAddress2": "", //Optional
"beneficiaryCity": "San Francisco",
"beneficiaryState": "CA",
"beneficiaryPostal": "94108",
"beneficiaryLandlineNumber":"+123464542947",
"beneficiaryEmailAddress":"tes@testwyre.com",
"beneficiaryEinTin":"00000000",
"beneficiaryDobDay": "15", //Date of Incorporation
"beneficiaryDobMonth":"12", //Date of Incorporation
"beneficiaryDobYear":"1989", //Date of Incorporation
"paymentType" : "LOCAL_BANK_WIRE", // LOCAL_BANK_WIRE
"accountType": "CHECKING", //CHECKING or SAVINGS
"accountNumber": "0000000000000",
"routingNumber": "0000000000",
"chargeablePM": "true"
}
此终端节点用于创建银行账户。
查询收款方式
此终端节点用于查询收款方式下的银行账户信息。
定义
GET
https://api.testwyre.com/v2/paymentMethod/:paymentMethodId
Result Format
{ "id": "TestPaymentMethod", "owner": "account:ABCDEFG", "createdAt": 1230940800000, "name": "TEST PAYMENT METHOD", "defaultCurrency": "USD", "status": "ACTIVE", "statusMessage": null, "waitingPrompts": [], "linkType": "TEST", "supportsDeposit": true, "nameOnMethod": null, "last4Digits": null, "brand": null, "expirationDisplay": null, "countryCode": null, "nickname": null, "rejectionMessage": null, "disabled": false, "supportsPayment": true, "chargeableCurrencies": [ "GBP", "MXN", "HKD", "USD", "CNY", "BRL", "EUR", ], "depositableCurrencies": [ "USD" ], "chargeFeeSchedule": null, "depositFeeSchedule": null, "minCharge": null, "maxCharge": null, "minDeposit": null, "maxDeposit": null, "documents": [], "srn": "paymentmethod:TestPaymentMethod" }
美元付款
美元付款由我们在当地的银行之一发起,与收入款项国家相对应。需注意美元付款每笔最低限额为5美金。
定义
POST
https://api.testwyre.com/v2/transfers
{
"dest": {
"paymentMethodType":"INTERNATIONAL_TRANSFER",
"country": "US",
"currency": "USD",
"beneficiaryType": "INDIVIDUAL",
"beneficiaryAddress": "112 Brannan St",
"beneficiaryAddress2": "", //Optional
"beneficiaryCity": "San Francisco",
"beneficiaryState": "CA",
"beneficiaryPostal": "94108",
"beneficiaryPhoneNumber": "+14102239203",
"beneficiaryDobDay": "15",
"beneficiaryDobMonth":"12",
"beneficiaryDobYear":"1989",
"paymentType" : "LOCAL_BANK_WIRE",
"firstNameOnAccount": "Billy-Bob",
"lastNameOnAccount":"Jones",
"accountNumber": "0000000000000",
"routingNumber": "0000000000",
"accountType": "CHECKING", // CHECKING or SAVINGS
"bankName": "Bank of America"
},
"sourceCurrency": "BRL",
"destCurrency": "USD",
"destAmount": 10,
"message":"USD Personal example"
}
美元付款字段
参数 | 描述 |
---|---|
dest | object |
dest.paymentMethodType | INTERNATIONAL_TRANSFER |
dest.country | US |
dest.currency | USD |
dest.beneficiaryType | INDIVIDUAL or CORPORATE |
dest.beneficiaryPhoneNumber | 个人账户需填写 |
dest.beneficiaryLandlineNumber | 公司账户需填写 |
dest.beneficiaryEinTin | 公司账户需填写 |
dest.beneficiaryEmailAddress | 公司账户需填写 |
dest.beneficiaryCompanyName | 公司账户需填写 |
dest.firstNameOnAccount | 法人名(不包括姓) |
dest.lastNameOnAccount | 法人姓 |
dest.accountNumber | 法人银行账号 |
dest.routingNumber | 法人银行账户 routing number |
dest.accountType | CHECKING or SAVINGS |
destAmount | 存入收方金额,从用户账户减除的款项将自动计算汇率和手续费 |
destCurrency | 存入收方币种,若汇出存入币种不一致将自动进行兑换 |
sourceCurrency | 账户原币种 |
到账时间
美国银行结算时间为美国中部标准时间下午四点。
若在当日下午四点前提交转账提现请求,当日处理到账,当天下午四点后提交则下一个工作日到账。
更新日志
更新日期 2018/11/30
1. 测试环境
- 测试环境域名更新,新测试环境域名为 test.epiapi.com,停止使用原环境下所有功能
2. 澳大利亚虚拟银行收款(VBA)
- 添加澳大利亚 VBA 相关功能
3. 子钱包更新
- 添加“countries” 字段,允许在一个子钱包下添加多个国家 VBA(不填写此字段将默认为美国VBA)
— 更新子钱包字段内容有效性验证
4. 子钱包回传
— 添加 vbaData 状态变更的回传,用于更方便地监视账户创建进展
5. 内部服务
- 针对自动开户和检查状态更新内部服务流程