@wayke-se/ecom v4.5.0
SDK for consuming Waykes e-commerce APIs
This project aims to provide a simple, yet powerful, way to integrate Waykes e-commerce functionality into retailers own websites.
Installing
npm i --save @wayke-se/ecom
Using
The SDK exposes several namespaces, each containing functionality for a specific set of use cases. The namespaces are:
config
customers
insurances
orders
vehicles
bankid
Configuring
By default the library reads an environment variable called WAYKE_ECOM_API_ADDRESS
to know the location of the restful API. However, this is not always possible or the desired behavior - so as an alternative configuration option, a SDK consumer can manually bind an address for the API. This should preferrably be done in a setup file in the consuming project, and only run once:
import { config } from "@wayke-se/ecom";
const newConfig = {
api: {
address: "[ECOM_API_URL]",
},
};
config.bind(newConfig);
The [ECOM_API_URL]
is either of the following urls:
Environment | Url |
---|---|
Test | https://ecom.wayketech.se |
Production | https://ecom.wayke.se |
The library also tries to guess the origin of the runtime application, to mark where an order originates (when creating it). By default it uses window.location.host
to create what's called a "topic". A "topic" is a categorization for an order origin. Parallell with the "topic", a "channel" is also automatically created for the origin. This value is Web
when window.location.host
is available during the runtime.
To manually set these values, to enable a more granular control over the order origins, a configuration takes these parameters as:
import { config } from "@wayke-se/ecom";
const newConfig = {
api: {
address: "[ECOM_API_URL]",
},
origin: {
topic: "[Dealer Specific Topic]",
channel: "[Ecom Channel Name]",
},
bankIdThumbprint: "[Dealer Specific BankId Certificate Thumbprint]"
};
config.bind(newConfig);
As an example, orders originating from https://www.wayke.se will have a topic Wayke
and a channel wayke.se
. For orders originating from the Wayke iOS app, the topic will be Wayke
and the channel iOS-app
. For Android, the channel will be Android-app
.
Swedish BankId Thumbprint
By default Wayke's BankId certificate is used to verify customer identity. There is an optional configuration property bankIdThumbprint
which allows for dealers to use their own BankId certificate. If the certificate's thumbprint is set that certificate will be used instead, given that it is correctly setup in the Dealer back-office.
Using
The information flow between clients and Wayke should be in a specific order. First of all we should retrieve all order options available for a vehicle, to know how to set up the rest of the information flow.
Retrieving order options
We start off by creating a request builder, which will - when built - return an valid IOrderOptionsRequest
object:
import { orders } from "@wayke-se/ecom";
const request = orders.newOptionsRequest()
.forVehicle("VEHICLE-ID-FROM-WAYKE")
.build();
The resulting request object can now be used to communicate with Waykes e-com API:
const response = await orders.getOptions(request);
The returned IOrderOptionsResponse
exposes helper methods to select partial data points:
.requiresDealerSelection()
returnstrue
when more than one dealer site is available.getOrderVehicle()
returns aIOrderVehicle
.getDealerSites()
return aIDealerOption[]
with all available dealer sites.getPaymentOptions()
returns aIPaymentOption[]
.getDeliveryOptions()
returns aIDeliveryOption[]
.getAccessories()
returns aIAccessory[]
.getInsuranceOption()
returns aIInsuranceOption
if available.getOrderConditions()
returns astring
with conditions for possible orders.getOrderReturnConditions()
returns astring
with return conditions for possible orders.getConditionsPdfUri()
returns astring
with a URI to a PDF containing the conditions.getContactInformation()
returns aIContactInformation
if available.allowsTradeIn()
returnstrue
/false
if the retailer allows a trade-in vehicle for possible orders.isUnavailable()
returnsboolean
defining if the vehicle is unavailable for purchase..isPaymentRequired()
returnsboolean
defining if payment is required for purchase.
These values should provide enough information to continue the boarding process for the customer.
If .requiresDealerSelection()
returns truthy, the customer should be displayed a list of all available dealer sites from .getDealerSites()
. When the customer has made a selection for his or hers preferred dealer site, a new order option request should be made:
const request = orders.newOptionsRequest()
.forVehicle("VEHICLE-ID-FROM-WAYKE")
.forDealer("DEALER-ID-SELECTION")
.build();
Retrieving insurance options
The customer might want to sign up for an insurance as well, and to select a suitable insurance we need to know which payment type the customer would like to proceed with. Payment types are availble from the IPaymentOption
s, and should be added to the request object - as well as the customers swedish personal number, the current selected vehicle, and an estimated driving range (per year).
import { insurances, DrivingDistance } from "@wayke-se/ecom";
const request = insurances.newInsuranceOptionsRequest()
.forCustomer("YYYYMMDD-XXXX")
.forVehicle("VEHICLE-ID-FROM-WAYKE")
.forDealer("DEALER-ID-SELECTION") // optional, should be applied when there's multiple dealer sites available for a vehicle
.withDrivingDistance(DrivingDistance.Between1000And1500)
.build();
const response = await insurances.getOptions(request);
The returned IInsuranceOptionsResponse
exposes only one method:
.getInsuranceOptions()
returns aIInsuranceOption[]
.
Retrieving a customers address based on his or hers personal number
Wayke provides functionality to lookup a customers address, in an anonymized form. This reduces the risk for errors when entering these details by hand, and it lessens the input required to proceed with the order.
import { customers } from "@wayke-se/ecom";
const request = customers.newAddressLookupRequest()
.forCustomer("YYYYMMDD-XXXX")
.build();
const response = await customers.lookupAddress(request);
lookupAddress
returns a IAddressLookupResponse
object, exposing:
.getAddress()
returns the address for the customer, in an anonymized form.
Looking up a vehicle for trade-in
When a customer wants to submit a vehicle for trade-in with the order, we need to look it up through Wayke before hand.
import { vehicles } from "@wayke-se/ecom";
const request = vehicles.newLookupRequest()
.withRegistrationNumber("REGISTRATION-NUMBER")
.build();
const response = await vehicles.lookupVehicle(request);
To include a valuation for the vehicle in question, add a mileage and a VehicleCondition to the request:
const request = vehicles.newLookupRequest()
.withRegistrationNumber("REGISTRATION-NUMBER")
.withMileage(12345)
.withCondition(VehicleCondition.VeryGood)
.build();
The returned IVehicleLookupResponse
exposes only one method:
.getVehicle()
returns aIVehicle
object containing manufacturer, model series, model name, and optionally valuation for the vehicle.
Retrieve payment data for vehicle
Wayke exposes functionality to calculate interest rates, fees, etc, for a vehicle - given a few parameters such as payment duration (in months) and a down payment.
import { payments } from "@wayke-se/ecom";
const request = payments.newLookupRequest()
.forVehicle("VEHICLE-ID-FROM-WAYKE")
.forDealer("DEALER-ID-SELECTION") // optional, should be applied when there's multiple dealer sites available for a vehicle
.withDownPayment(90000)
.withDuration(72)
.withResidualValue(0.2) // optional
.build();
const response = await payments.lookupPayment(request);
The returned IPaymentLookupResponse
exposes methods to retrieve financial data:
.getCosts()
returns aIPaymentCosts
object containing a monthly cost and a total cost..getFees()
returns aIPaymentFees
object containing setup fee and administration fee..getInterests()
returns aIPaymentInterests
object containing the interest and effective interest..getDownPaymentSpec()
returns aIPaymentRangeSpec
defining the ranges for a down payment..getDurationSpec()
returns aIPaymentRangeSpec
defining the ranges for a duration..getResidualValueSpec()
returns aIPaymentRangeSpec
defining the ranges for a residual value..getPrice()
returns anumber
defining the price of the vehicle..getCreditAmount()
returns anumber
defining the total credit amount used..hasPrivacyPolicy()
returns aboolean
defining if the financial provider has a privacy policy..getPrivacyPolicyUrl()
returns astring
defining if the financial provider's privacy policy..shouldUseCreditScoring()
returns aboolean
defining if the vehicle is configured to use real time credit assessments (described below)..getFinancialProductCode()
returns astring
defining if the financial product code (used by real time credit assessments).
Creating order details and an order
To create a valid order we should use appropriate builders, and specify our data in batches:
import { customers, orders, vehicles } from "@wayke-se/ecom";
const customer = customers.newCustomer()
.withAddress(addressLookupResponse.getAddress())
.withPersonalNumber("YYYYMMDD-XXXX")
.withEmail("person.name@email.com")
.withPhoneNumber("0707-123 123")
.build();
const creditAssessment = orders.newCreditAssessment() // Only required for orders using credit assessment
.withScoreId("score-id")
.withFinancialProductCode("code")
.withRecommendation("recommendation")
.withDecision("decision")
.build()
const payment = orders.newPayment()
.withType(aPaymentOption.type)
.withDuration(36) // months, only applicable when payment type === PaymentType.Loan
.withDownPayment(25000) // only applicable when payment type === PaymentType.Loan
.withResidualValue(20000) // only applicable when payment type === PaymentType.Loan
.withExternalId("some-id") // only applicable when payment type === PaymentType.Loan
.withCreditAssessment(creditAssessment) // only applicable for orders that should use credit assessment
.build();
const insurance = orders.newInsurance()
.withDrivingRange(2000)
.withAddOns(["Add On 1", "Add On 2"]) // optional
.build();
const tradein = vehicles.newVehicleTrade()
.forVehicle("REGISTRATION-NUMBER")
.withMileage(7600)
.withCondition(VehicleCondition.VeryGood)
.withComment("A very nice car for daily commute!") // optional
.build();
const request = orders.newCreateRequest()
.forVehicle("VEHICLE-ID-FROM-WAYKE")
.withCustomer(customer)
.withPayment(payment)
.withDeliveryType(aDeliveryOption.type)
.withInsurance(insurance) // optional
.withTradeIn(tradein) // optional
.withAccessories(accessories) // optional
.withUrls(redirectURL, paymentURL) // optional
.build();
const response = await orders.create(request);
As one can see, splitting up the builders over multiple frontend components/views is easy (and encouraged!) to do.
Authenticating using Swedish BankId
Bank id authentication is performed using three methods: auth
, collect
and cancel
. In short, auth
starts a authentication process, collect
retrieves status information about the process and cancel
aborts a process. More information about this can be found here..
Starting a BankId authentication process
const request = bankid
.newAuthRequest()
.withIpAddress("130.242.197.92")
.withMethod(AuthMethod.QrCode)
.build();
try {
await bankid.auth(request);
// Do something with response.
} catch (e) {
// Handle errors.
}
The response of auth contains an order ref. This id is used to track an authentication process and must be included in collect and cancel request.
interface IBankIdAuthResponse {
isQrCode: () => boolean;
isSameDevice: () => boolean;
getOrderRef: () => string;
getQrCode: () => string | undefined;
getAutoLaunchUrl: () => string | undefined;
getMethod: () => AuthMethod;
}
The qr code from getQrCode()
is base64 encoded. It can be rendered in an img tag:
const qrCode = authResponse.getQrCode();
<img src="data:image/png;base64, {qrCode}" />;
Collecting status information from a BankId process
Once a authentication process is started, a collect request should be made approximatelly every 2 seconds.
const request = bankid
.newCollectRequest()
.withOrderRef(data.orderRef)
.build();
try {
await bankid.collect(request);
// Do something with response.
} catch (e) {
// Handle errors.
}
A collect response have a function isPedning()
. While this returns true, additional collect requests should be performed. Additionally, if shouldRenew()
returns true a new auth request should be performed (For example because of an expired qr code).
isComplete
: When this is true, the authentication is succesful and getPersonalNumber()
and getAddress()
should return personal data.
interface IBankIdCollectResponse {
getOrderRef: () => string;
getStatus: () => AuthStatus;
isPending: () => boolean;
getHintCode: () => string | undefined;
hasMessage(): boolean;
getMessage(): string;
shouldRenew(): boolean;
isCompleted(): boolean;
getPersonalNumber(): string | undefined;
getAddress(): IAddress | undefined;
}
Refresh a BankId QR code
Once an authentication process is started with QR code support, the QR code should be refreshed continuously every second.
const cancellation = setInterval(() => {
const request = bankid
.newQrCodeRequest()
.withOrderRef(data.orderRef)
.build();
try {
const response = await bankid.qrcode(request);
// update displayed QR code for consumer
} catch (e) {
// Handle errors.
}
}, 1000);
// When an authentication is successful, clear the interval
//
// clearInterval(cancellation);
The response of qrcode
contains an orderRef and a base64 encoded QR code.
interface IBankIdQrCodeResponse {
getOrderRef: () => string;
getQrCode: () => string;
}
The qr code from getQrCode()
is base64 encoded. It can be rendered in an img tag:
const qrCode = qrcodeResponse.getQrCode();
<img src="data:image/png;base64, {qrCode}" />;
Assess a customers credit
This feature is currently limited to some specific financial providers. This feature is currently limited to loans. Credit inquiries for leases are not yet supported.
For some financial providers it is possible to do a real time credit assessment for a loan. The prerequisites for using real time credit assessments are:
- The dealer that sells the vehicle must have a financial option with credit assessments activated for the vehicle. This includes defining a payment option that:
- Should be a loan.
- Should have an
externalId
. - Should have a
financialProductCode
. - Should have set the
useCreditScoring
flag totrue
.
The steps of this process are:
- Perform a payment lookup request returns a
IPaymentLookupResponse
(described above). This response has a methodshouldUseCreditScoring()
that indicates if credit scoring is available for the vehicle. The response also contains a methodgetFinancialProductCode()
. This methods returns the financial product code of the payment option, which is required for the credit inquiry. - Create the credit assessment case. This includes various household information such as monthly income as well as social id, email and phone number. The provided social id will be used for the bank id signing that is required for the order to be processed.
- The credit assessment case is signed by the customer using Swedish bank id. The signing is towards the financial provider that performs the credit assessment. The status of the signing can be polled using the
creditAssessment.getStatus(caseId)
method. - The credit assessment is performed by the financial provider. The status of the scoring can be polled using the
creditAssessment.getStatus(caseId)
method. Once complete, a result of the inquiry is provided in the status response. - Finally, in order to accept the credit assessment, the case is accepted by the
creditAssessment.accept(caseId)
method.
- If a started case should be abandoned or cancelled for any reason, the
creditAssessment.decline(caseId)
method should be called. This is needed for the financial provider to cancel the case in their systems.
- Note that if the credit assessment flow replaces the bank id flow described above. Both flows can not be used for the same Ecom order.
Starting a new credit assessment cases
Start a new credit inquiry using the financial provider of the vehicle.
const inquiry = {...} // Specify inquiry.
try {
const case = await creditAssessment.newCase(inquiry);
// Do something with response.
} catch (e) {
// Handle errors.
}
To start a case, an ICreditAssessmentInquiry
is required. This includes customer personal information, household finance information as well as information about the loan.
interface ICreditAssessmentInquiry {
externalId: string; // Acquired in `IPaymentOption`
customer: ICreditAssessmentCustomer;
loan: ICreditAssessmentLoan;
householdEconomy: ICreditAssessmentHouseholdEconomy;
}
interface ICreditAssessmentCustomer {
socialId: string;
email: string;
phone: string;
signerIp?: string;
}
interface ICreditAssessmentLoan { // Required data is acquired in `IPaymentLookupResponse`
financialProductId: string;
price: number;
downPayment: number;
credit: number;
interestRate: number;
monthlyCost: number;
term: string; // See below
}
interface ICreditAssessmentHouseholdEconomy {
maritalStatus: MaritalStatus;
income: number;
employment: Employment;
householdChildren: number;
householdIncome: number;
householdHousingCost: number;
}
The newCase
functions returns a ICreditAssessmentCase
that has a case id that is used for all other credit assessment requests.
export interface ICreditAssessmentCase {
caseId: string;
}
Term
The term
field required in ICreditAssessmentLoan
is created from the duration of the loan:
const durationInMonths = 24;
const term = durationInMonths + "months";
for example:
"12months"
"48months"
"72months"
- The months in
term
must be a factor of 12.
The result of the request should be a credit assessment case id. This id is used for all following credit assessment requests.
Signing a credit assessment case with bank id
A credit asssessment case needs to be signed with Swedish bank id. The two supported signing methds are qr code and same device.
const request = {
caseId,
method: AuthMethod.QrCode,
};
creditAssessment.signCase(request)
.then((response) => // Handle success)
.catch((err) => // Handle failure);
The signCase
function returns a ICreditAssessmentSignRespons
that may include the qr code to display or the auto launch url.
export interface ICreditAssessmentSignResponse {
isQrCode: () => boolean;
isSameDevice: () => boolean;
getQrCode: () => string | undefined;
getAutoLaunchUrl: () => string | undefined;
getMethod: () => AuthMethod;
getCaseId: () => string;
}
To collect the results of signing process the getStatus
method is used. This should be done approximately every two seconds, just like bank id collect.
creditAssessment.getStatus(caseId)
.then((status) => // Handle success)
.catch((err) => // Handle failure);
The getStatus
method returns a credit assessment status response:
interface ICreditAssessmentStatusResponse {
getStatus: () => CreditAssessmentStatus;
hasPendingSigning: () => boolean;
getHintCode: () => string | undefined;
getSigningMessage(): string;
shouldRenewSigning(): boolean;
isSigned(): boolean;
getAddress(): IAddress | undefined;
hasPendingScoring: () => boolean;
isScored: () => boolean;
hasScoringError: () => boolean;
getScoringId: () => string | undefined;
getRecommendation: () => CreditAssessmentRecommendation;
getDecision: () => CreditAssessmentDecision;
isAccepted: () => boolean;
}
During the signing the status should be SigningInitiated
. Once the case is signed, the status shuld change to Signed
or ScoringInitiated
.
hasPendingSigning
indicates that a signing is ongoing.getHintCode
returns the current bank id hint code of the signing process.getSigningMessage
returns the message that should be displayed for an ongoing sign process.shouldRenewSigning
indicates that a signing failed for some reason (for example time out) and should be restarted.isSigned
indicates that the user has successfully signed the credit assessment case using Swedish bank id.
It is also possible to cancel the signing process. This should be done if the user wants to change sign method (for example from qr code to same device) or if a new case should be created with new other information.
creditAssessment
.cancelSigning(caseId)
.then(() => // Handle success)
.catch((err) => // Handle failure);
Collecting the assessment result
To acquire the credit assessment result, simply use the getStatus
method (the same one that was used to poll the status of the bank id signing) untill a status response is received with the status Scored
. This response should also have a recommendation and possible a decision.
hasPendingScoring
indicates that a scoring is ongoing.isScored
indicates that the case is scored.getRecommendation
provides the recommendation of the scoring.getDecision
provides the decision of the scoring.
Accepting a credit assessment result
The credit assessment won't be finalized unless it is accepted, which is possible once the case has the Scored
status. This is done with using the accept
method:
creditAssessment
.accept(caseId)
.then(() => // Handle success)
.catch((err) => // Handle failure);
The status returned from the creditAssessment.getStatus
method should now be accepted:
isAccepted
indicates that the case is accepted.
Declining the credit assessment result
Anytime during the credit assessment process, the case may be declined. To do this, use the decline
method:
creditAssessment
.decline(caseId)
.then(() => // Handle success)
.catch((err) => // Handle failure);
16 days ago
27 days ago
28 days ago
28 days ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
8 months ago
9 months ago
9 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
12 months ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago