index-android.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. import inAppPurchase from '../www/index-android';
  2. import assert from 'assert';
  3. describe('Android purchases', () => {
  4. const execError = code => (success, err, pluginName, name, args) => err({ code });
  5. before(() => {
  6. GLOBAL.window = {};
  7. GLOBAL.window.cordova = {};
  8. });
  9. describe('#getProducts()', () => {
  10. it('should initialize the Android plugin', async (done) => {
  11. try {
  12. const productIds = ['com.test.prod1', 'com.test.prod2'];
  13. let initCalled = false;
  14. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  15. if (name === 'init') {
  16. assert(typeof success === 'function', 'should define a success callback');
  17. assert(typeof err === 'function', 'should define an error callback');
  18. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  19. assert(args.length === 0, 'args should be empty');
  20. initCalled = true;
  21. success();
  22. } else if (name === 'getSkuDetails') {
  23. success([]);
  24. }
  25. };
  26. await inAppPurchase.getProducts(productIds);
  27. assert(initCalled, 'init() should be called');
  28. done();
  29. } catch (err) {
  30. done(err);
  31. }
  32. });
  33. it('should call the Android getSkuDetails() function with the correct args', async (done) => {
  34. try {
  35. const productIds = ['com.test.prod1', 'com.test.prod2'];
  36. let getSkuDetailsCalled = false;
  37. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  38. if (name === 'getSkuDetails') {
  39. assert(typeof success === 'function', 'should define a success callback');
  40. assert(typeof err === 'function', 'should define an error callback');
  41. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  42. assert.deepEqual(args, productIds, 'should get productIds as args');
  43. getSkuDetailsCalled = true;
  44. success([]);
  45. } else if (name === 'init') {
  46. success();
  47. }
  48. };
  49. await inAppPurchase.getProducts(productIds);
  50. assert(getSkuDetailsCalled, 'getSkuDetails() should be called');
  51. done();
  52. } catch (err) {
  53. done(err);
  54. }
  55. });
  56. it('should chunk the getSkuDetails call when more than 19 product ids are given', async (done) => {
  57. try {
  58. const productIds = [
  59. '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21'
  60. ];
  61. const calls = [];
  62. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  63. if (name === 'getSkuDetails') {
  64. calls.push(args);
  65. success([]);
  66. } else if (name === 'init') {
  67. success();
  68. }
  69. };
  70. await inAppPurchase.getProducts(productIds);
  71. assert.deepEqual(calls, [
  72. ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
  73. ['20', '21']
  74. ],'getSkuDetails() should be called chunked');
  75. done();
  76. } catch (err) {
  77. done(err);
  78. }
  79. });
  80. it('should return an array of objects', async (done) => {
  81. try {
  82. const products = [
  83. { productId: 'com.test.prod1', title: 'prod1 title', description: 'prod1 description', price: '$0.99', currency: 'USD', priceAsDecimal: 0.99 },
  84. { productId: 'com.test.prod2', title: 'prod2 title', description: 'prod2 description', price: '$1.99', currency: 'USD', priceAsDecimal: 1.99 }
  85. ];
  86. const productIds = products.map(i => i.productId );
  87. GLOBAL.window.cordova.exec = (success, err, pluginName, name) => {
  88. if (name === 'getSkuDetails') {
  89. success(products);
  90. } else if (name === 'init') {
  91. success();
  92. }
  93. };
  94. const resProducts = await inAppPurchase.getProducts(productIds);
  95. assert.deepEqual(resProducts, products);
  96. done();
  97. } catch (err) {
  98. done(err);
  99. }
  100. });
  101. it('should throw an error on an invalid argument', async (done) => {
  102. let err1 = false;
  103. let err2 = false;
  104. let err3 = false;
  105. try {
  106. await inAppPurchase.getProducts();
  107. } catch (err) {
  108. if (err.message === inAppPurchase.utils.errors[101]) {
  109. err1 = true;
  110. } else {
  111. done(err);
  112. }
  113. }
  114. try {
  115. await inAppPurchase.getProducts([1]);
  116. } catch (err) {
  117. if (err.message === inAppPurchase.utils.errors[101]) {
  118. err2 = true;
  119. } else {
  120. done(err);
  121. }
  122. }
  123. try {
  124. await inAppPurchase.getProducts('test');
  125. } catch (err) {
  126. if (err.message === inAppPurchase.utils.errors[101]) {
  127. err3 = true;
  128. } else {
  129. done(err);
  130. }
  131. }
  132. assert(err1, 'should throw an error for no arguments');
  133. assert(err2, 'should throw an error for an array of int');
  134. assert(err3, 'should throw an error for a string');
  135. done();
  136. });
  137. it('should return an errorCode property when there is an error', async (done) => {
  138. try {
  139. GLOBAL.window.cordova.exec = execError(-1)
  140. await inAppPurchase.getProducts(['com.test.prod1']);
  141. done(new Error('Call to #getProducts() suceeded but was expected to fail.'));
  142. } catch (err) {
  143. assert(err.errorCode === -1, 'should create an errorCode property');
  144. done();
  145. }
  146. });
  147. });
  148. describe('#buy()', () => {
  149. it('should call the Android buy() function with the correct args ', async (done) => {
  150. try {
  151. const productId = 'com.test.prod1';
  152. const orderId = '_some_order_id_';
  153. const purchaseToken = '_some_purchase_token_';
  154. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  155. assert(typeof success === 'function', 'should define a success callback');
  156. assert(typeof err === 'function', 'should define an error callback');
  157. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  158. assert(name === 'buy', 'invalid function name');
  159. assert(args[0] === productId, 'should get productId as args');
  160. success({ orderId, purchaseToken });
  161. };
  162. await inAppPurchase.buy(productId);
  163. done();
  164. } catch (err) {
  165. done(err);
  166. }
  167. });
  168. it('should throw an error on an invalid argument', async (done) => {
  169. let err1 = false;
  170. let err2 = false;
  171. let err3 = false;
  172. try {
  173. await inAppPurchase.buy();
  174. } catch (err) {
  175. if (err.message === inAppPurchase.utils.errors[102]) {
  176. err1 = true;
  177. } else {
  178. done(err);
  179. }
  180. }
  181. try {
  182. await inAppPurchase.buy([1]);
  183. } catch (err) {
  184. if (err.message === inAppPurchase.utils.errors[102]) {
  185. err2 = true;
  186. } else {
  187. done(err);
  188. }
  189. }
  190. try {
  191. await inAppPurchase.buy(1);
  192. } catch (err) {
  193. if (err.message === inAppPurchase.utils.errors[102]) {
  194. err3 = true;
  195. } else {
  196. done(err);
  197. }
  198. }
  199. assert(err1, 'should throw an error for no arguments');
  200. assert(err2, 'should throw an error for an array of int');
  201. assert(err3, 'should throw an error for an int');
  202. done();
  203. });
  204. it('should return an object with the correct attributes', async (done) => {
  205. try {
  206. const productId = 'com.test.prod1';
  207. const packageName = 'com.test';
  208. const orderId = '_some_order_id_';
  209. const purchaseToken = '_some_purchase_token_';
  210. const signature = '_some_signature_';
  211. const receipt = '_some_receipt_';
  212. const purchaseTime = Date.now();
  213. const purchaseState = 0;
  214. GLOBAL.window.cordova.exec = (success) => {
  215. success({ productId, orderId, purchaseToken, signature, packageName, purchaseTime, purchaseState, receipt });
  216. };
  217. const res = await inAppPurchase.buy(productId);
  218. assert(res.signature === signature);
  219. assert(res.productId === productId);
  220. assert(res.transactionId === purchaseToken);
  221. assert(res.receipt === receipt);
  222. done();
  223. } catch (err) {
  224. done(err);
  225. }
  226. });
  227. it('should return an errorCode property when there is an error', async (done) => {
  228. try {
  229. GLOBAL.window.cordova.exec = execError(-1);
  230. await inAppPurchase.buy('com.test.prod1');
  231. done(new Error('Call to #buy() suceeded but was expected to fail.'));
  232. } catch (err) {
  233. assert(err.errorCode === -1, 'should create an errorCode property');
  234. done();
  235. }
  236. });
  237. });
  238. describe('#subscribe()', () => {
  239. it('should call the Android subscribe() function with the correct args ', async (done) => {
  240. try {
  241. const productId = 'com.test.prod1';
  242. const orderId = '_some_order_id_';
  243. const purchaseToken = '_some_purchase_token_';
  244. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  245. assert(typeof success === 'function', 'should define a success callback');
  246. assert(typeof err === 'function', 'should define an error callback');
  247. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  248. assert(name === 'subscribe', 'invalid function name');
  249. assert(args[0] === productId, 'should get productId as args');
  250. success({ orderId, purchaseToken });
  251. };
  252. await inAppPurchase.subscribe(productId);
  253. done();
  254. } catch (err) {
  255. done(err);
  256. }
  257. });
  258. it('should return an errorCode property when there is an error', async (done) => {
  259. try {
  260. GLOBAL.window.cordova.exec = execError(-1);
  261. await inAppPurchase.subscribe('com.test.prod1');
  262. done(new Error('Call to #subscribe() suceeded but was expected to fail.'));
  263. } catch (err) {
  264. assert(err.errorCode === -1, 'should create an errorCode property');
  265. done();
  266. }
  267. });
  268. });
  269. describe('#consume()', () => {
  270. it('should call the Android consume() function with the correct args ', async (done) => {
  271. try {
  272. const receipt = '_some_receipt_';
  273. const signature = '_some_signature_';
  274. const type = 'inapp';
  275. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  276. assert(typeof success === 'function', 'should define a success callback');
  277. assert(typeof err === 'function', 'should define an error callback');
  278. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  279. assert(name === 'consumePurchase', 'invalid function name');
  280. assert(args[0] === type, 'should get type as args 1');
  281. assert(args[1] === receipt, 'should get receipt as args 2');
  282. assert(args[2] === signature, 'should get signature as arg 3');
  283. success({});
  284. };
  285. await inAppPurchase.consume(type, receipt, signature);
  286. done();
  287. } catch (err) {
  288. done(err);
  289. }
  290. });
  291. it('should return an errorCode property when there is an error', async(done) => {
  292. try {
  293. const receipt = '_some_receipt_';
  294. const signature = '_some_signature_';
  295. const type = 'inapp';
  296. GLOBAL.window.cordova.exec = execError(-1);
  297. await inAppPurchase.consume(type, receipt, signature);
  298. done(new Error('Call to #consume() suceeded but was expected to fail.'));
  299. } catch (err) {
  300. assert(err.errorCode === -1, 'should create an errorCode property');
  301. done();
  302. }
  303. });
  304. });
  305. describe('#restorePurchases()', () => {
  306. it('should initialize the Android plugin', async (done) => {
  307. try {
  308. let initCalled = false;
  309. GLOBAL.window.cordova.exec = (success, err, pluginName, name, args) => {
  310. if (name === 'init') {
  311. assert(typeof success === 'function', 'should define a success callback');
  312. assert(typeof err === 'function', 'should define an error callback');
  313. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  314. assert(args.length === 0, 'args should be empty');
  315. initCalled = true;
  316. success();
  317. } else if (name === 'restorePurchases') {
  318. success([]);
  319. }
  320. };
  321. await inAppPurchase.restorePurchases();
  322. assert(initCalled, 'init() should be called');
  323. done();
  324. } catch (err) {
  325. done(err);
  326. }
  327. });
  328. it('should call the Android restorePurchases() function with the correct args ', async (done) => {
  329. try {
  330. GLOBAL.window.cordova.exec = (success, err, pluginName, name) => {
  331. if (name === 'restorePurchases') {
  332. assert(typeof success === 'function', 'should define a success callback');
  333. assert(typeof err === 'function', 'should define an error callback');
  334. assert(pluginName === 'InAppBillingV3', 'invalid Android plugin name');
  335. success([{}]);
  336. } else if (name === 'init') {
  337. success();
  338. }
  339. };
  340. await inAppPurchase.restorePurchases();
  341. done();
  342. } catch (err) {
  343. done(err);
  344. }
  345. });
  346. it('should return an array of objects with the correct attributes', async (done) => {
  347. const productId = 'com.test.prod1';
  348. const state = 0;
  349. const date = new Date();
  350. const type = 'inapp';
  351. try {
  352. GLOBAL.window.cordova.exec = (success, err, pluginName, name) => {
  353. if (name === 'restorePurchases') {
  354. success([{ productId, state, date, type }]);
  355. } else if (name === 'init') {
  356. success();
  357. }
  358. };
  359. const res = await inAppPurchase.restorePurchases();
  360. assert(res[0].productId === productId);
  361. assert(res[0].state === state);
  362. assert(res[0].date === date);
  363. assert(res[0].type === type);
  364. done();
  365. } catch (err) {
  366. done(err);
  367. }
  368. });
  369. it('should return an errorCode property when there is an error', async(done) => {
  370. try {
  371. GLOBAL.window.cordova.exec = execError(-1);
  372. await inAppPurchase.restorePurchases();
  373. done(new Error('Call to #restorePurchases() suceeded but was expected to fail.'));
  374. } catch (err) {
  375. assert(err.errorCode === -1, 'should create an errorCode property');
  376. done();
  377. }
  378. });
  379. });
  380. describe('#getReceipt()', () => {
  381. it('should always successfully resolve without doing anything', async (done) => {
  382. try {
  383. await inAppPurchase.getReceipt();
  384. done();
  385. } catch (err) {
  386. done(err);
  387. }
  388. });
  389. });
  390. });