Skip to content

Commit

Permalink
Userinfoapi (#35)
Browse files Browse the repository at this point in the history
* BREAKING: Move QR paths

* Add Userinfo api
  • Loading branch information
tomas-zijdemans-vipps authored Jan 24, 2024
1 parent 23f5bfc commit 6301901
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 45 deletions.
103 changes: 103 additions & 0 deletions sample_code/user_sample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import "https://deno.land/std@0.212.0/dotenv/load.ts";
// Add back in when the API has been released
// import { Client } from "https://deno.land/x/vipps_mobilepay_sdk@0.8.0/mod.ts";
import { Client } from "../src/mod.ts";

// First, get your API keys from https://portal.vipps.no/
// Here we assume they are stored in a .env file, see .env.example
const clientId = Deno.env.get("CLIENT_ID") || "";
const clientSecret = Deno.env.get("CLIENT_SECRET") || "";

const merchantSerialNumber = Deno.env.get("MERCHANT_SERIAL_NUMBER") || "";
const subscriptionKey = Deno.env.get("SUBSCRIPTION_KEY") || "";

// Create a client
const client = Client({
merchantSerialNumber,
subscriptionKey,
useTestMode: true,
retryRequests: false,
});

// Grab a token
const accessToken = await client.auth.getToken({
clientId,
clientSecret,
subscriptionKey,
});

// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.error);
Deno.exit(1);
}

const token = accessToken.data.access_token;

// Create a payment with profile flow
const payment = await client.payment.create(token, {
amount: {
currency: "NOK",
value: 1000, // This value equals 10 NOK
},
paymentMethod: { type: "WALLET" },
customer: { phoneNumber: "4712345678" },
returnUrl: `https://yourwebsite.com/redirect`,
userFlow: "WEB_REDIRECT",
paymentDescription: "One pair of socks",
profile: { scope: "name phoneNumber address birthDate" },
});

// Check if the payment was created successfully
if (!payment.ok) {
console.error("😟 Error creating payment 😟");
console.error(payment.error);
Deno.exit(1);
}
console.log("🎉 Payment created successfully!");

// Force approve the payment, for testing purposes.
// This requires the customer to have cards registered in Vipps MT
const approve = await client.payment.forceApprove(
token,
payment.data.reference,
{ customer: { phoneNumber: "4712345678" } },
);

// Check if the payment was approved successfully
if (!approve.ok) {
console.error("😟 Error approving payment 😟");
console.error(approve.error);
Deno.exit(1);
}

// Retrive the payment
const paymentInfo = await client.payment.info(token, payment.data.reference);

// Check if the payment was retrieved successfully
if (!paymentInfo.ok) {
console.error("😟 Error retriving payment 😟");
console.error(paymentInfo.error);
Deno.exit(1);
}

const sub = paymentInfo.data.profile.sub;

if (!sub) {
console.error("😟 Error retriving sub 😟");
Deno.exit(1);
}

// Retrive the user info
const userInfo = await client.user.info(token, sub);

// Check if the user info was retrieved successfully
if (!userInfo.ok) {
console.error("😟 Error retriving user info 😟");
console.error(userInfo.error);
Deno.exit(1);
}

console.log("🎉 User info retrieved successfully!");
console.log(userInfo.data);
149 changes: 149 additions & 0 deletions src/apis/types/user_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* Represents an error response for user information.
*/
export type UserInfoError = {
/**
* A URI reference that identifies the problem type.
* @example "https://example.com/validation-error"
*/
type?: string;
/**
* A short, human-readable summary of the problem type. It will not change from occurrence to occurrence of the problem.
* @example "Your request parameters didn't validate."
*/
title?: string;
/**
* The HTTP status code.
* @example 400
*/
status?: string;
/**
* A human-readable explanation specific to this occurrence of the problem.
* @example "The request body contains one or more errors"
*/
detail?: string;
/**
* An ID that can help when troubleshooting.
* @example "123e4567-e89b-12d3-a456-426655440000"
*/
instance?: string;
/**
* Additional information related to the error.
*/
extraInfo?: Record<string, object>;
};

/**
* Represents the user information.
*/
export type UserInfo = {
/** Contains the user's preferred (default) address. */
address?: UserInfoAddress;
/** Contains an array with the user's non-default addresses, if any. This list can contain an address with the address_type home, work, and/or other, if the user has registered them in the Vipps app */
other_addresses?: UserInfoAddress[];
/**
* The user's birthday formatted as YYYY-MM-DD
* @example "2000-12-31"
*/
birthdate?: string;
/**
* The user's email address.
* @example "user@example.com"
*/
email?: string;
/**
* Boolean value indicating whether the user's email address is verified or not.
* @example true
*/
email_verified?: boolean;
/**
* Surname(s) or last name(s) of the user.
* @example "Lovelace"
*/
family_name?: string;
/**
* Given name(s) or first name(s) of the user. Note that in some cultures, people can have multiple given names; all can be present, with the names being separated by space characters.
* @example "Ada"
*/
given_name?: string;
/**
* The user's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the user's locale and preferences.
* @example "Ada Lovelace"
*/
name?: string;
/**
* National identity number.
* For Norway this is the "fødselsnummer": 11 digits.
* The format is "YYYYMMDD" + five digits.
* See https://www.skatteetaten.no/en/person/foreign/norwegian-identification-number/national-identity-number/
* @pattern ^\d{11}$
* @example "09057517287"
*/
nin?: string;
/**
* The user's telephone number.
* The format is MSISDN: Digits only: Country code and subscriber
* number, but no prefix.
* See https://en.wikipedia.org/wiki/MSISDN
* @pattern ^\d{15}$
* @example "4791234567"
*/
phone_number?: string;
/**
* Session identifier: This represents a session of a User Agent or
* device. Currently not in use.
* @example "7d78a726-af92-499e-b857-de263ef9a969"
*/
sid?: string;
/**
* Subject: Unique identifier for the user.
* The sub is based on the user's national identity number (NIN) and
* does not change (except in very special cases).
* The `sub` is the same when the user logs in again and re-consents.
* @example "c06c4afe-d9e1-4c5d-939a-177d752a0944"
*/
sub?: string;
};

/**
* Represents the address information of a user.
*/
export type UserInfoAddress = {
/**
* Address type is either `home`, `work` or `other`.
* @example "home"
*/
address_type?: string;
/**
* Two letter country code
* @format ^[A-Z]{2}$
* @example "NO"
*/
country?: string;
/**
* True if this is the default address
* @example true
*/
default?: boolean;
/**
* The user's address as a formatted string
* @example "Robert Levins gate 5
* 0154 Oslo"
*/
formatted?: string;
/**
* Postal code
* @example "0154"
*/
postal_code?: string;
/**
* The user's region (typically a county, town or city)
* @example "Oslo"
*/
region?: string;
/**
* The user's street address
* @example "Robert Levins gate 5"
*/
street_address?: string;
};
15 changes: 15 additions & 0 deletions src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RequestData } from "../types.ts";
import { UserInfo, UserInfoError } from "./types/user_types.ts";

/**
* Factory object for creating Userinfo API requests.
*/
export const userRequestFactory = {
info(token: string, sub: string): RequestData<UserInfo, UserInfoError> {
return {
url: `/vipps-userinfo-api/userinfo/${sub}`,
method: "GET",
token,
};
},
} as const;
21 changes: 14 additions & 7 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { ClientConfig } from "./types.ts";
import { baseClient } from "./base_client.ts";
import { ApiClient, createApi } from "./api_proxy.ts";
import { authRequestFactory } from "./apis/auth.ts";
import { ePaymentRequestFactory } from "./apis/epayment.ts";
import { webhooksRequestFactory } from "./apis/webhooks.ts";
import { checkoutRequestFactory } from "./apis/checkout.ts";
import { agreementRequestFactory } from "./apis/recurring.ts";
import { chargeRequestFactory } from "./apis/recurring.ts";
import { ePaymentRequestFactory } from "./apis/epayment.ts";
import { loginRequestFactory } from "./apis/login.ts";
import {
callbackQRRequestFactory,
redirectQRRequestFactory,
} from "./apis/qr.ts";
import { loginRequestFactory } from "./apis/login.ts";
import {
agreementRequestFactory,
chargeRequestFactory,
} from "./apis/recurring.ts";
import { userRequestFactory } from "./apis/user.ts";
import { webhooksRequestFactory } from "./apis/webhooks.ts";

/**
* Export all API types, for convenience. All exported types are
Expand All @@ -23,6 +26,7 @@ export type * from "./apis/types/epayment_types.ts";
export type * from "./apis/types/login_types.ts";
export type * from "./apis/types/qr_types.ts";
export type * from "./apis/types/recurring_types.ts";
export type * from "./apis/types/user_types.ts";
export type * from "./apis/types/webhooks_types.ts";

/**
Expand All @@ -37,15 +41,18 @@ export const Client = (options: ClientConfig) => {
// Create the API client
const apiClient = {
auth: createApi(client, authRequestFactory),
callbackQR: createApi(client, callbackQRRequestFactory),
checkout: createApi(client, checkoutRequestFactory),
login: createApi(client, loginRequestFactory),
payment: createApi(client, ePaymentRequestFactory),
qr: {
callback: createApi(client, callbackQRRequestFactory),
redirect: createApi(client, redirectQRRequestFactory),
},
recurring: {
charge: createApi(client, chargeRequestFactory),
agreement: createApi(client, agreementRequestFactory),
},
redirectQR: createApi(client, redirectQRRequestFactory),
user: createApi(client, userRequestFactory),
webhook: createApi(client, webhooksRequestFactory),
} satisfies ApiClient;

Expand Down
3 changes: 1 addition & 2 deletions tests/api_proxy_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ Deno.test("createApi - Should return an error if method is not in factory", asyn
},
};

// deno-lint-ignore no-explicit-any
const api = createApi(client, factory) as any;
const api = createApi(client, factory) as unknown as { bar(): Promise<void> };

try {
await api.bar();
Expand Down
22 changes: 17 additions & 5 deletions tests/checkout_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { checkoutRequestFactory } from "../src/apis/checkout.ts";
import { CheckoutInitiateSessionRequest } from "../src/apis/types/checkout_types.ts";
import { assertEquals, assertExists, assertNotEquals } from "./test_deps.ts";
import {
assert,
assertEquals,
assertExists,
assertNotEquals,
} from "./test_deps.ts";

Deno.test("create - should return the correct request data", () => {
const client_id = "your_client_id";
Expand Down Expand Up @@ -58,11 +63,18 @@ Deno.test("create - should fill in missing properties", () => {
client_id,
client_secret,
body,
// deno-lint-ignore no-explicit-any
) as any;
);

assertExists(requestData.body.transaction.reference);
assertExists(requestData.body.merchantInfo.callbackAuthorizationToken);
assertExists(requestData.body);
assert("transaction" in requestData.body);
assert(
"reference" in (requestData.body.transaction as Record<string, unknown>),
);
assert("merchantInfo" in requestData.body);
assert(
"callbackAuthorizationToken" in
(requestData.body.merchantInfo as Record<string, unknown>),
);
});

Deno.test("info - should return the correct request data", () => {
Expand Down
6 changes: 3 additions & 3 deletions tests/epayment_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ Deno.test("ePayment - create - Should fill in missing props", () => {
userFlow: "WEB_REDIRECT",
paymentDescription: "One pair of socks",
},
// deno-lint-ignore no-explicit-any
) as any;
);

assertExists(requestData.body.reference);
assertExists(requestData.body);
assertExists("reference" in requestData.body);
});

Deno.test("ePayment - info - Should have correct url and header", async () => {
Expand Down
Loading

0 comments on commit 6301901

Please sign in to comment.