implement refunds
This commit is contained in:
parent
21c176a69e
commit
8697efd2c8
2
node_modules/.bin/tsc
generated
vendored
2
node_modules/.bin/tsc
generated
vendored
@ -1 +1 @@
|
||||
../typedoc/node_modules/typescript/bin/tsc
|
||||
../typescript/bin/tsc
|
2
node_modules/.bin/tsserver
generated
vendored
2
node_modules/.bin/tsserver
generated
vendored
@ -1 +1 @@
|
||||
../typedoc/node_modules/typescript/bin/tsserver
|
||||
../typescript/bin/tsserver
|
2
node_modules/.bin/uglifyjs
generated
vendored
2
node_modules/.bin/uglifyjs
generated
vendored
@ -1 +1 @@
|
||||
../handlebars/node_modules/uglify-js/bin/uglifyjs
|
||||
../uglify-js/bin/uglifyjs
|
0
node_modules/handlebars/node_modules/uglify-js/bin/uglifyjs
generated
vendored
Executable file → Normal file
0
node_modules/handlebars/node_modules/uglify-js/bin/uglifyjs
generated
vendored
Executable file → Normal file
97
node_modules/nyc/node_modules/md5-hex/package.json
generated
vendored
97
node_modules/nyc/node_modules/md5-hex/package.json
generated
vendored
@ -1,82 +1,25 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
{
|
||||
"raw": "md5-hex@^1.2.0",
|
||||
"scope": null,
|
||||
"escapedName": "md5-hex",
|
||||
"name": "md5-hex",
|
||||
"rawSpec": "^1.2.0",
|
||||
"spec": ">=1.2.0 <2.0.0",
|
||||
"type": "range"
|
||||
},
|
||||
"/Users/benjamincoe/bcoe/nyc"
|
||||
]
|
||||
],
|
||||
"_from": "md5-hex@>=1.2.0 <2.0.0",
|
||||
"_id": "md5-hex@1.3.0",
|
||||
"_inCache": true,
|
||||
"_location": "/md5-hex",
|
||||
"_nodeVersion": "4.4.2",
|
||||
"_npmOperationalInternal": {
|
||||
"host": "packages-12-west.internal.npmjs.com",
|
||||
"tmp": "tmp/md5-hex-1.3.0.tgz_1460471196734_0.9732175024691969"
|
||||
},
|
||||
"_npmUser": {
|
||||
"name": "sindresorhus",
|
||||
"email": "sindresorhus@gmail.com"
|
||||
},
|
||||
"_npmVersion": "2.15.0",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"raw": "md5-hex@^1.2.0",
|
||||
"scope": null,
|
||||
"escapedName": "md5-hex",
|
||||
"name": "md5-hex",
|
||||
"rawSpec": "^1.2.0",
|
||||
"spec": ">=1.2.0 <2.0.0",
|
||||
"type": "range"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/",
|
||||
"/caching-transform"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz",
|
||||
"_shasum": "d2c4afe983c4370662179b8cad145219135046c4",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "md5-hex@^1.2.0",
|
||||
"_where": "/Users/benjamincoe/bcoe/nyc",
|
||||
"name": "md5-hex",
|
||||
"version": "1.3.0",
|
||||
"description": "Create a MD5 hash with hex encoding",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/md5-hex",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "sindresorhus.com"
|
||||
},
|
||||
"browser": "browser.js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sindresorhus/md5-hex/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"md5-o-matic": "^0.1.1"
|
||||
},
|
||||
"description": "Create a MD5 hash with hex encoding",
|
||||
"devDependencies": {
|
||||
"ava": "*",
|
||||
"xo": "*"
|
||||
},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"shasum": "d2c4afe983c4370662179b8cad145219135046c4",
|
||||
"tarball": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"browser.js"
|
||||
],
|
||||
"gitHead": "273d9c659a29e4cd53512f526282afd5ac1c1413",
|
||||
"homepage": "https://github.com/sindresorhus/md5-hex#readme",
|
||||
"keywords": [
|
||||
"hash",
|
||||
"crypto",
|
||||
@ -86,23 +29,11 @@
|
||||
"browser",
|
||||
"browserify"
|
||||
],
|
||||
"license": "MIT",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "sindresorhus",
|
||||
"email": "sindresorhus@gmail.com"
|
||||
}
|
||||
],
|
||||
"name": "md5-hex",
|
||||
"optionalDependencies": {},
|
||||
"readme": "# md5-hex [![Build Status](https://travis-ci.org/sindresorhus/md5-hex.svg?branch=master)](https://travis-ci.org/sindresorhus/md5-hex)\n\n> Create a MD5 hash with hex encoding\n\n*Please don't use MD5 hashes for anything sensitive!*\n\nCheckout [`hasha`](https://github.com/sindresorhus/hasha) if you need something more flexible.\n\n\n## Install\n\n```\n$ npm install --save md5-hex\n```\n\n\n## Usage\n\n```js\nconst fs = require('fs');\nconst md5Hex = require('md5-hex');\nconst buffer = fs.readFileSync('unicorn.png');\n\nmd5Hex(buffer);\n//=> '1abcb33beeb811dca15f0ac3e47b88d9'\n```\n\n\n## API\n\n### md5Hex(input)\n\n#### input\n\nType: `buffer` `string` `array[string|buffer]`\n\nPrefer buffers as they're faster to hash, but strings can be useful for small things.\n\nPass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation.\n\n\n## License\n\nMIT © [Sindre Sorhus](https://sindresorhus.com)\n",
|
||||
"readmeFilename": "readme.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sindresorhus/md5-hex.git"
|
||||
"dependencies": {
|
||||
"md5-o-matic": "^0.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"version": "1.3.0"
|
||||
"devDependencies": {
|
||||
"ava": "*",
|
||||
"xo": "*"
|
||||
}
|
||||
}
|
||||
|
89
node_modules/nyc/node_modules/resolve-from/package.json
generated
vendored
89
node_modules/nyc/node_modules/resolve-from/package.json
generated
vendored
@ -1,73 +1,23 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
{
|
||||
"raw": "resolve-from@^2.0.0",
|
||||
"scope": null,
|
||||
"escapedName": "resolve-from",
|
||||
"name": "resolve-from",
|
||||
"rawSpec": "^2.0.0",
|
||||
"spec": ">=2.0.0 <3.0.0",
|
||||
"type": "range"
|
||||
},
|
||||
"/Users/benjamincoe/bcoe/nyc"
|
||||
]
|
||||
],
|
||||
"_from": "resolve-from@>=2.0.0 <3.0.0",
|
||||
"_id": "resolve-from@2.0.0",
|
||||
"_inCache": true,
|
||||
"_location": "/resolve-from",
|
||||
"_nodeVersion": "4.2.1",
|
||||
"_npmUser": {
|
||||
"name": "sindresorhus",
|
||||
"email": "sindresorhus@gmail.com"
|
||||
},
|
||||
"_npmVersion": "2.14.7",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"raw": "resolve-from@^2.0.0",
|
||||
"scope": null,
|
||||
"escapedName": "resolve-from",
|
||||
"name": "resolve-from",
|
||||
"rawSpec": "^2.0.0",
|
||||
"spec": ">=2.0.0 <3.0.0",
|
||||
"type": "range"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
|
||||
"_shasum": "9480ab20e94ffa1d9e80a804c7ea147611966b57",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "resolve-from@^2.0.0",
|
||||
"_where": "/Users/benjamincoe/bcoe/nyc",
|
||||
"name": "resolve-from",
|
||||
"version": "2.0.0",
|
||||
"description": "Resolve the path of a module like require.resolve() but from a given path",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/resolve-from",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "sindresorhus.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/sindresorhus/resolve-from/issues"
|
||||
},
|
||||
"dependencies": {},
|
||||
"description": "Resolve the path of a module like require.resolve() but from a given path",
|
||||
"devDependencies": {
|
||||
"ava": "*",
|
||||
"xo": "*"
|
||||
},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"shasum": "9480ab20e94ffa1d9e80a804c7ea147611966b57",
|
||||
"tarball": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"gitHead": "583e0f8df06e1bc4d1c96d8d4f2484c745f522c3",
|
||||
"homepage": "https://github.com/sindresorhus/resolve-from#readme",
|
||||
"keywords": [
|
||||
"require",
|
||||
"resolve",
|
||||
@ -77,23 +27,8 @@
|
||||
"like",
|
||||
"path"
|
||||
],
|
||||
"license": "MIT",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "sindresorhus",
|
||||
"email": "sindresorhus@gmail.com"
|
||||
}
|
||||
],
|
||||
"name": "resolve-from",
|
||||
"optionalDependencies": {},
|
||||
"readme": "# resolve-from [![Build Status](https://travis-ci.org/sindresorhus/resolve-from.svg?branch=master)](https://travis-ci.org/sindresorhus/resolve-from)\n\n> Resolve the path of a module like [`require.resolve()`](http://nodejs.org/api/globals.html#globals_require_resolve) but from a given path\n\nUnlike `require.resolve()` it returns `null` instead of throwing when the module can't be found.\n\n\n## Install\n\n```\n$ npm install --save resolve-from\n```\n\n\n## Usage\n\n```js\nconst resolveFrom = require('resolve-from');\n\n// there's a file at `./foo/bar.js`\n\nresolveFrom('foo', './bar');\n//=> '/Users/sindresorhus/dev/test/foo/bar.js'\n```\n\n\n## API\n\n### resolveFrom(fromDir, moduleId)\n\n#### fromDir\n\nType: `string`\n\nDirectory to resolve from.\n\n#### moduleId\n\nType: `string`\n\nWhat you would use in `require()`.\n\n\n## Tip\n\nCreate a partial using a bound function if you want to require from the same `fromDir` multiple times:\n\n```js\nconst resolveFromFoo = resolveFrom.bind(null, 'foo');\n\nresolveFromFoo('./bar');\nresolveFromFoo('./baz');\n```\n\n\n## License\n\nMIT © [Sindre Sorhus](http://sindresorhus.com)\n",
|
||||
"readmeFilename": "readme.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sindresorhus/resolve-from.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"version": "2.0.0"
|
||||
"devDependencies": {
|
||||
"ava": "*",
|
||||
"xo": "*"
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^16.0.2",
|
||||
"@types/react-dom": "^15.5.2"
|
||||
"@types/react-dom": "^15.5.2",
|
||||
"axios": "^0.16.2"
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +271,7 @@ namespace RpcFunctions {
|
||||
const newAmount = new native.Amount(cd.coin.currentAmount);
|
||||
newAmount.sub(coinSpend);
|
||||
cd.coin.currentAmount = newAmount.toJson();
|
||||
cd.coin.status = CoinStatus.TransactionPending;
|
||||
cd.coin.status = CoinStatus.PurchasePending;
|
||||
|
||||
const d = new native.DepositRequestPS({
|
||||
amount_with_fee: coinSpend.toNbo(),
|
||||
|
@ -658,13 +658,13 @@ export class QueryRoot {
|
||||
/**
|
||||
* Get, modify and store an element inside a transaction.
|
||||
*/
|
||||
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
|
||||
mutate<T>(store: Store<T>, key: any, f: (v: T|undefined) => T|undefined): QueryRoot {
|
||||
this.checkFinished();
|
||||
const doPut = (tx: IDBTransaction) => {
|
||||
const reqGet = tx.objectStore(store.name).get(key);
|
||||
reqGet.onsuccess = () => {
|
||||
const r = reqGet.result;
|
||||
let m: T;
|
||||
let m: T|undefined;
|
||||
try {
|
||||
m = f(r);
|
||||
} catch (e) {
|
||||
@ -674,8 +674,9 @@ export class QueryRoot {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
tx.objectStore(store.name).put(m);
|
||||
if (m !== undefined && m !== null) {
|
||||
tx.objectStore(store.name).put(m);
|
||||
}
|
||||
};
|
||||
};
|
||||
this.scheduleFinish();
|
||||
|
32
src/types.ts
32
src/types.ts
@ -798,9 +798,9 @@ export enum CoinStatus {
|
||||
*/
|
||||
Fresh,
|
||||
/**
|
||||
* Currently planned to be sent to a merchant for a transaction.
|
||||
* Currently planned to be sent to a merchant for a purchase.
|
||||
*/
|
||||
TransactionPending,
|
||||
PurchasePending,
|
||||
/**
|
||||
* Used for a completed transaction and now dirty.
|
||||
*/
|
||||
@ -1662,3 +1662,31 @@ export class ReturnCoinsRequest {
|
||||
*/
|
||||
static checked: (obj: any) => ReturnCoinsRequest;
|
||||
}
|
||||
|
||||
|
||||
export interface RefundPermission {
|
||||
refund_amount: AmountJson;
|
||||
refund_fee: AmountJson;
|
||||
h_contract_terms: string;
|
||||
coin_pub: string;
|
||||
rtransaction_id: number;
|
||||
merchant_pub: string;
|
||||
merchant_sig: string;
|
||||
}
|
||||
|
||||
|
||||
export interface PurchaseRecord {
|
||||
contractTermsHash: string;
|
||||
contractTerms: ContractTerms;
|
||||
payReq: PayReq;
|
||||
merchantSig: string;
|
||||
|
||||
/**
|
||||
* The purchase isn't active anymore, it's either successfully paid or
|
||||
* refunded/aborted.
|
||||
*/
|
||||
finished: boolean;
|
||||
|
||||
refundsPending: { [refundSig: string]: RefundPermission };
|
||||
refundsDone: { [refundSig: string]: RefundPermission };
|
||||
}
|
||||
|
155
src/wallet.ts
155
src/wallet.ts
@ -82,6 +82,8 @@ import {
|
||||
WalletBalanceEntry,
|
||||
WireFee,
|
||||
WireInfo,
|
||||
RefundPermission,
|
||||
PurchaseRecord,
|
||||
} from "./types";
|
||||
import URI = require("urijs");
|
||||
|
||||
@ -241,19 +243,6 @@ class WireDetailJson {
|
||||
}
|
||||
|
||||
|
||||
interface TransactionRecord {
|
||||
contractTermsHash: string;
|
||||
contractTerms: ContractTerms;
|
||||
payReq: PayReq;
|
||||
merchantSig: string;
|
||||
|
||||
/**
|
||||
* The transaction isn't active anymore, it's either successfully paid
|
||||
* or refunded/aborted.
|
||||
*/
|
||||
finished: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Badge that shows activity for the wallet.
|
||||
@ -516,13 +505,13 @@ export namespace Stores {
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionsStore extends Store<TransactionRecord> {
|
||||
class PurchasesStore extends Store<PurchaseRecord> {
|
||||
constructor() {
|
||||
super("transactions", {keyPath: "contractTermsHash"});
|
||||
super("purchases", {keyPath: "contractTermsHash"});
|
||||
}
|
||||
|
||||
fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
|
||||
orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contractTerms.order_id");
|
||||
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
|
||||
orderIdIndex = new Index<string, PurchaseRecord>(this, "order_id", "contractTerms.order_id");
|
||||
}
|
||||
|
||||
class DenominationsStore extends Store<DenominationRecord> {
|
||||
@ -568,7 +557,7 @@ export namespace Stores {
|
||||
export const proposals = new ProposalsStore();
|
||||
export const refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "meltCoinPub"});
|
||||
export const reserves = new Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"});
|
||||
export const transactions = new TransactionsStore();
|
||||
export const purchases = new PurchasesStore();
|
||||
}
|
||||
|
||||
/* tslint:enable:completed-docs */
|
||||
@ -909,12 +898,14 @@ export class Wallet {
|
||||
merchant_pub: proposal.contractTerms.merchant_pub,
|
||||
order_id: proposal.contractTerms.order_id,
|
||||
};
|
||||
const t: TransactionRecord = {
|
||||
const t: PurchaseRecord = {
|
||||
contractTerms: proposal.contractTerms,
|
||||
contractTermsHash: proposal.contractTermsHash,
|
||||
finished: false,
|
||||
merchantSig: proposal.merchantSig,
|
||||
payReq,
|
||||
refundsDone: {},
|
||||
refundsPending: {},
|
||||
};
|
||||
|
||||
const historyEntry: HistoryRecord = {
|
||||
@ -931,7 +922,7 @@ export class Wallet {
|
||||
};
|
||||
|
||||
await this.q()
|
||||
.put(Stores.transactions, t)
|
||||
.put(Stores.purchases, t)
|
||||
.put(Stores.history, historyEntry)
|
||||
.putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
|
||||
.finish();
|
||||
@ -972,9 +963,9 @@ export class Wallet {
|
||||
throw Error(`proposal with id ${proposalId} not found`);
|
||||
}
|
||||
|
||||
const transaction = await this.q().get(Stores.transactions, proposal.contractTermsHash);
|
||||
const purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
|
||||
|
||||
if (transaction) {
|
||||
if (purchase) {
|
||||
// Already payed ...
|
||||
return "paid";
|
||||
}
|
||||
@ -1017,8 +1008,8 @@ export class Wallet {
|
||||
}
|
||||
|
||||
// First check if we already payed for it.
|
||||
const transaction = await this.q().get(Stores.transactions, proposal.contractTermsHash);
|
||||
if (transaction) {
|
||||
const purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
|
||||
if (purchase) {
|
||||
return "paid";
|
||||
}
|
||||
|
||||
@ -1049,7 +1040,7 @@ export class Wallet {
|
||||
async queryPayment(url: string): Promise<QueryPaymentResult> {
|
||||
console.log("query for payment", url);
|
||||
|
||||
const t = await this.q().getIndexed(Stores.transactions.fulfillmentUrlIndex, url);
|
||||
const t = await this.q().getIndexed(Stores.purchases.fulfillmentUrlIndex, url);
|
||||
|
||||
if (!t) {
|
||||
console.log("query for payment failed");
|
||||
@ -1890,7 +1881,7 @@ export class Wallet {
|
||||
return balance;
|
||||
}
|
||||
|
||||
function collectPayments(t: TransactionRecord, balance: WalletBalance) {
|
||||
function collectPayments(t: PurchaseRecord, balance: WalletBalance) {
|
||||
if (t.finished) {
|
||||
return balance;
|
||||
}
|
||||
@ -1934,7 +1925,7 @@ export class Wallet {
|
||||
.reduce(collectPendingWithdraw, balance);
|
||||
tx.iter(Stores.reserves)
|
||||
.reduce(collectPaybacks, balance);
|
||||
tx.iter(Stores.transactions)
|
||||
tx.iter(Stores.purchases)
|
||||
.reduce(collectPayments, balance);
|
||||
await tx.finish();
|
||||
return balance;
|
||||
@ -2282,7 +2273,7 @@ export class Wallet {
|
||||
|
||||
async paymentSucceeded(contractTermsHash: string, merchantSig: string): Promise<any> {
|
||||
const doPaymentSucceeded = async() => {
|
||||
const t = await this.q().get<TransactionRecord>(Stores.transactions,
|
||||
const t = await this.q().get<PurchaseRecord>(Stores.purchases,
|
||||
contractTermsHash);
|
||||
if (!t) {
|
||||
console.error("contract not found");
|
||||
@ -2309,7 +2300,7 @@ export class Wallet {
|
||||
|
||||
await this.q()
|
||||
.putAll(Stores.coins, modifiedCoins)
|
||||
.put(Stores.transactions, t)
|
||||
.put(Stores.purchases, t)
|
||||
.finish();
|
||||
for (const c of t.payReq.coins) {
|
||||
this.refresh(c.coin_pub);
|
||||
@ -2560,4 +2551,110 @@ export class Wallet {
|
||||
await this.q().put(Stores.coinsReturns, currentCrr);
|
||||
}
|
||||
}
|
||||
|
||||
async acceptRefund(refundPermissions: RefundPermission[]): Promise<void> {
|
||||
if (!refundPermissions.length) {
|
||||
console.warn("got empty refund list");
|
||||
return;
|
||||
}
|
||||
const hc = refundPermissions[0].h_contract_terms;
|
||||
if (!hc) {
|
||||
throw Error("h_contract_terms missing in refund permission");
|
||||
}
|
||||
const m = refundPermissions[0].merchant_pub;
|
||||
if (!hc) {
|
||||
throw Error("merchant_pub missing in refund permission");
|
||||
}
|
||||
for (const perm of refundPermissions) {
|
||||
if (perm.h_contract_terms !== hc) {
|
||||
throw Error("h_contract_terms different in refund permission");
|
||||
}
|
||||
if (perm.merchant_pub !== m) {
|
||||
throw Error("merchant_pub different in refund permission");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add refund to purchase if not already added.
|
||||
*/
|
||||
function f(t: PurchaseRecord|undefined): PurchaseRecord|undefined {
|
||||
if (!t) {
|
||||
console.error("purchase not found, not adding refunds");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const perm of refundPermissions) {
|
||||
if (!t.refundsPending[perm.merchant_sig] && !t.refundsDone[perm.merchant_sig]) {
|
||||
t.refundsPending[perm.merchant_sig] = perm;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// Add the refund permissions to the purchase within a DB transaction
|
||||
await this.q().mutate(Stores.purchases, hc, f).finish();
|
||||
this.notifier.notify();
|
||||
|
||||
// Start submitting it but don't wait for it here.
|
||||
this.submitRefunds(hc);
|
||||
}
|
||||
|
||||
async submitRefunds(contractTermsHash: string): Promise<void> {
|
||||
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
|
||||
if (!purchase) {
|
||||
console.error("not submitting refunds, contract terms not found:", contractTermsHash);
|
||||
return;
|
||||
}
|
||||
const pendingKeys = Object.keys(purchase.refundsPending);
|
||||
if (pendingKeys.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const pk of pendingKeys) {
|
||||
const perm = purchase.refundsPending[pk];
|
||||
console.log("sending refund permission", perm);
|
||||
const reqUrl = (new URI("refund")).absoluteTo(purchase.payReq.exchange);
|
||||
const resp = await this.http.postJson(reqUrl.href(), perm);
|
||||
if (resp.status !== 200) {
|
||||
console.error("refund failed", resp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transactionally mark successful refunds as done
|
||||
const transformPurchase = (t: PurchaseRecord|undefined): PurchaseRecord|undefined => {
|
||||
if (!t) {
|
||||
console.warn("purchase not found, not updating refund");
|
||||
return;
|
||||
}
|
||||
if (t.refundsPending[pk]) {
|
||||
t.refundsDone[pk] = t.refundsPending[pk];
|
||||
delete t.refundsPending[pk];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
const transformCoin = (c: CoinRecord|undefined): CoinRecord|undefined => {
|
||||
if (!c) {
|
||||
console.warn("coin not found, can't apply refund");
|
||||
return;
|
||||
}
|
||||
c.status = CoinStatus.Dirty;
|
||||
c.currentAmount = Amounts.add(c.currentAmount, perm.refund_amount).amount;
|
||||
c.currentAmount = Amounts.sub(c.currentAmount, perm.refund_fee).amount;
|
||||
|
||||
return c;
|
||||
};
|
||||
|
||||
|
||||
await this.q()
|
||||
.mutate(Stores.purchases, contractTermsHash, transformPurchase)
|
||||
.mutate(Stores.coins, perm.coin_pub, transformCoin)
|
||||
.finish();
|
||||
this.refresh(perm.coin_pub);
|
||||
}
|
||||
|
||||
this.notifier.notify();
|
||||
}
|
||||
|
||||
async getPurchase(contractTermsHash: string): Promise<PurchaseRecord|undefined> {
|
||||
return this.q().get(Stores.purchases, contractTermsHash);
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +184,14 @@ export interface MessageMap {
|
||||
request: { reportUid: string };
|
||||
response: void;
|
||||
};
|
||||
"accept-refund": {
|
||||
request: any;
|
||||
response: void;
|
||||
};
|
||||
"get-purchase": {
|
||||
request: any;
|
||||
response: void;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,8 @@ import wxApi = require("./wxApi");
|
||||
|
||||
import { QueryPaymentResult } from "../types";
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
declare var cloneInto: any;
|
||||
|
||||
let logVerbose: boolean = false;
|
||||
@ -98,85 +100,38 @@ function setStyles(installed: boolean) {
|
||||
}
|
||||
|
||||
|
||||
function handlePaymentResponse(maybeFoundResponse: QueryPaymentResult) {
|
||||
async function handlePaymentResponse(maybeFoundResponse: QueryPaymentResult) {
|
||||
if (!maybeFoundResponse.found) {
|
||||
console.log("pay-failed", {hint: "payment not found in the wallet"});
|
||||
return;
|
||||
}
|
||||
const walletResp = maybeFoundResponse;
|
||||
/**
|
||||
* Handle a failed payment.
|
||||
*
|
||||
* Try to notify the wallet first, before we show a potentially
|
||||
* synchronous error message (such as an alert) or leave the page.
|
||||
*/
|
||||
async function handleFailedPayment(r: XMLHttpRequest) {
|
||||
let timeoutHandle: number|null = null;
|
||||
function err() {
|
||||
// FIXME: proper error reporting!
|
||||
console.log("pay-failed", {status: r.status, response: r.responseText});
|
||||
}
|
||||
function onTimeout() {
|
||||
timeoutHandle = null;
|
||||
err();
|
||||
}
|
||||
timeoutHandle = window.setTimeout(onTimeout, 200);
|
||||
|
||||
await wxApi.paymentFailed(walletResp.contractTermsHash);
|
||||
if (timeoutHandle !== null) {
|
||||
clearTimeout(timeoutHandle);
|
||||
timeoutHandle = null;
|
||||
}
|
||||
err();
|
||||
}
|
||||
|
||||
logVerbose && console.log("handling taler-notify-payment: ", walletResp);
|
||||
// Payment timeout in ms.
|
||||
let timeout_ms = 1000;
|
||||
// Current request.
|
||||
let r: XMLHttpRequest|null;
|
||||
let timeoutHandle: number|null = null;
|
||||
function sendPay() {
|
||||
r = new XMLHttpRequest();
|
||||
r.open("post", walletResp.contractTerms.pay_url);
|
||||
r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
r.send(JSON.stringify(walletResp.payReq));
|
||||
r.onload = async () => {
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
switch (r.status) {
|
||||
case 200:
|
||||
const merchantResp = JSON.parse(r.responseText);
|
||||
logVerbose && console.log("got success from pay_url");
|
||||
await wxApi.paymentSucceeded(walletResp.contractTermsHash, merchantResp.sig);
|
||||
const nextUrl = walletResp.contractTerms.fulfillment_url;
|
||||
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
|
||||
window.location.href = nextUrl;
|
||||
window.location.reload(true);
|
||||
break;
|
||||
default:
|
||||
handleFailedPayment(r);
|
||||
break;
|
||||
}
|
||||
r = null;
|
||||
if (timeoutHandle !== null) {
|
||||
clearTimeout(timeoutHandle!);
|
||||
timeoutHandle = null;
|
||||
}
|
||||
};
|
||||
function retry() {
|
||||
if (r) {
|
||||
r.abort();
|
||||
r = null;
|
||||
}
|
||||
timeout_ms = Math.min(timeout_ms * 2, 10 * 1000);
|
||||
logVerbose && console.log("sendPay timed out, retrying in ", timeout_ms, "ms");
|
||||
sendPay();
|
||||
let resp;
|
||||
try {
|
||||
const config = {
|
||||
timeout: 5000, /* 5 seconds */
|
||||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||||
validateStatus: (s: number) => s == 200,
|
||||
}
|
||||
timeoutHandle = window.setTimeout(retry, timeout_ms);
|
||||
resp = await axios.post(walletResp.contractTerms.pay_url, walletResp.payReq, config);
|
||||
} catch (e) {
|
||||
// Gives the user the option to retry / abort and refresh
|
||||
wxApi.logAndDisplayError({
|
||||
name: "pay-post-failed",
|
||||
message: e.message,
|
||||
response: e.response,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
sendPay();
|
||||
const merchantResp = resp.data;
|
||||
logVerbose && console.log("got success from pay_url");
|
||||
await wxApi.paymentSucceeded(walletResp.contractTermsHash, merchantResp.sig);
|
||||
const nextUrl = walletResp.contractTerms.fulfillment_url;
|
||||
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
|
||||
window.location.href = nextUrl;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
|
||||
@ -233,53 +188,24 @@ function init() {
|
||||
|
||||
type HandlerFn = (detail: any, sendResponse: (msg: any) => void) => void;
|
||||
|
||||
function downloadContract(url: string, nonce: string): Promise<any> {
|
||||
async function downloadContract(url: string, nonce: string): Promise<any> {
|
||||
const parsed_url = new URI(url);
|
||||
url = parsed_url.setQuery({nonce}).href();
|
||||
// FIXME: include and check nonce!
|
||||
return new Promise((resolve, reject) => {
|
||||
const contract_request = new XMLHttpRequest();
|
||||
console.log("downloading contract from '" + url + "'");
|
||||
contract_request.open("GET", url, true);
|
||||
contract_request.onload = (e) => {
|
||||
if (contract_request.readyState === 4) {
|
||||
if (contract_request.status === 200) {
|
||||
console.log("response text:",
|
||||
contract_request.responseText);
|
||||
const contract_wrapper = JSON.parse(contract_request.responseText);
|
||||
if (!contract_wrapper) {
|
||||
console.error("response text was invalid json");
|
||||
const detail = {
|
||||
body: contract_request.responseText,
|
||||
hint: "invalid json",
|
||||
status: contract_request.status,
|
||||
};
|
||||
reject(detail);
|
||||
return;
|
||||
}
|
||||
resolve(contract_wrapper);
|
||||
} else {
|
||||
const detail = {
|
||||
body: contract_request.responseText,
|
||||
hint: "contract download failed",
|
||||
status: contract_request.status,
|
||||
};
|
||||
reject(detail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
contract_request.onerror = (e) => {
|
||||
const detail = {
|
||||
body: contract_request.responseText,
|
||||
hint: "contract download failed",
|
||||
status: contract_request.status,
|
||||
};
|
||||
reject(detail);
|
||||
return;
|
||||
};
|
||||
contract_request.send();
|
||||
});
|
||||
console.log("downloading contract from '" + url + "'");
|
||||
let resp;
|
||||
try {
|
||||
resp = await axios.get(url, { validateStatus: (s) => s == 200 });
|
||||
} catch (e) {
|
||||
wxApi.logAndDisplayError({
|
||||
name: "contract-download-failed",
|
||||
message: e.message,
|
||||
response: e.response,
|
||||
sameTab: true,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
console.log("got response", resp);
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
async function processProposal(proposal: any) {
|
||||
@ -328,8 +254,38 @@ async function processProposal(proposal: any) {
|
||||
document.location.replace(target);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a payment request (coming either from an HTTP 402 or
|
||||
* the JS wallet API).
|
||||
*/
|
||||
function talerPay(msg: any): Promise<any> {
|
||||
// Use a promise directly instead of of an async
|
||||
// function since some paths never resolve the promise.
|
||||
return new Promise(async(resolve, reject) => {
|
||||
if (msg.refund_url) {
|
||||
console.log("processing refund");
|
||||
let resp;
|
||||
try {
|
||||
const config = {
|
||||
validateStatus: (s: number) => s == 200,
|
||||
}
|
||||
resp = await axios.get(msg.refund_url, config);
|
||||
} catch (e) {
|
||||
wxApi.logAndDisplayError({
|
||||
name: "refund-download-failed",
|
||||
message: e.message,
|
||||
response: e.response,
|
||||
sameTab: true,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
await wxApi.acceptRefund(resp.data);
|
||||
const hc = resp.data.refund_permissions[0].h_contract_terms;
|
||||
document.location.href = chrome.extension.getURL(`/src/webex/pages/refund.html?contractTermsHash=${hc}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// current URL without fragment
|
||||
const url = new URI(document.location.href).fragment("").href();
|
||||
const res = await wxApi.queryPayment(url);
|
||||
|
18
src/webex/pages/refund.html
Normal file
18
src/webex/pages/refund.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Taler Wallet: Refund Status</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
||||
|
||||
<link rel="icon" href="/img/icon.png">
|
||||
|
||||
<script src="/dist/page-common-bundle.js"></script>
|
||||
<script src="/dist/refund-bundle.js"></script>
|
||||
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
138
src/webex/pages/refund.tsx
Normal file
138
src/webex/pages/refund.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2015-2016 GNUnet e.V.
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Page that shows refund status for purchases.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import URI = require("urijs");
|
||||
|
||||
import * as wxApi from "../wxApi";
|
||||
import * as types from "../../types";
|
||||
|
||||
import { AmountDisplay } from "../renderHtml";
|
||||
|
||||
interface RefundStatusViewProps {
|
||||
contractTermsHash: string;
|
||||
}
|
||||
|
||||
interface RefundStatusViewState {
|
||||
purchase?: types.PurchaseRecord;
|
||||
gotResult: boolean;
|
||||
}
|
||||
|
||||
|
||||
const RefundDetail = ({purchase}: {purchase: types.PurchaseRecord}) => {
|
||||
const pendingKeys = Object.keys(purchase.refundsPending);
|
||||
const doneKeys = Object.keys(purchase.refundsDone);
|
||||
if (pendingKeys.length == 0 && doneKeys.length == 0) {
|
||||
return <p>No refunds</p>;
|
||||
}
|
||||
|
||||
const currency = { ...purchase.refundsDone, ...purchase.refundsPending }[([...pendingKeys, ...doneKeys][0])].refund_amount.currency;
|
||||
if (!currency) {
|
||||
throw Error("invariant");
|
||||
}
|
||||
|
||||
let amountPending = types.Amounts.getZero(currency);
|
||||
let feesPending = types.Amounts.getZero(currency)
|
||||
for (let k of pendingKeys) {
|
||||
amountPending = types.Amounts.add(amountPending, purchase.refundsPending[k].refund_amount).amount;
|
||||
feesPending = types.Amounts.add(feesPending, purchase.refundsPending[k].refund_fee).amount;
|
||||
}
|
||||
let amountDone = types.Amounts.getZero(currency);
|
||||
let feesDone = types.Amounts.getZero(currency);
|
||||
for (let k of doneKeys) {
|
||||
amountDone = types.Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount;
|
||||
feesDone = types.Amounts.add(feesDone, purchase.refundsDone[k].refund_fee).amount;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Refund fully received: <AmountDisplay amount={amountDone} /> (refund fees: <AmountDisplay amount={feesDone} />)</p>
|
||||
<p>Refund incoming: <AmountDisplay amount={amountPending} /> (refund fees: <AmountDisplay amount={feesPending} />)</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStatusViewState> {
|
||||
|
||||
constructor(props: RefundStatusViewProps) {
|
||||
super(props);
|
||||
this.state = { gotResult: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.update();
|
||||
const port = chrome.runtime.connect();
|
||||
port.onMessage.addListener((msg: any) => {
|
||||
if (msg.notify) {
|
||||
console.log("got notified");
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const purchase = this.state.purchase;
|
||||
if (!purchase) {
|
||||
if (this.state.gotResult) {
|
||||
return <span>No purchase with contract terms hash {this.props.contractTermsHash} found</span>;
|
||||
} else {
|
||||
return <span>...</span>;
|
||||
}
|
||||
}
|
||||
const merchantName = purchase.contractTerms.merchant.name || "(unknown)";
|
||||
const summary = purchase.contractTerms.summary || purchase.contractTerms.order_id;
|
||||
return (
|
||||
<div id="main">
|
||||
<h1>Refund Status</h1>
|
||||
<p>Status of purchase <strong>{summary}</strong> from merchant <strong>{merchantName}</strong> (order id {purchase.contractTerms.order_id}).</p>
|
||||
<p>Total amount: <AmountDisplay amount={purchase.contractTerms.amount} /></p>
|
||||
{purchase.finished ? <RefundDetail purchase={purchase} /> : <p>Purchase not completed.</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async update() {
|
||||
const purchase = await wxApi.getPurchase(this.props.contractTermsHash);
|
||||
console.log("got purchase", purchase);
|
||||
this.setState({ purchase, gotResult: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
const url = new URI(document.location.href);
|
||||
const query: any = URI.parseQuery(url.query());
|
||||
|
||||
const container = document.getElementById("container");
|
||||
if (!container) {
|
||||
console.error("fatal: can't mount component, countainer missing");
|
||||
return;
|
||||
}
|
||||
|
||||
const contractTermsHash = query.contractTermsHash || "(none)";
|
||||
ReactDOM.render(<RefundStatusView contractTermsHash={contractTermsHash} />, container);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => main());
|
@ -73,6 +73,8 @@ export function renderAmount(amount: AmountJson) {
|
||||
return <span>{x} {amount.currency}</span>;
|
||||
}
|
||||
|
||||
export const AmountDisplay = ({amount}: {amount: AmountJson}) => renderAmount(amount);
|
||||
|
||||
|
||||
/**
|
||||
* Abbreviate a string to a given length, and show the full
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
DenominationRecord,
|
||||
ExchangeRecord,
|
||||
PreCoinRecord,
|
||||
PurchaseRecord,
|
||||
QueryPaymentResult,
|
||||
ReserveCreationInfo,
|
||||
ReserveRecord,
|
||||
@ -322,6 +323,13 @@ export function returnCoins(args: { amount: AmountJson, exchange: string, sender
|
||||
return callBackend("return-coins", args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Record an error report and display it in a tabl.
|
||||
*
|
||||
* If sameTab is set, the error report will be opened in the current tab,
|
||||
* otherwise in a new tab.
|
||||
*/
|
||||
export function logAndDisplayError(args: any): Promise<void> {
|
||||
return callBackend("log-and-display-error", args);
|
||||
}
|
||||
@ -329,3 +337,11 @@ export function logAndDisplayError(args: any): Promise<void> {
|
||||
export function getReport(reportUid: string): Promise<void> {
|
||||
return callBackend("get-report", { reportUid });
|
||||
}
|
||||
|
||||
export function acceptRefund(refundData: any): Promise<number> {
|
||||
return callBackend("accept-refund", refundData);
|
||||
}
|
||||
|
||||
export function getPurchase(contractTermsHash: string): Promise<PurchaseRecord> {
|
||||
return callBackend("get-purchase", { contractTermsHash });
|
||||
}
|
||||
|
@ -305,13 +305,24 @@ function handleMessage(sender: MessageSender,
|
||||
}
|
||||
case "log-and-display-error":
|
||||
logging.storeReport(detail).then((reportUid) => {
|
||||
chrome.tabs.create({
|
||||
url: chrome.extension.getURL(`/src/webex/pages/error.html?reportUid=${reportUid}`),
|
||||
});
|
||||
const url = chrome.extension.getURL(`/src/webex/pages/error.html?reportUid=${reportUid}`);
|
||||
if (detail.sameTab && sender && sender.tab && sender.tab.id) {
|
||||
chrome.tabs.update(detail.tabId, { url });
|
||||
} else {
|
||||
chrome.tabs.create({ url });
|
||||
}
|
||||
});
|
||||
return;
|
||||
case "get-report":
|
||||
return logging.getReport(detail.reportUid);
|
||||
case "accept-refund":
|
||||
return needsWallet().acceptRefund(detail.refund_permissions);
|
||||
case "get-purchase":
|
||||
const contractTermsHash = detail.contractTermsHash;
|
||||
if (!contractTermsHash) {
|
||||
throw Error("contractTermsHash missing");
|
||||
}
|
||||
return needsWallet().getPurchase(contractTermsHash);
|
||||
default:
|
||||
// Exhaustiveness check.
|
||||
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
|
||||
@ -380,6 +391,9 @@ class ChromeNotifier implements Notifier {
|
||||
|
||||
/**
|
||||
* Mapping from tab ID to payment information (if any).
|
||||
*
|
||||
* Used to pass information from an intercepted HTTP header to the content
|
||||
* script on the page.
|
||||
*/
|
||||
const paymentRequestCookies: { [n: number]: any } = {};
|
||||
|
||||
@ -401,6 +415,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
||||
const fields = {
|
||||
contract_url: headers["x-taler-contract-url"],
|
||||
offer_url: headers["x-taler-offer-url"],
|
||||
refund_url: headers["x-taler-refund-url"],
|
||||
};
|
||||
|
||||
const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
|
||||
@ -415,6 +430,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
||||
const payDetail = {
|
||||
contract_url: fields.contract_url,
|
||||
offer_url: fields.offer_url,
|
||||
refund_url: fields.refund_url,
|
||||
};
|
||||
|
||||
console.log("got pay detail", payDetail);
|
||||
|
@ -76,6 +76,7 @@ module.exports = function (env) {
|
||||
"popup": "./src/webex/pages/popup.tsx",
|
||||
"reset-required": "./src/webex/pages/reset-required.tsx",
|
||||
"return-coins": "./src/webex/pages/return-coins.tsx",
|
||||
"refund": "./src/webex/pages/refund.tsx",
|
||||
"show-db": "./src/webex/pages/show-db.ts",
|
||||
"tree": "./src/webex/pages/tree.tsx",
|
||||
},
|
||||
|
15
yarn.lock
15
yarn.lock
@ -490,6 +490,13 @@ aws4@^1.2.1:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||
|
||||
axios@^0.16.2:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
|
||||
dependencies:
|
||||
follow-redirects "^1.2.3"
|
||||
is-buffer "^1.1.5"
|
||||
|
||||
babel-code-frame@^6.22.0:
|
||||
version "6.22.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
|
||||
@ -1523,7 +1530,7 @@ debug-log@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"
|
||||
|
||||
debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3:
|
||||
debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.4.5, debug@^2.6.3:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||
dependencies:
|
||||
@ -2091,6 +2098,12 @@ fn-name@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
|
||||
|
||||
follow-redirects@^1.2.3:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
|
||||
dependencies:
|
||||
debug "^2.4.5"
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
Loading…
Reference in New Issue
Block a user